From fc90dc5b38fd5f2f6c63d1645edb9d8303ea5d93 Mon Sep 17 00:00:00 2001 From: wolter <11@gmail> Date: Mon, 15 Dec 2025 15:48:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BF=AB=E9=80=92=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E4=B8=8E=E4=BC=81=E4=B8=9A=E6=9F=A5=E8=AF=A2=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_test.yaml | 10 + go.mod | 4 +- go.sum | 6 + internal/config/config.go | 4 + internal/tools/manager.go | 10 + internal/tools/public/coze_company.go | 264 ++++++++++++++++++++++++++ internal/tools/public/coze_express.go | 140 ++++++++++++++ 7 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 internal/tools/public/coze_company.go create mode 100644 internal/tools/public/coze_express.go diff --git a/config/config_test.yaml b/config/config_test.yaml index 85a7eb1..0ef429d 100644 --- a/config/config_test.yaml +++ b/config/config_test.yaml @@ -75,6 +75,16 @@ tools: enabled: true base_url: "https://restapi.amap.com/v3/weather/weatherInfo" api_key: "12afbde5ab78cb7e575ff76bd0bdef2b" + cozeExpress: + enabled: true + base_url: "https://api.coze.cn" + api_key: "7582477438102552616" + api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" + cozeCompany: + enabled: true + base_url: "https://api.coze.cn" + api_key: "7583905168607100978" + api_secret: "pat_eEN0BdLNDughEtABjJJRYTW71olvDU0qUbfQUeaPc2NnYWO8HeyNoui5aR9z0sSZ" diff --git a/go.mod b/go.mod index 0f01662..a398d0d 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 // indirect + github.com/coze-dev/coze-go v0.0.0-20251029161603-312b7fd62d20 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -59,8 +60,9 @@ require ( github.com/fasthttp/websocket v1.5.3 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/goph/emperror v0.17.2 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect diff --git a/go.sum b/go.sum index c1b06f6..b7f99b7 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2/go.mod h1:S4OkvglPY9hsm9tXe github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coze-dev/coze-go v0.0.0-20251029161603-312b7fd62d20 h1:m6P88V9lLrxZsE7uj9otq7l7nqDuCSAJ86KhzRlWf0M= +github.com/coze-dev/coze-go v0.0.0-20251029161603-312b7fd62d20/go.mod h1:wdT5CFt/sFsWz9hna2Z7DWzUra9spx0SoX1PUZyoSB0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -200,6 +202,8 @@ github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPArei github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -269,6 +273,8 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= diff --git a/internal/config/config.go b/internal/config/config.go index 160275f..df79ed0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -105,6 +105,10 @@ type ToolsConfig struct { ZltxOrderAfterSaleReseller ToolConfig `mapstructure:"zltxOrderAfterSaleReseller"` // 下游批充订单售后 ZltxOrderAfterSaleResellerBatch ToolConfig `mapstructure:"zltxOrderAfterSaleResellerBatch"` + // Coze 快递查询工具 + CozeExpress ToolConfig `mapstructure:"cozeExpress"` + // Coze 公司查询工具 + CozeCompany ToolConfig `mapstructure:"cozeCompany"` } // ToolConfig 单个工具配置 diff --git a/internal/tools/manager.go b/internal/tools/manager.go index 87212b7..7025d8c 100644 --- a/internal/tools/manager.go +++ b/internal/tools/manager.go @@ -92,6 +92,16 @@ func NewManager(config *config.Config, llm *utils_ollama.Client) *Manager { weatherTool := public.NewWeatherTool(config.Tools.Weather) m.tools[weatherTool.Name()] = weatherTool } + // 注册 Coze 快递查询工具 + if config.Tools.CozeExpress.Enabled { + cozeTool := public.NewCozeExpress(config.Tools.CozeExpress, m.llm) + m.tools[cozeTool.Name()] = cozeTool + } + // 注册 Coze 公司查询工具 + if config.Tools.CozeCompany.Enabled { + cozeTool := public.NewCozeCompany(config.Tools.CozeCompany, m.llm) + m.tools[cozeTool.Name()] = cozeTool + } // 普通对话 chat := public.NewNormalChatTool(m.llm, config) diff --git a/internal/tools/public/coze_company.go b/internal/tools/public/coze_company.go new file mode 100644 index 0000000..3f10638 --- /dev/null +++ b/internal/tools/public/coze_company.go @@ -0,0 +1,264 @@ +package public + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/entitys" + "ai_scheduler/internal/pkg/utils_ollama" + "context" + "encoding/json" + "fmt" + "github.com/ollama/ollama/api" + "net/http" + "time" + + "github.com/coze-dev/coze-go" +) + +type CozeCompany struct { + cozeApi coze.CozeAPI + config config.ToolConfig + llm *utils_ollama.Client +} + +// NewCoze 创建 Coze 实例 +func NewCozeCompany(config config.ToolConfig, llm *utils_ollama.Client) *CozeCompany { + return &CozeCompany{ + cozeApi: newCozeApi(config), + config: config, + llm: llm, + } +} + +// newCozeClient 创建 Coze 客户端 +func newCozeApi(config config.ToolConfig) coze.CozeAPI { + authCli := coze.NewTokenAuth(config.APISecret) + cozeApi := coze.NewCozeAPI(authCli, coze.WithBaseURL(config.BaseURL), coze.WithHttpClient(&http.Client{ + Timeout: time.Second * 120, + })) + return cozeApi +} + +// Name 返回工具名称 +func (c *CozeCompany) Name() string { + return "coze_company" +} + +// Description 返回工具描述 +func (c *CozeCompany) Description() string { + return "查询企业信息" +} + +// Definition 返回工具定义 +func (c *CozeCompany) Definition() entitys.ToolDefinition { + return entitys.ToolDefinition{ + Type: "function", + Function: entitys.FunctionDef{ + Name: c.Name(), + Description: c.Description(), + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "company_name": map[string]interface{}{ + "type": "string", + "description": "企业名称", + }, + }, + "required": []string{"company_name"}, + }, + }, + } +} + +// Execute 执行查询 +func (c *CozeCompany) Execute(ctx context.Context, requireData *entitys.RequireData) error { + var req map[string]interface{} + if err := json.Unmarshal([]byte(requireData.Match.Parameters), &req); err != nil { + return fmt.Errorf("invalid express request: %w", err) + } + + if req["company_name"] == "" { + return fmt.Errorf("company_name is required") + } + + // 调用 Coze 工作流 + rsp, err := c.callWorkflow(ctx, req) + if err != nil { + return fmt.Errorf("failed to get real weather: %w", err) + } + + companyInfo := CompanyInfo{} + err = json.Unmarshal([]byte(rsp.Data), &companyInfo) + if err != nil { + return fmt.Errorf("failed to unmarshal company info: %w", err) + } + + // 调用 LLM 模型 + err = c.llm.ChatStream(ctx, requireData.Ch, []api.Message{ + { + Role: "system", + Content: `# Role: 企业信息分析与经营诊断专家: +请基于以下12项指定数据字段(无需补充未提供的信息),完成目标企业的全维度分析总结,要求每部分结论必须100%锚定对应数据,拒绝主观推测,突出“风险可见性”与“关键信息关联性”: +一、输入数据清单(需逐一对应分析) +行政处罚:公司是否有行政处罚(含处罚事由、处罚机关、处罚日期、处罚文号) +清算信息:公司的清算信息(含清算原因、清算组构成、清算进展状态) +变更记录:公司的变更记录(含变更事项:注册资本/股东/法定代表人/经营范围、变更时间、变更前后内容) +主要成员:公司名搜索公司的主要成员(含姓名、职位、任职时间、核心履历关键词) +企业详情:公司名称,搜索公司详细信息(含成立时间、注册资本、经营范围、行业分类、注册地址) +经营异常:公司是否有经营异常(含列入原因、列入日期、移出状态) +破产重组:公司破产重组的信息(含申请法院、受理时间、重组方案核心内容) +严重违法:是否有严重违法信息(含违法事由、认定机关、公示期限) +司法信息:司法信息(含案件类型、原被告身份、判决结果/进展) +被执行信息:公司的被执行信息(含执行法院、执行标的、未履行金额、失信状态) +企业手机号:公司名称查询企业手机号(需标注是否为公开备案号) +股东信息:公司对应的股东(含股东名称、出资额、出资比例、股东类型:自然人/企业/机构) +二、分析总结框架(严格对应数据) +1. 企业基础画像(锚定公司名称,搜索公司详细信息) +核心属性:成立时间、注册资本(实缴/认缴)、行业分类(如“批发和零售业”“软件和信息技术服务业”)、主营业务(从经营范围中提炼1-2个核心赛道,如“专注于智能仓储设备研发与销售”); +注册地址:是否与主要经营地一致(若公司名称,搜索公司详细信息有披露)。 +2. 股权结构与股东特征(锚定公司对应的股东) +股权集中度:前三大股东出资占比之和(如“第一大股东持股60%,为绝对控股股东”); +股东类型分布:自然人股东、企业股东、机构股东的占比(如“70%为企业股东,含1家行业头部企业”); +关键股东亮点:若有知名企业/机构股东,需明确标注(如“股东含XX产业投资基金,具备产业链资源协同潜力”)。 +3. 经营稳定性与合规风险(锚定公司是否有行政处罚/公司是否有经营异常/是否有严重违法信息/公司的清算信息/公司破产重组的信息) +行政处罚风险:是否存在行政处罚? +若有:列清「处罚事由(如“虚假宣传”“税务逾期申报”)、处罚机关、处罚日期」,并判断“是否属于高频违规”(如1年内≥2次同类处罚→高风险); +若无:标注“无公开行政处罚记录”。 +经营异常风险:是否存在经营异常? +若有:说明「列入原因(如“通过登记的住所无法联系”“未公示年度报告”)、是否已移出」,并评估对业务的影响(如“地址失联可能导致客户信任下降”); +若无:标注“无经营异常记录”。 +严重违法红线:是否存在严重违法? +若有:明确「违法事由(如“拒不履行生效法律文书”“欺诈消费者”)、认定机关、公示期限」,并标注“触及监管红线,需重点核查整改情况”; +若无:标注“无严重违法记录”。 +极端状态预警:是否存在清算或破产重组? +若有清算:说明「清算原因(如“股东会决议解散”“营业期限届满”)、清算进展(如“清算组已完成资产清查”)」; +若有破产重组:说明「申请法院、受理时间、重组方案核心内容(如“拟引入战略投资者注资5000万”)」; +若无:标注“无清算或破产重组记录”。 +4. 司法与执行风险(锚定司法信息/公司的被执行信息) +涉诉情况:司法案件的核心特征(如“80%为买卖合同纠纷,20%为劳动争议仲裁”;“作为被告的案件占比75%”;“判决胜诉率约60%”); +被执行压力:是否存在被执行信息? +若有:列清「执行法院、执行标的金额、未履行金额、是否纳入失信被执行人名单」,并计算“未履行金额占注册资本的比例”(如“未履行800万,占注册资本的20%”); +若无:标注“无公开被执行记录”。 +5. 管理团队与联系信息(锚定公司名搜索公司的主要成员/公司名称查询企业手机号) +核心团队稳定性:主要成员的任职时间分布(如“CEO任职4年,CFO任职2年,核心团队平均任职3年”);是否有频繁变动(如“1年内2位高管离职”→需提示“管理稳定性风险”); +关键岗位资质:核心成员(如法定代表人、CEO、CFO)的过往履历亮点(如“CEO曾任职XX上市公司,主导过亿元级项目落地”); +联系信息可信度:企业手机号是否为公开备案(如“与工商登记预留电话一致→可信度高”;“为非备案号→需提示‘联系信息真实性存疑’”)。 +6. 综合结论与行动建议(整合所有数据) +整体风险评级:基于数据密度,给出“低风险/中风险/高风险”定性(示例:“无行政处罚、无被执行、仅1条普通合同纠纷→低风险”;“有失信被执行+严重违法→高风险”); +Top3核心风险:按“严重违法>被执行>破产重组>行政处罚>经营异常>司法纠纷”排序,列出最需关注的3个问题(如“1. 未履行金额占注册资本20%,存在债务违约风险;2. 1年内2次地址异常,经营稳定性弱;3. 自然人股东占比过高,决策易受个人因素影响”); + actionable 建议:针对每个核心风险,给出可执行的核查/应对动作(如“核查未履行金额对应的案件进展,评估企业偿债能力;要求企业提供近1年的地址证明,确认经营场所稳定性;穿透核查自然人股东的资产状况,降低决策风险”)。 +三、输出规则(强制遵守) +数据溯源:每句结论必须标注对应数据字段(如“根据公司是否有行政处罚,企业2023年因‘税务逾期申报’被区税务局处罚”); +量化优先:拒绝模糊表述(如不说“很多案件”,要说“近1年涉及5起买卖合同纠纷”); +风险分级:用“★”标注风险等级(★越多越严重,如“严重违法★★★”“被执行★★”“经营异常★”); +语言风格:专业简洁,避免冗余,适合风控/投资/合作前的快速决策阅读。 + +【示例输出片段】 +3. 经营稳定性与合规风险 + +行政处罚:根据公司是否有行政处罚,企业2022年11月因“发布虚假广告”被区市场监管局处以3万元罚款(文号:X市监罚字〔2022〕456号),无后续同类处罚→风险等级★; + +经营异常:根据公司是否有经营异常,企业2023年6月因“通过登记的住所无法联系”被列入异常名录,2023年12月已移出→风险等级★; + +严重违法:根据是否有严重违法信息,无公开严重违法记录→风险等级无; + +极端状态:根据公司的清算信息/公司破产重组的信息,无清算或破产重组记录→风险等级无。 + +6. 综合结论与行动建议 + +整体评级:低风险(仅1次轻微行政处罚,无重大合规瑕疵); + +Top3核心风险:1. 根据司法信息,近1年作为被告的合同纠纷占比75%,需警惕应收账款回收风险★★;2. 根据公司名搜索公司的主要成员,1年内2位销售总监离职,管理稳定性弱★;3. 根据公司名称查询企业手机号,企业手机号为非备案号,联系信息真实性存疑★; + +建议:核查合同纠纷案件的原告身份及回款情况,评估坏账概率;要求企业提供离职人员的交接说明,确认业务连续性;索要企业备案的联系方式,验证沟通有效性。 +`, + }, + + { + Role: "assistant", + Content: fmt.Sprintf(`请分析企业:%s, +公司是否有行政处罚:%v, +公司的清算信息:%v, +公司的变更记录:%v, +公司名搜索公司的主要成员:%v, +公司名称,搜索公司详细信息:%v, +公司是否有经营异常:%v, +公司破产重组的信息:%v, +是否有严重违法信息:%v, +司法信息:%v, +公司的被执行信息:%v, +公司名称查询企业手机号:%v, +公司对应的股东:%v`, req["company_name"], companyInfo.Xzcf, companyInfo.Clears, companyInfo.Changes, companyInfo.Employees, companyInfo.Searchdata, companyInfo.Operations, companyInfo.BankruptcyPublicList, companyInfo.Illegals, companyInfo.JudicialList, companyInfo.Executes, companyInfo.Phone, companyInfo.Partners), + }, + { + Role: "user", + Content: requireData.Req.Text, + }, + }, + c.Name(), "") + if err != nil { + return fmt.Errorf("failed to get express info: %w", err) + } + + //entitys.ResText(requireData.Ch, "", rsp.Data) + + return nil +} + +// CallWorkflow 调用 Coze 工作流 +// 参数: +// - ctx: 上下文,用于控制超时和取消 +// - workflowId: 工作流 ID +// - params: 工作流参数 +// 返回: +// - interface{}: 工作流执行结果 +// - error: 错误信息 +func (c *CozeCompany) callWorkflow(ctx context.Context, params map[string]interface{}) (*coze.RunWorkflowsResp, error) { + // 准备工作流请求参数 + workflowReq := &coze.RunWorkflowsReq{ + WorkflowID: c.config.APIKey, + Parameters: params, + } + + // 调用工作流 + resp, err := c.cozeApi.Workflows.Runs.Create(ctx, workflowReq) + if err != nil { + return nil, fmt.Errorf("工作流调用失败: %w", err) + } + + // 处理工作流响应 + if resp == nil { + return nil, fmt.Errorf("工作流响应为空") + } + + // 返回工作流执行结果 + return resp, nil +} + +type CompanyInfo struct { + BankruptcyPublicList interface{} `json:"bankruptcy_public_list"` // 破产公示列表 + Changes interface{} `json:"changes"` // 变更记录 + Clears interface{} `json:"clears"` // 清算记录 + Employees interface{} `json:"employees"` // 员工列表 + Executes interface{} `json:"executes"` // 执行记录 + Illegals interface{} `json:"illegals"` // 违法记录 + JudicialList interface{} `json:"judicial_list"` // 司法记录 + Operations interface{} `json:"operations"` // 经营记录 + Partners interface{} `json:"partners"` // 合伙人列表 + Phone string `json:"phone"` // 联系电话 + // 搜索数据 + Searchdata struct { + Authority interface{} `json:"authority"` + BusinessScope interface{} `json:"business_scope"` + Capital interface{} `json:"capital"` + CompanyAddress interface{} `json:"company_address"` + CompanyName string `json:"company_name"` + CompanyStatus interface{} `json:"company_status"` + CompanyType interface{} `json:"company_type"` + CreditNo interface{} `json:"credit_no"` + EstablishDate interface{} `json:"establish_date"` + Industry interface{} `json:"industry"` + LegalPerson interface{} `json:"legal_person"` + Province interface{} `json:"province"` + } `json:"searchdata"` + Xzcf interface{} `json:"xzcf"` // 行政处罚 +} diff --git a/internal/tools/public/coze_express.go b/internal/tools/public/coze_express.go new file mode 100644 index 0000000..de316b5 --- /dev/null +++ b/internal/tools/public/coze_express.go @@ -0,0 +1,140 @@ +package public + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/entitys" + "ai_scheduler/internal/pkg" + "ai_scheduler/internal/pkg/utils_ollama" + "context" + "encoding/json" + "fmt" + "github.com/ollama/ollama/api" + + "github.com/coze-dev/coze-go" +) + +type CozeExpress struct { + cozeApi coze.CozeAPI + config config.ToolConfig + llm *utils_ollama.Client +} + +// NewCozeExpress 创建 CozeExpress 实例 +func NewCozeExpress(config config.ToolConfig, llm *utils_ollama.Client) *CozeExpress { + return &CozeExpress{ + cozeApi: newCozeApi(config), + config: config, + llm: llm, + } +} + +// newCozeExpressClient 创建 CozeExpress 客户端 +func newCozeExpressApi(config config.ToolConfig) coze.CozeAPI { + authCli := coze.NewTokenAuth(config.APISecret) + cozeApi := coze.NewCozeAPI(authCli, coze.WithBaseURL(config.BaseURL)) + return cozeApi +} + +// Name 返回工具名称 +func (c *CozeExpress) Name() string { + return "coze_express" +} + +// Description 返回工具描述 +func (c *CozeExpress) Description() string { + return "查询快递物流信息" +} + +// Definition 返回工具定义 +func (c *CozeExpress) Definition() entitys.ToolDefinition { + return entitys.ToolDefinition{ + Type: "function", + Function: entitys.FunctionDef{ + Name: c.Name(), + Description: c.Description(), + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "express_id": map[string]interface{}{ + "type": "string", + "description": "快递单号", + }, + }, + "required": []string{"express_id"}, + }, + }, + } +} + +// Execute 执行查询 +func (c *CozeExpress) Execute(ctx context.Context, requireData *entitys.RequireData) error { + var req map[string]interface{} + if err := json.Unmarshal([]byte(requireData.Match.Parameters), &req); err != nil { + return fmt.Errorf("invalid express request: %w", err) + } + + if req["express_id"] == "" { + return fmt.Errorf("express_id is required") + } + + // 调用 Coze 工作流查询快递物流信息 + rsp, err := c.callWorkflow(ctx, req) + if err != nil { + return fmt.Errorf("failed to get real weather: %w", err) + } + err = c.llm.ChatStream(ctx, requireData.Ch, []api.Message{ + { + Role: "system", + Content: "你是一个快递查询助手。用户可能会提供快递单号,你需要分析快递单号,根据快递单号查询物流信息并反馈给我", + }, + { + Role: "assistant", + Content: fmt.Sprintf("聊天记录:%s", pkg.JsonStringIgonErr(requireData.Histories)), + }, + { + Role: "assistant", + Content: fmt.Sprintf("需要分析的快递单号:%s", rsp.Data), + }, + { + Role: "user", + Content: requireData.Req.Text, + }, + }, c.Name(), "") + if err != nil { + return fmt.Errorf("failed to get express info: %w", err) + } + + //entitys.ResText(requireData.Ch, "", rsp.Data) + + return nil +} + +// CallWorkflow 调用 Coze 工作流 +// 参数: +// - ctx: 上下文,用于控制超时和取消 +// - workflowId: 工作流 ID +// - params: 工作流参数 +// 返回: +// - interface{}: 工作流执行结果 +// - error: 错误信息 +func (c *CozeExpress) callWorkflow(ctx context.Context, params map[string]interface{}) (*coze.RunWorkflowsResp, error) { + // 准备工作流请求参数 + workflowReq := &coze.RunWorkflowsReq{ + WorkflowID: c.config.APIKey, + Parameters: params, + } + + // 调用工作流 + resp, err := c.cozeApi.Workflows.Runs.Create(ctx, workflowReq) + if err != nil { + return nil, fmt.Errorf("工作流调用失败: %w", err) + } + + // 处理工作流响应 + if resp == nil { + return nil, fmt.Errorf("工作流响应为空") + } + + // 返回工作流执行结果 + return resp, nil +}