parent
8414a57661
commit
5b789e557a
|
|
@ -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"
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ type ToolsConfig struct {
|
|||
CozeExpress ToolConfig `mapstructure:"cozeExpress"`
|
||||
// Coze 公司查询工具
|
||||
CozeCompany ToolConfig `mapstructure:"cozeCompany"`
|
||||
// 货易通商品上传
|
||||
HytProductUpload ToolConfig `mapstructure:"hytProductUpload"`
|
||||
}
|
||||
|
||||
// ToolConfig 单个工具配置
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue