From 5b789e557a876bb270844bb7601729b608429a7d Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Fri, 19 Dec 2025 18:38:06 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=201.=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=B4=A7=E6=98=93=E9=80=9A=E5=95=86=E5=93=81=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=202.=20=E6=96=B0=E5=A2=9E=E8=B4=A7=E6=98=93?= =?UTF-8?q?=E9=80=9A=E5=95=86=E5=93=81=E4=B8=8A=E4=BC=A0=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=203.=20=E6=96=B0=E5=A2=9E=E5=95=86=E5=93=81=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E8=87=B3=E8=B4=A7=E6=98=93=E9=80=9A=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config_env.yaml | 3 + internal/config/config.go | 2 + internal/data/constants/capability.go | 174 ++++++++++++++++++ .../domain/tools/hyt/product_upload/client.go | 60 ++++++ .../tools/hyt/product_upload/client_test.go | 60 ++++++ .../domain/tools/hyt/product_upload/types.go | 54 ++++++ .../domain/workflow/hyt/product_upload.go | 112 +++++++++++ internal/domain/workflow/runtime/registry.go | 2 +- .../zltx/order_after_reseller_batch.go | 14 +- internal/pkg/util/map.go | 14 ++ internal/server/router/router.go | 8 +- internal/services/capability.go | 107 +++++------ 12 files changed, 548 insertions(+), 62 deletions(-) create mode 100644 internal/data/constants/capability.go create mode 100644 internal/domain/tools/hyt/product_upload/client.go create mode 100644 internal/domain/tools/hyt/product_upload/client_test.go create mode 100644 internal/domain/tools/hyt/product_upload/types.go create mode 100644 internal/domain/workflow/hyt/product_upload.go create mode 100644 internal/pkg/util/map.go diff --git a/config/config_env.yaml b/config/config_env.yaml index 23a123b..aa8b07b 100644 --- a/config/config_env.yaml +++ b/config/config_env.yaml @@ -74,6 +74,9 @@ tools: zltxOrderAfterSaleResellerBatch: enabled: true base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai" + hytProductUpload: + enabled: true + base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/oursProduct" diff --git a/internal/config/config.go b/internal/config/config.go index ee0c1a5..0c3f4dd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -141,6 +141,8 @@ type ToolsConfig struct { CozeExpress ToolConfig `mapstructure:"cozeExpress"` // Coze 公司查询工具 CozeCompany ToolConfig `mapstructure:"cozeCompany"` + // 货易通商品上传 + HytProductUpload ToolConfig `mapstructure:"hytProductUpload"` } // ToolConfig 单个工具配置 diff --git a/internal/data/constants/capability.go b/internal/data/constants/capability.go new file mode 100644 index 0000000..4ee518b --- /dev/null +++ b/internal/data/constants/capability.go @@ -0,0 +1,174 @@ +package constants + +// Token +const ( + CapabilityProductIngestToken = "A7f9KQ3mP2X8LZC4R5e" +) + +// Prompt +const ( + SystemPrompt = ` + #你是一个专业的商品属性提取助手,你的任务是根据用户输入提取商品的属性信息。 + 目标属性模板:%s。 + 1.最终输出格式为纯JSON字符串,键值对对应目标属性和提取到的属性值。 + 2.最终输出不要携带markdown标识,不要携带回车换行` +) + +// 商品属性模板-中文 +const ( + // 货易通商品属性模板-中文 + HYTProductPropertyTemplateZH = `{ + "条码": "string", // 商品编号 + "分类名称": "string", // 商品分类 + "货品名称": "string", // 商品名称 + "货品编号": "string", // 商品编号 + "商品货号": "string", // 商品编号 + "品牌": "string", // 商品品牌 + "单位": "string", // 商品单位,若无则使用'个' + "规格参数": "string", // 商品规格参数 + "货品说明": "string", // 商品说明 + "保质期": "string", // 商品保质期 + "保质期单位": "string", // 商品保质期单位 + "链接": "string", // + "货品图片": ["string"], // 商品多图,取1-2个即可 + "电商销售价格": "decimal(10,2)", // 商品电商销售价格 + "销售价": "decimal(10,2)", // 商品销售价格 + "供应商报价": "decimal(10,2)", // 商品供应商报价 + "税率": "number%", // 商品税率 x% + "默认供应商": "", // 空即可 + "默认存放仓库": "", // 空即可 + "备注": "", // 备注 + "长": "string", // 商品长度,decimal(10,2)+单位 + "宽": "string", // 商品宽度,decimal(10,2)+单位 + "高": "string", // 商品高度,decimal(10,2)+单位 + "重量": "string", // 商品重量(kg) + "SPU名称": "string", // 商品SPU名称 + "SPU编码": "string" // 编码串,jd_{timestamp}_rand(1000-999) + }` +) + +// 商品属性模板 +const ( + HYTProductPropertyTemplate = `{ + "important_data": { + "type": "object", + "properties": { + "supplier_name": { + "type": "string", + "description": "供应商名称" + }, + "warehouse_name": { + "type": "string", + "description": "仓库名称" + }, + "profit": { + "type": "float64", + "description": "利润 decimal(10,2)" + }, + "tax_rate": { + "type": "integer", + "description": "税率 (x)%" + } + } + }, + "goods_info": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "商品名称" + }, + "brand": { + "type": "string", + "description": "品牌" + }, + "category": { + "type": "string", + "description": "分类" + }, + "price": { + "type": "float64", + "description": "市场价 decimal(10,2)" + }, + "sales_price": { + "type": "float64", + "description": "建议销售价 decimal(10,2)" + }, + "discount": { + "type": "integer", + "description": "折扣百分比 公式:(市场价-建议销售价)/市场价*100" + }, + "goods_attributes": { + "type": "string", + "description": "商品属性" + }, + "goods_bar_code": { + "type": "string", + "description": "商品条码" + }, + "goods_illustration": { + "type": "string", + "description": "商品插图" + }, + "goods_num": { + "type": "string", + "description": "商品编号" + }, + "introduction": { + "type": "string", + "description": "商品介绍" + }, + "spu_name": { + "type": "string", + "description": "SPU名称" + }, + "spu_num": { + "type": "string", + "description": "SPU编号" + }, + "stock": { + "type": "integer", + "description": "库存" + }, + "tax_rate": { + "type": "integer", + "description": "税率" + }, + "unit": { + "type": "string", + "description": "单位" + }, + "weight": { + "type": "string", + "description": "重量" + } + } + }, + "goods_media_list": { + "type": "array", + "description": "商品媒体文件列表", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "媒体文件URL" + }, + "type": { + "type": "integer", + "description": "媒体类型(1:图片, 2:视频)" + }, + "sort": { + "type": "integer", + "description": "排序序号" + } + } + } + } + }` +) + +// 外部平台地址 +const ( + HYTProductListPageURL = "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage" +) diff --git a/internal/domain/tools/hyt/product_upload/client.go b/internal/domain/tools/hyt/product_upload/client.go new file mode 100644 index 0000000..4924b50 --- /dev/null +++ b/internal/domain/tools/hyt/product_upload/client.go @@ -0,0 +1,60 @@ +package product_upload + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/pkg/l_request" + "ai_scheduler/internal/pkg/util" + "context" + "encoding/json" + "errors" +) + +func Call(ctx context.Context, cfg config.ToolConfig, toolReq *ProductUploadRequest) (toolResp *ProductUploadResponse, err error) { + // 商品有且只能有一个 + if len(toolReq.GoodsList) != 1 { + err = errors.New("商品只能有一个") + return + } + + apiReq, _ := util.StructToMap(toolReq) + + req := l_request.Request{ + Method: "Post", + Url: "http://120.55.12.245:8100/api/v1/goods/supplier/batch/add/complete", + Json: apiReq, + } + res, err := req.Send() + + if err != nil { + return + } + + type resType struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data struct { + Ids []int `json:"ids"` // 预览URL + } `json:"data"` + } + var resMap resType + err = json.Unmarshal([]byte(res.Text), &resMap) + if err != nil { + return + } + if resMap.Code != 200 { + err = errors.New("货易通商品创建失败") + return + } + if len(resMap.Data.Ids) == 0 { + err = errors.New("货易通商品创建失败") + return + } + + toolResp = &ProductUploadResponse{ + PreviewUrl: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage", + SpuNum: toolReq.GoodsList[0].GoodsInfo.SpuNum, + Id: resMap.Data.Ids[0], + } + + return toolResp, nil +} diff --git a/internal/domain/tools/hyt/product_upload/client_test.go b/internal/domain/tools/hyt/product_upload/client_test.go new file mode 100644 index 0000000..2f4b01b --- /dev/null +++ b/internal/domain/tools/hyt/product_upload/client_test.go @@ -0,0 +1,60 @@ +package product_upload + +import ( + "ai_scheduler/internal/config" + "context" + "fmt" + "testing" +) + +// Test_Call +func Test_Call(t *testing.T) { + req := &ProductUploadRequest{ + SupplierId: 261, + WarehouseId: 257, + IsDefaultWarehouse: 1, + Sort: 1, + Profit: 40, + TaxRate: 13, + GoodsList: []Goods{ + { + GoodsInfo: GoodsInfo{ + Title: "Apple iPhone 17 Pro Max 星宇橙色 256GB", + Brand: "Apple/苹果", + Category: "手机", + CostPrice: 9999.00, + GoodsAttributes: "CPU型号:A19 Pro;操作系统:iOS;机身存储:256GB;屏幕尺寸:6.86英寸;屏幕材质:OLED直屏;屏幕技术:视网膜XDR;后置摄像头:4800万像素三主摄系统(主摄4800万+超广角4800万+长焦4800万);前置摄像头:1800万像素;网络支持:5G双卡双待(移动/联通/电信);生物识别:人脸识别;防水等级:IP68;充电功率:40W;无线充电:支持;机身尺寸:163.4mm×78.0mm×8.75mm;机身重量:231g;机身颜色:星宇橙色;特征特质:轻薄、防水防尘、无线充电、NFC、磁吸无线充", + GoodsBarCode: "10181383848993", + GoodsIllustration: "Apple/苹果 iPhone 17 Pro Max 【需当面激活】支持移动联通电信 5G 双卡双待手机 星宇橙色 256GB 官方标配。搭载A19 Pro芯片,6.86英寸OLED视网膜XDR直屏,4800万像素三主摄系统,支持5G双卡双待,IP68防水防尘,40W有线充电,支持无线充电和磁吸充电。", + GoodsNum: "10181383848993", + Introduction: "Apple/苹果 iPhone 17 Pro Max 【需当面激活】支持移动联通电信 5G 双卡双待手机 星宇橙色 256GB 官方标配。搭载A19 Pro芯片,6.86英寸OLED视网膜XDR直屏,4800万像素三主摄系统,支持5G双卡双待,IP68防水防尘,40W有线充电,支持无线充电和磁吸充电。", + IsBind: 1, + SpuName: "Apple iPhone 17 Pro Max", + SpuNum: "jd_1766038130329_8721", + TaxRate: 13, + Unit: "台", + Weight: "0.231", // 单位:kg + Price: 9999.00, + SalesPrice: 9999.00, + Stock: 0, // JSON 中未提供库存信息 + Discount: 10, // JSON 中未提供折扣信息 + IsComposeGoods: 2, + IsHot: 2, + }, + GoodsMediaList: []GoodsMedia{ + { + Type: 1, + Url: "https://img10.360buyimg.com/pcpubliccms/s228x228_jfs/t1/363919/12/2409/45712/691d9970F84b99d32/9f9a5d5d16efeb79.jpg.avif", + }, + }, + }, + }, + } + toolResp, err := Call(context.Background(), config.ToolConfig{}, req) + + if err != nil { + t.Errorf("Call() error = %v", err) + } + + fmt.Printf("toolResp: %v\n", toolResp) +} diff --git a/internal/domain/tools/hyt/product_upload/types.go b/internal/domain/tools/hyt/product_upload/types.go new file mode 100644 index 0000000..947dbe5 --- /dev/null +++ b/internal/domain/tools/hyt/product_upload/types.go @@ -0,0 +1,54 @@ +package product_upload + +type ProductUploadRequest struct { + SupplierId int `json:"supplier_id"` // 供应商ID + WarehouseId int `json:"warehouse_id"` // 仓库ID + IsDefaultWarehouse int `json:"is_default_warehouse"` // 是否默认仓库 + Sort int `json:"sort"` // 排序 + Profit float64 `json:"profit"` // 利润 + TaxRate int `json:"tax_rate"` // 税率 + GoodsList []Goods `json:"goods_list"` // 商品列表 +} + +type Goods struct { + GoodsInfo GoodsInfo `json:"goods_info"` + GoodsMediaList []GoodsMedia `json:"goods_media_list"` +} + +type GoodsInfo struct { + Title string `json:"title"` // 商品名称 + Brand string `json:"brand"` // 品牌 + Category string `json:"category"` // 分类 + Discount int `json:"discount"` // 折扣 + GoodsAttributes string `json:"goods_attributes"` // 商品属性 + GoodsBarCode string `json:"goods_bar_code"` // 商品条码 + GoodsNum string `json:"goods_num"` // 商品编号 + Introduction string `json:"introduction"` // 商品介绍 + SpuName string `json:"spu_name"` // SPU名称 + SpuNum string `json:"spu_num"` // SPU编号 + Stock int `json:"stock"` // 库存 + TaxRate int `json:"tax_rate"` // 税率 + Unit string `json:"unit"` // 单位 + Weight string `json:"weight"` // 重量 + Price float64 `json:"price"` // 市场价 + SalesPrice float64 `json:"sales_price"` // 建议销售价格 + GoodsIllustration string `json:"goods_illustration"` // 商品插图 - 暂不提供 + Id int `json:"id"` // 商品ID - 无需 + CostPrice float64 `json:"cost_price"` // 成本价格 - 无需 + IsBind int `json:"is_bind"` // 是否绑定 - 默认0 + IsComposeGoods int32 `json:"is_compose_goods"` // 是否组合商品 - 默认2 + IsHot int `json:"is_hot"` // 是否热门商品 - 默认2 +} + +type GoodsMedia struct { + Remark string `json:"remark"` // 备注 + Sort int `json:"sort"` // 排序 + Type int `json:"type"` // 类型 + Url string `json:"url"` // URL +} + +type ProductUploadResponse struct { + PreviewUrl string `json:"preview_url"` // 预览URL + SpuNum string `json:"spu_code"` // SPU编码 + Id int `json:"id"` // 商品ID +} diff --git a/internal/domain/workflow/hyt/product_upload.go b/internal/domain/workflow/hyt/product_upload.go new file mode 100644 index 0000000..6ab98f1 --- /dev/null +++ b/internal/domain/workflow/hyt/product_upload.go @@ -0,0 +1,112 @@ +package hyt + +import ( + "ai_scheduler/internal/config" + "ai_scheduler/internal/data/constants" + toolHytPu "ai_scheduler/internal/domain/tools/hyt/product_upload" + "ai_scheduler/internal/domain/workflow/runtime" + "ai_scheduler/internal/entitys" + "context" + "encoding/json" + "fmt" + + eino_ollama "github.com/cloudwego/eino-ext/components/model/ollama" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" +) + +const WorkflowID = "hyt.productUpload" + +func init() { + runtime.Register(WorkflowID, func(d *runtime.Deps) (runtime.Workflow, error) { + return &productUpload{cfg: d.Conf}, nil + }) +} + +type productUpload struct { + cfg *config.Config + data *ProductUploadWorkflowInput +} + +type ProductUploadWorkflowInput struct { + Text string `mapstructure:"text"` +} + +func (o *productUpload) ID() string { return WorkflowID } + +func (o *productUpload) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) { + // 构建工作流 + chain, err := o.buildWorkflow(ctx) + if err != nil { + return nil, err + } + + o.data = &ProductUploadWorkflowInput{ + Text: rec.UserContent.Text, + } + // 工作流过程调用 + output, err := chain.Invoke(ctx, o.data) + if err != nil { + return nil, err + } + + fmt.Printf("workflow output: %v\n", output) + + // 不关心输出,全部在途中输出 + return output, nil +} + +func (o *productUpload) buildWorkflow(ctx context.Context) (compose.Runnable[*ProductUploadWorkflowInput, map[string]any], error) { + // 定义工作流 + c := compose.NewChain[*ProductUploadWorkflowInput, map[string]any]() + + // AI映射工具所需参数' + paramMappingModel, err := eino_ollama.NewChatModel(ctx, &eino_ollama.ChatModelConfig{ + BaseURL: o.cfg.Ollama.BaseURL, + Timeout: o.cfg.Ollama.Timeout, + Model: o.cfg.Ollama.Model, + Thinking: &eino_ollama.ThinkValue{Value: true}, + Options: &eino_ollama.Options{Temperature: 0.5}, + }) + if err != nil { + return nil, err + } + + // 1. 构建参LLM数映射提示词 + c.AppendChatTemplate(prompt.FromMessages( + schema.FString, + schema.SystemMessage("你是一个专业的商品参数解析器,你需要根据用户输入的商品描述,解析出商品的目标参数。"), + schema.SystemMessage("目标参数:"+constants.HYTProductPropertyTemplate), + schema.UserMessage("用户输入:{{.Text}}"), + )) + // 2. 调用LLM + c.AppendChatModel(paramMappingModel) + + // 3.工具参数整理 + c.AppendLambda(compose.InvokableLambda(func(_ context.Context, in *schema.Message) (*toolHytPu.ProductUploadRequest, error) { + toolReq := &toolHytPu.ProductUploadRequest{} + if err := json.Unmarshal([]byte(in.Content), toolReq); err != nil { + return nil, err + } + return toolReq, nil + })) + + // 4.工具调用 + c.AppendLambda(compose.InvokableLambda(func(_ context.Context, in *toolHytPu.ProductUploadRequest) (*toolHytPu.ProductUploadResponse, error) { + toolRes, err := toolHytPu.Call(ctx, o.cfg.Tools.HytProductUpload, in) + return toolRes, err + })) + + // 5.结果数据映射 + c.AppendLambda(compose.InvokableLambda(func(_ context.Context, in *toolHytPu.ProductUploadResponse) (map[string]any, error) { + return map[string]any{ + "预览URL(货易通商品列表)": in.PreviewUrl, + "SPU编码": in.SpuNum, + "商品ID": in.Id, + }, nil + })) + + // 6.编译工作流 + return c.Compile(ctx) +} diff --git a/internal/domain/workflow/runtime/registry.go b/internal/domain/workflow/runtime/registry.go index c854053..bf840e6 100644 --- a/internal/domain/workflow/runtime/registry.go +++ b/internal/domain/workflow/runtime/registry.go @@ -11,7 +11,7 @@ import ( type Workflow interface { ID() string - Schema() map[string]any + // Schema() map[string]any Invoke(ctx context.Context, requireData *entitys.Recognize) (map[string]any, error) } diff --git a/internal/domain/workflow/zltx/order_after_reseller_batch.go b/internal/domain/workflow/zltx/order_after_reseller_batch.go index 9749bf7..5e5fa51 100644 --- a/internal/domain/workflow/zltx/order_after_reseller_batch.go +++ b/internal/domain/workflow/zltx/order_after_reseller_batch.go @@ -79,13 +79,13 @@ type OrderAfterSaleResellerBatchData struct { func (o *orderAfterSaleResellerBatch) ID() string { return "zltx.orderAfterSaleResellerBatch" } // Schema 返回入参约束(用于校验/表单生成) -func (o *orderAfterSaleResellerBatch) Schema() map[string]any { - return map[string]any{ - "type": "object", - "properties": map[string]any{"orderNumber": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}}, - "required": []string{"orderNumber"}, - } -} +// func (o *orderAfterSaleResellerBatch) Schema() map[string]any { +// return map[string]any{ +// "type": "object", +// "properties": map[string]any{"orderNumber": map[string]any{"type": "array", "items": map[string]any{"type": "string"}}}, +// "required": []string{"orderNumber"}, +// } +// } // Invoke 调用原有编排工作流并规范化输出 func (o *orderAfterSaleResellerBatch) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) { diff --git a/internal/pkg/util/map.go b/internal/pkg/util/map.go new file mode 100644 index 0000000..5ca80c8 --- /dev/null +++ b/internal/pkg/util/map.go @@ -0,0 +1,14 @@ +package util + +import "encoding/json" + +// StructToMap 将结构体转换为 map[string]any +func StructToMap(v any) (map[string]any, error) { + b, err := json.Marshal(v) + if err != nil { + return nil, err + } + var m map[string]any + err = json.Unmarshal(b, &m) + return m, err +} diff --git a/internal/server/router/router.go b/internal/server/router/router.go index be861aa..3359984 100644 --- a/internal/server/router/router.go +++ b/internal/server/router/router.go @@ -34,6 +34,11 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi c.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + // AI能力调用路由,设置不同的 CORS 头 + if strings.HasPrefix(c.Path(), "/api/v1/capability") { + c.Set("Access-Control-Allow-Headers", "Content-Type, X-Source-Key, X-Timestamp") + } + // 如果是预检请求(OPTIONS),直接返回 204 if c.Method() == "OPTIONS" { return c.SendStatus(fiber.StatusNoContent) // 204 @@ -88,7 +93,8 @@ func SetupRoutes(app *fiber.App, ChatService *services.ChatService, sessionServi r.Post("/chat/history/update/content", chatHist.UpdateContent) // 能力 - r.Post("/capability/product/ingest", capabilityService.ProductIngest) // 商品数据提取 + r.Post("/capability/product/ingest", capabilityService.ProductIngest) // 商品数据提取 + r.Post("/capability/product/upload/hyt", capabilityService.ProductUploadHyt) // 货易通商品数据上传 } func routerSocket(app *fiber.App, chatService *services.ChatService) { diff --git a/internal/services/capability.go b/internal/services/capability.go index aac842d..d0eb1fe 100644 --- a/internal/services/capability.go +++ b/internal/services/capability.go @@ -2,7 +2,10 @@ package services import ( "ai_scheduler/internal/config" + "ai_scheduler/internal/data/constants" errorcode "ai_scheduler/internal/data/error" + "ai_scheduler/internal/domain/workflow/runtime" + "ai_scheduler/internal/entitys" "ai_scheduler/internal/pkg/util" "ai_scheduler/internal/pkg/utils_ollama" "context" @@ -16,12 +19,14 @@ import ( // CapabilityService 统一回调入口 type CapabilityService struct { - cfg *config.Config + cfg *config.Config + workflowManager *runtime.Registry } -func NewCapabilityService(cfg *config.Config) *CapabilityService { +func NewCapabilityService(cfg *config.Config, workflowManager *runtime.Registry) *CapabilityService { return &CapabilityService{ - cfg: cfg, + cfg: cfg, + workflowManager: workflowManager, } } @@ -34,56 +39,11 @@ type ProductIngestReq struct { Timestamp int64 `json:"timestamp"` // 商品发布时间戳 } -const ( - // 货易通商品属性模板-中文 - HYTProductPropertyTemplateZH = `{ - "条码": "string", // 商品编号 - "分类名称": "string", // 商品分类 - "货品名称": "string", // 商品名称 - "货品编号": "string", // 商品编号 - "商品货号": "string", // 商品编号 - "品牌": "string", // 商品品牌 - "单位": "string", // 商品单位,若无则使用'个' - "规格参数": "string", // 商品规格参数 - "货品说明": "string", // 商品说明 - "保质期": "string", // 商品保质期 - "保质期单位": "string", // 商品保质期单位 - "链接": "string", // - "货品图片": ["string"], // 商品多图,取1-2个即可 - "电商销售价格": "decimal(10,2)", // 商品电商销售价格 - "销售价": "decimal(10,2)", // 商品销售价格 - "供应商报价": "decimal(10,2)", // 商品供应商报价 - "税率": "number%", // 商品税率 x% - "默认供应商": "", // 空即可 - "默认存放仓库": "", // 空即可 - "备注": "", // 备注 - "长": "string", // 商品长度,decimal(10,2)+单位 - "宽": "string", // 商品宽度,decimal(10,2)+单位 - "高": "string", // 商品高度,decimal(10,2)+单位 - "重量": "string", // 商品重量(kg) - "SPU名称": "string", // 商品SPU名称 - "SPU编码": "string" // 编码串,jd_{timestamp}_rand(1000-999) - }` - - SystemPrompt = `你是一个专业的商品属性提取助手,你的任务是根据用户输入提取商品的属性信息。 - 目标属性模板:%s。 - 最终输出格式为纯JSON字符串,键值对对应目标属性和提取到的属性值。 - 最终输出不要携带markdown标识,不要携带回车换行` -) - // ProductIngest 产品数据提取 func (s *CapabilityService) ProductIngest(c *fiber.Ctx) error { - // 读取头 - token := strings.TrimSpace(c.Get("X-Source-Key")) - ts := strings.TrimSpace(c.Get("X-Timestamp")) - - // 时间窗口校验 - if ts != "" && !util.IsInTimeWindow(ts, 5*time.Minute) { - return errorcode.AuthNotFound - } - // token校验 - if token == "" || token != "A7f9KQ3mP2X8LZC4R5e" { - return errorcode.KeyErr() + // 请求头校验 + if err := s.checkRequestHeader(c); err != nil { + return err } // 解析请求参数 @@ -91,7 +51,6 @@ func (s *CapabilityService) ProductIngest(c *fiber.Ctx) error { if err := c.BodyParser(&req); err != nil { return errorcode.ParamErr("invalid request body: %v", err) } - // 必要参数校验 if req.Text == "" { return errorcode.ParamErr("missing required fields") @@ -107,7 +66,7 @@ func (s *CapabilityService) ProductIngest(c *fiber.Ctx) error { res, err := client.Chat(context.Background(), []api.Message{ { Role: "system", - Content: fmt.Sprintf(SystemPrompt, HYTProductPropertyTemplateZH), + Content: fmt.Sprintf(constants.SystemPrompt, constants.HYTProductPropertyTemplateZH), }, { Role: "user", @@ -122,8 +81,50 @@ func (s *CapabilityService) ProductIngest(c *fiber.Ctx) error { return err } + // res.Message.Content Go中map会无序,交给前端解析 + // 解析模型输出 c.JSON(res.Message.Content) return nil } + +// checkRequestHeader 校验请求头 +func (s *CapabilityService) checkRequestHeader(c *fiber.Ctx) error { + // 读取头 + token := strings.TrimSpace(c.Get("X-Source-Key")) + ts := strings.TrimSpace(c.Get("X-Timestamp")) + + // 时间窗口校验 + if ts != "" && !util.IsInTimeWindow(ts, 5*time.Minute) { + return errorcode.AuthNotFound + } + // token校验 + if token == "" || token != "A7f9KQ3mP2X8LZC4R5e" { + return errorcode.KeyErr() + } + + return nil +} + +// ProductUploadHyt 商品上传至货易通 +func (s *CapabilityService) ProductUploadHyt(c *fiber.Ctx) error { + // 请求头校验 + if err := s.checkRequestHeader(c); err != nil { + return err + } + + // 获取 body json 串 + raw := append([]byte(nil), c.BodyRaw()...) + bodyStr := string(raw) + + // 调用eino工作流,实现商品上传到货易通 + workflowId := "hyt.productUpload" + rec := &entitys.Recognize{UserContent: &entitys.RecognizeUserContent{Text: bodyStr}} + res, err := s.workflowManager.Invoke(context.Background(), workflowId, rec) + if err != nil { + return err + } + + return c.JSON(res) +}