feat: 1.增加 eino tool 相关配置,货易通商品上传参数配置化 2. eino tool 注册方法调整
This commit is contained in:
parent
d0ba329024
commit
d8df571cce
|
|
@ -74,11 +74,19 @@ tools:
|
||||||
zltxOrderAfterSaleResellerBatch:
|
zltxOrderAfterSaleResellerBatch:
|
||||||
enabled: true
|
enabled: true
|
||||||
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai"
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai"
|
||||||
|
|
||||||
|
# eino tool 配置
|
||||||
|
eino_tools:
|
||||||
|
# 货易通商品上传
|
||||||
hytProductUpload:
|
hytProductUpload:
|
||||||
enabled: true
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete"
|
||||||
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/oursProduct"
|
add_url: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage"
|
||||||
|
# 货易通供应商查询
|
||||||
|
hytSupplierSearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/supplier/list"
|
||||||
|
# 货易通仓库查询
|
||||||
|
hytWarehouseSearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/warehouse/list"
|
||||||
|
|
||||||
default_prompt:
|
default_prompt:
|
||||||
img_recognize:
|
img_recognize:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type Config struct {
|
||||||
Coze CozeConfig `mapstructure:"coze"`
|
Coze CozeConfig `mapstructure:"coze"`
|
||||||
Sys SysConfig `mapstructure:"sys"`
|
Sys SysConfig `mapstructure:"sys"`
|
||||||
Tools ToolsConfig `mapstructure:"tools"`
|
Tools ToolsConfig `mapstructure:"tools"`
|
||||||
|
EinoTools EinoToolsConfig `mapstructure:"eino_tools"`
|
||||||
Logging LoggingConfig `mapstructure:"logging"`
|
Logging LoggingConfig `mapstructure:"logging"`
|
||||||
Redis Redis `mapstructure:"redis"`
|
Redis Redis `mapstructure:"redis"`
|
||||||
DB DB `mapstructure:"db"`
|
DB DB `mapstructure:"db"`
|
||||||
|
|
@ -141,8 +142,6 @@ type ToolsConfig struct {
|
||||||
CozeExpress ToolConfig `mapstructure:"cozeExpress"`
|
CozeExpress ToolConfig `mapstructure:"cozeExpress"`
|
||||||
// Coze 公司查询工具
|
// Coze 公司查询工具
|
||||||
CozeCompany ToolConfig `mapstructure:"cozeCompany"`
|
CozeCompany ToolConfig `mapstructure:"cozeCompany"`
|
||||||
// 货易通商品上传
|
|
||||||
HytProductUpload ToolConfig `mapstructure:"hytProductUpload"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToolConfig 单个工具配置
|
// ToolConfig 单个工具配置
|
||||||
|
|
@ -155,6 +154,16 @@ type ToolConfig struct {
|
||||||
AddURL string `mapstructure:"add_url"`
|
AddURL string `mapstructure:"add_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EinoToolsConfig eino tool 配置
|
||||||
|
type EinoToolsConfig struct {
|
||||||
|
// 货易通商品上传
|
||||||
|
HytProductUpload ToolConfig `mapstructure:"hytProductUpload"`
|
||||||
|
// 货易通供应商查询
|
||||||
|
HytSupplierSearch ToolConfig `mapstructure:"hytSupplierSearch"`
|
||||||
|
// 货易通仓库查询
|
||||||
|
HytWarehouseSearch ToolConfig `mapstructure:"hytWarehouseSearch"`
|
||||||
|
}
|
||||||
|
|
||||||
// LoggingConfig 日志配置
|
// LoggingConfig 日志配置
|
||||||
type LoggingConfig struct {
|
type LoggingConfig struct {
|
||||||
Level string `mapstructure:"level"`
|
Level string `mapstructure:"level"`
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,6 @@ const (
|
||||||
"货品图片": ["string"], // 商品多图,取1-2个即可
|
"货品图片": ["string"], // 商品多图,取1-2个即可
|
||||||
"电商销售价格": "string", // 商品电商销售价格 decimal(10,2)
|
"电商销售价格": "string", // 商品电商销售价格 decimal(10,2)
|
||||||
"销售价": "string", // 商品销售价格 decimal(10,2)
|
"销售价": "string", // 商品销售价格 decimal(10,2)
|
||||||
"供应商报价": "string", // 商品供应商报价 decimal(10,2)
|
|
||||||
"税率": "string", // 商品税率 x%
|
|
||||||
"默认供应商": "string", // 供应商名称
|
|
||||||
"默认存放仓库": "string", // 仓库名称
|
|
||||||
"利润": "string", // 商品利润 decimal(10,2)
|
|
||||||
"备注": "string", // 备注
|
"备注": "string", // 备注
|
||||||
"长": "string", // 商品长度,decimal(10,2)+单位
|
"长": "string", // 商品长度,decimal(10,2)+单位
|
||||||
"宽": "string", // 商品宽度,decimal(10,2)+单位
|
"宽": "string", // 商品宽度,decimal(10,2)+单位
|
||||||
|
|
@ -44,135 +39,14 @@ const (
|
||||||
"重量": "string", // 商品重量(kg)
|
"重量": "string", // 商品重量(kg)
|
||||||
"SPU名称": "string", // 商品SPU名称
|
"SPU名称": "string", // 商品SPU名称
|
||||||
"SPU编码": "string" // 编码串,jd_{timestamp}_rand(1000-999)
|
"SPU编码": "string" // 编码串,jd_{timestamp}_rand(1000-999)
|
||||||
|
"供应商报价": "string", // 商品供应商报价 decimal(10,2)
|
||||||
|
"税率": "string", // 商品税率 x%
|
||||||
|
"利润": "string", // 商品利润 decimal(10,2)
|
||||||
|
"默认供应商": "string", // 供应商名称
|
||||||
|
"默认存放仓库": "string", // 仓库名称
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
// 商品属性模板
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 缓存key
|
// 缓存key
|
||||||
const (
|
const (
|
||||||
CapabilityProductIngestCacheKey = "ai_scheduler:capability:product_ingest:%s"
|
CapabilityProductIngestCacheKey = "ai_scheduler:capability:product_ingest:%s"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,17 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Call(ctx context.Context, cfg config.ToolConfig, toolReq *ProductUploadRequest) (toolResp *ProductUploadResponse, err error) {
|
type Client struct {
|
||||||
|
cfg config.ToolConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.ToolConfig) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(ctx context.Context, toolReq *ProductUploadRequest) (toolResp *ProductUploadResponse, err error) {
|
||||||
// 商品有且只能有一个
|
// 商品有且只能有一个
|
||||||
if len(toolReq.GoodsList) != 1 {
|
if len(toolReq.GoodsList) != 1 {
|
||||||
err = errors.New("商品只能有一个")
|
err = errors.New("商品只能有一个")
|
||||||
|
|
@ -20,7 +30,7 @@ func Call(ctx context.Context, cfg config.ToolConfig, toolReq *ProductUploadRequ
|
||||||
|
|
||||||
req := l_request.Request{
|
req := l_request.Request{
|
||||||
Method: "Post",
|
Method: "Post",
|
||||||
Url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete",
|
Url: c.cfg.BaseURL,
|
||||||
Json: apiReq,
|
Json: apiReq,
|
||||||
}
|
}
|
||||||
res, err := req.Send()
|
res, err := req.Send()
|
||||||
|
|
@ -51,7 +61,7 @@ func Call(ctx context.Context, cfg config.ToolConfig, toolReq *ProductUploadRequ
|
||||||
}
|
}
|
||||||
|
|
||||||
toolResp = &ProductUploadResponse{
|
toolResp = &ProductUploadResponse{
|
||||||
PreviewUrl: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage",
|
PreviewUrl: c.cfg.AddURL,
|
||||||
SpuNum: toolReq.GoodsList[0].GoodsInfo.SpuNum,
|
SpuNum: toolReq.GoodsList[0].GoodsInfo.SpuNum,
|
||||||
Id: resMap.Data.Ids[0],
|
Id: resMap.Data.Ids[0],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ func Test_Call(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
toolResp, err := Call(context.Background(), config.ToolConfig{}, req)
|
client := New(config.ToolConfig{})
|
||||||
|
toolResp, err := client.Call(context.Background(), req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Call() error = %v", err)
|
t.Errorf("Call() error = %v", err)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,27 @@
|
||||||
package supplier_search
|
package supplier_search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/pkg/l_request"
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Call(ctx context.Context, name string) (int, error) {
|
type Client struct {
|
||||||
|
cfg config.ToolConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.ToolConfig) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return 0, errors.New("supplier name is empty")
|
// 如果没有供应商名,返回0,不报错,由上层业务决定是否允许
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
reqBody := SearchRequest{
|
reqBody := SearchRequest{
|
||||||
|
|
@ -27,7 +38,7 @@ func Call(ctx context.Context, name string) (int, error) {
|
||||||
|
|
||||||
req := l_request.Request{
|
req := l_request.Request{
|
||||||
Method: "Post",
|
Method: "Post",
|
||||||
Url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/supplier/list",
|
Url: c.cfg.BaseURL,
|
||||||
Json: apiReq,
|
Json: apiReq,
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,24 @@
|
||||||
package warehouse_search
|
package warehouse_search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/pkg/l_request"
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Call(ctx context.Context, name string) (int, error) {
|
type Client struct {
|
||||||
|
cfg config.ToolConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.ToolConfig) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(ctx context.Context, name string) (int, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
// 如果没有仓库名,返回0,不报错,由上层业务决定是否允许
|
// 如果没有仓库名,返回0,不报错,由上层业务决定是否允许
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
|
@ -22,7 +33,7 @@ func Call(ctx context.Context, name string) (int, error) {
|
||||||
|
|
||||||
req := l_request.Request{
|
req := l_request.Request{
|
||||||
Method: "Get",
|
Method: "Get",
|
||||||
Url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/warehouse/list",
|
Url: c.cfg.BaseURL,
|
||||||
Params: params,
|
Params: params,
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
type Tool interface{
|
import (
|
||||||
Name() string
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/tools/hyt/product_upload"
|
||||||
|
"ai_scheduler/internal/domain/tools/hyt/supplier_search"
|
||||||
|
"ai_scheduler/internal/domain/tools/hyt/warehouse_search"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
Hyt *HytTools
|
||||||
|
// Zltx *ZltxTools
|
||||||
}
|
}
|
||||||
|
|
||||||
var registry = map[string]Tool{}
|
type HytTools struct {
|
||||||
|
ProductUpload *product_upload.Client
|
||||||
func Register(t Tool){
|
SupplierSearch *supplier_search.Client
|
||||||
registry[t.Name()] = t
|
WarehouseSearch *warehouse_search.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(name string) Tool{
|
func NewManager(cfg *config.Config) *Manager {
|
||||||
return registry[name]
|
return &Manager{
|
||||||
|
Hyt: &HytTools{
|
||||||
|
ProductUpload: product_upload.New(cfg.EinoTools.HytProductUpload),
|
||||||
|
SupplierSearch: supplier_search.New(cfg.EinoTools.HytSupplierSearch),
|
||||||
|
WarehouseSearch: warehouse_search.New(cfg.EinoTools.HytWarehouseSearch),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,33 @@ package hyt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/data/constants"
|
toolManager "ai_scheduler/internal/domain/tools"
|
||||||
toolPu "ai_scheduler/internal/domain/tools/hyt/product_upload"
|
toolPu "ai_scheduler/internal/domain/tools/hyt/product_upload"
|
||||||
toolSs "ai_scheduler/internal/domain/tools/hyt/supplier_search"
|
|
||||||
toolWs "ai_scheduler/internal/domain/tools/hyt/warehouse_search"
|
|
||||||
"ai_scheduler/internal/domain/workflow/runtime"
|
"ai_scheduler/internal/domain/workflow/runtime"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
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/compose"
|
||||||
"github.com/cloudwego/eino/schema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const WorkflowID = "hyt.productUpload"
|
const WorkflowID = "hyt.productUpload"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runtime.Register(WorkflowID, func(d *runtime.Deps) (runtime.Workflow, error) {
|
runtime.Register(WorkflowID, func(d *runtime.Deps) (runtime.Workflow, error) {
|
||||||
return &productUpload{cfg: d.Conf}, nil
|
return &productUpload{cfg: d.Conf, toolManager: d.ToolManager}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type productUpload struct {
|
type productUpload struct {
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
data *ProductUploadWorkflowInput
|
toolManager *toolManager.Manager
|
||||||
|
data *ProductUploadWorkflowInput
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductUploadWorkflowInput struct {
|
type ProductUploadWorkflowInput struct {
|
||||||
|
|
@ -41,8 +38,8 @@ type ProductUploadWorkflowInput struct {
|
||||||
func (o *productUpload) ID() string { return WorkflowID }
|
func (o *productUpload) ID() string { return WorkflowID }
|
||||||
|
|
||||||
func (o *productUpload) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) {
|
func (o *productUpload) Invoke(ctx context.Context, rec *entitys.Recognize) (map[string]any, error) {
|
||||||
// 构建工作流 (使用 V2 Graph 版本)
|
// 构建工作流
|
||||||
runnable, err := o.buildWorkflowV2(ctx)
|
runnable, err := o.buildWorkflow(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -103,65 +100,11 @@ type ProductUploadContext struct {
|
||||||
UploadResp *toolPu.ProductUploadResponse
|
UploadResp *toolPu.ProductUploadResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildWorkflow 构建基于 Graph 的并行工作流
|
||||||
func (o *productUpload) buildWorkflow(ctx context.Context) (compose.Runnable[*ProductUploadWorkflowInput, map[string]any], error) {
|
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) (*toolPu.ProductUploadRequest, error) {
|
|
||||||
toolReq := &toolPu.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 *toolPu.ProductUploadRequest) (*toolPu.ProductUploadResponse, error) {
|
|
||||||
toolRes, err := toolPu.Call(ctx, o.cfg.Tools.HytProductUpload, in)
|
|
||||||
return toolRes, err
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 5.结果数据映射
|
|
||||||
c.AppendLambda(compose.InvokableLambda(func(_ context.Context, in *toolPu.ProductUploadResponse) (map[string]any, error) {
|
|
||||||
return map[string]any{
|
|
||||||
"预览URL(货易通商品列表)": in.PreviewUrl,
|
|
||||||
"SPU编码": in.SpuNum,
|
|
||||||
"商品ID": in.Id,
|
|
||||||
}, nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 6.编译工作流
|
|
||||||
return c.Compile(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildWorkflowV2 构建基于 Graph 的并行工作流
|
|
||||||
func (o *productUpload) buildWorkflowV2(ctx context.Context) (compose.Runnable[*ProductUploadWorkflowInput, map[string]any], error) {
|
|
||||||
g := compose.NewGraph[*ProductUploadWorkflowInput, map[string]any]()
|
g := compose.NewGraph[*ProductUploadWorkflowInput, map[string]any]()
|
||||||
|
|
||||||
// 1. DataMapping 节点: 解析 JSON -> 填充基础 Request -> 提取供应商/仓库名
|
// 1. DataMapping 节点: 解析 JSON -> 填充基础 Request
|
||||||
g.AddLambdaNode("data_mapping", compose.InvokableLambda(func(ctx context.Context, in *ProductUploadWorkflowInput) (*ProductUploadContext, error) {
|
g.AddLambdaNode("data_mapping", compose.InvokableLambda(func(ctx context.Context, in *ProductUploadWorkflowInput) (*ProductUploadContext, error) {
|
||||||
state := &ProductUploadContext{
|
state := &ProductUploadContext{
|
||||||
mu: &sync.Mutex{}, // 初始化锁
|
mu: &sync.Mutex{}, // 初始化锁
|
||||||
|
|
@ -176,6 +119,21 @@ func (o *productUpload) buildWorkflowV2(ctx context.Context) (compose.Runnable[*
|
||||||
if err := json.Unmarshal([]byte(in.Text), &ingestData); err != nil {
|
if err := json.Unmarshal([]byte(in.Text), &ingestData); err != nil {
|
||||||
return nil, fmt.Errorf("解析商品数据失败: %w", err)
|
return nil, fmt.Errorf("解析商品数据失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 必填校验
|
||||||
|
if ingestData.SupplierName == "" {
|
||||||
|
return nil, errors.New("供应商名称不能为空")
|
||||||
|
}
|
||||||
|
if ingestData.WarehouseName == "" {
|
||||||
|
return nil, errors.New("仓库名称不能为空")
|
||||||
|
}
|
||||||
|
if ingestData.Profit == "" {
|
||||||
|
return nil, errors.New("利润不能为空")
|
||||||
|
}
|
||||||
|
if ingestData.TaxRate == "" {
|
||||||
|
return nil, errors.New("税率不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
state.IngestData = &ingestData
|
state.IngestData = &ingestData
|
||||||
state.SupplierName = ingestData.SupplierName
|
state.SupplierName = ingestData.SupplierName
|
||||||
state.WarehouseName = ingestData.WarehouseName
|
state.WarehouseName = ingestData.WarehouseName
|
||||||
|
|
@ -240,32 +198,38 @@ func (o *productUpload) buildWorkflowV2(ctx context.Context) (compose.Runnable[*
|
||||||
|
|
||||||
// 2. 获取供应商ID 节点
|
// 2. 获取供应商ID 节点
|
||||||
g.AddLambdaNode("get_supplier_id", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
g.AddLambdaNode("get_supplier_id", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
||||||
if state.SupplierName != "" {
|
if state.SupplierName == "" {
|
||||||
supplierId, err := toolSs.Call(ctx, state.SupplierName)
|
return state, errors.New("供应商名称不能为空")
|
||||||
if err != nil {
|
|
||||||
// 记录日志,但不阻断流程,可能允许 ID 为 0
|
|
||||||
fmt.Printf("warning: failed to get supplier id for %s: %v\n", state.SupplierName, err)
|
|
||||||
} else {
|
|
||||||
state.mu.Lock()
|
|
||||||
defer state.mu.Unlock()
|
|
||||||
state.UploadReq.SupplierId = supplierId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supplierId, err := o.toolManager.Hyt.SupplierSearch.Call(ctx, state.SupplierName)
|
||||||
|
if err != nil {
|
||||||
|
// 记录日志,但不阻断流程,可能允许 ID 为 0
|
||||||
|
fmt.Printf("warning: failed to get supplier id for %s: %v\n", state.SupplierName, err)
|
||||||
|
} else {
|
||||||
|
state.mu.Lock()
|
||||||
|
defer state.mu.Unlock()
|
||||||
|
state.UploadReq.SupplierId = supplierId
|
||||||
|
}
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 3. 获取仓库ID 节点
|
// 3. 获取仓库ID 节点
|
||||||
g.AddLambdaNode("get_warehouse_id", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
g.AddLambdaNode("get_warehouse_id", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
||||||
if state.WarehouseName != "" {
|
if state.WarehouseName == "" {
|
||||||
warehouseId, err := toolWs.Call(ctx, state.WarehouseName)
|
return state, errors.New("仓库名称不能为空")
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("warning: failed to get warehouse id for %s: %v\n", state.WarehouseName, err)
|
|
||||||
} else {
|
|
||||||
state.mu.Lock()
|
|
||||||
defer state.mu.Unlock()
|
|
||||||
state.UploadReq.WarehouseId = warehouseId
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warehouseId, err := o.toolManager.Hyt.WarehouseSearch.Call(ctx, state.WarehouseName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("warning: failed to get warehouse id for %s: %v\n", state.WarehouseName, err)
|
||||||
|
} else {
|
||||||
|
state.mu.Lock()
|
||||||
|
defer state.mu.Unlock()
|
||||||
|
state.UploadReq.WarehouseId = warehouseId
|
||||||
|
}
|
||||||
|
|
||||||
return state, nil
|
return state, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -280,7 +244,7 @@ func (o *productUpload) buildWorkflowV2(ctx context.Context) (compose.Runnable[*
|
||||||
|
|
||||||
// 5. 上传节点
|
// 5. 上传节点
|
||||||
g.AddLambdaNode("upload_product", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
g.AddLambdaNode("upload_product", compose.InvokableLambda(func(ctx context.Context, state *ProductUploadContext) (*ProductUploadContext, error) {
|
||||||
toolRes, err := toolPu.Call(ctx, o.cfg.Tools.HytProductUpload, state.UploadReq)
|
toolRes, err := o.toolManager.Hyt.ProductUpload.Call(ctx, state.UploadReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"ai_scheduler/internal/domain/workflow/runtime"
|
"ai_scheduler/internal/domain/workflow/runtime"
|
||||||
"ai_scheduler/internal/pkg/utils_ollama"
|
"ai_scheduler/internal/pkg/utils_ollama"
|
||||||
|
|
||||||
|
toolManager "ai_scheduler/internal/domain/tools"
|
||||||
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -13,7 +15,7 @@ var ProviderSetWorkflow = wire.NewSet(NewRegistry)
|
||||||
// NewRegistry 注入共享依赖并注册默认 Registry,确保自注册工作流可被发现
|
// NewRegistry 注入共享依赖并注册默认 Registry,确保自注册工作流可被发现
|
||||||
func NewRegistry(conf *config.Config, llm *utils_ollama.Client) *runtime.Registry {
|
func NewRegistry(conf *config.Config, llm *utils_ollama.Client) *runtime.Registry {
|
||||||
// 步骤1:设置运行时依赖(配置与LLM客户端),供工作流工厂在首次实例化时使用;必须在任何调用 Invoke 之前完成,否则会触发 "deps not set"
|
// 步骤1:设置运行时依赖(配置与LLM客户端),供工作流工厂在首次实例化时使用;必须在任何调用 Invoke 之前完成,否则会触发 "deps not set"
|
||||||
runtime.SetDeps(&runtime.Deps{Conf: conf, LLM: llm})
|
runtime.SetDeps(&runtime.Deps{Conf: conf, LLM: llm, ToolManager: toolManager.NewManager(conf)})
|
||||||
// 步骤2:创建新的工作流注册表;注册表负责按工作流ID惰性实例化并缓存单例实例,保障并发访问下的安全
|
// 步骤2:创建新的工作流注册表;注册表负责按工作流ID惰性实例化并缓存单例实例,保障并发访问下的安全
|
||||||
r := runtime.NewRegistry()
|
r := runtime.NewRegistry()
|
||||||
// 步骤3:将该注册表设置为全局默认,便于通过 runtime.Default() 获取;自注册的工作流可通过默认注册表被发现并调用
|
// 步骤3:将该注册表设置为全局默认,便于通过 runtime.Default() 获取;自注册的工作流可通过默认注册表被发现并调用
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ package workflow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
|
toolManager "ai_scheduler/internal/domain/tools"
|
||||||
"ai_scheduler/internal/pkg/utils_ollama"
|
"ai_scheduler/internal/pkg/utils_ollama"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 仅声明依赖结构,避免在 workflow 包内实现注册中心逻辑导致循环依赖
|
// 仅声明依赖结构,避免在 workflow 包内实现注册中心逻辑导致循环依赖
|
||||||
type Deps struct {
|
type Deps struct {
|
||||||
Conf *config.Config
|
Conf *config.Config
|
||||||
LLM *utils_ollama.Client
|
LLM *utils_ollama.Client
|
||||||
|
ToolManager *toolManager.Manager
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
|
toolManager "ai_scheduler/internal/domain/tools"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"ai_scheduler/internal/pkg/utils_ollama"
|
"ai_scheduler/internal/pkg/utils_ollama"
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -16,8 +17,9 @@ type Workflow interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Deps struct {
|
type Deps struct {
|
||||||
Conf *config.Config
|
Conf *config.Config
|
||||||
LLM *utils_ollama.Client
|
LLM *utils_ollama.Client
|
||||||
|
ToolManager *toolManager.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
type Factory func(deps *Deps) (Workflow, error)
|
type Factory func(deps *Deps) (Workflow, error)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue