Merge branch 'v3'
This commit is contained in:
commit
a6fec7c0dc
32
Dockerfile
32
Dockerfile
|
|
@ -1,3 +1,28 @@
|
||||||
|
## 使用官方Go镜像作为构建环境
|
||||||
|
FROM golang:1.24.7-alpine AS builder
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 使用国内镜像源加速APK包下载
|
||||||
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||||
|
|
||||||
|
# 使用国内镜像源加速依赖下载
|
||||||
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
|
|
||||||
|
# 复制项目源码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 复制go模块依赖文件
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod tidy
|
||||||
|
|
||||||
|
RUN go install github.com/google/wire/cmd/wire@latest
|
||||||
|
RUN wire ./cmd/server
|
||||||
|
|
||||||
|
# 编译Go应用程序,生成静态链接的二进制文件
|
||||||
|
RUN go build -ldflags="-s -w" -o server ./cmd/server
|
||||||
|
|
||||||
# 创建最终镜像,用于运行编译后的Go程序
|
# 创建最终镜像,用于运行编译后的Go程序
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|
||||||
|
|
@ -12,10 +37,13 @@ RUN echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/main' > /etc/apk/repositories \
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 将编译好的二进制文件从构建阶段复制到运行阶段
|
# 将编译好的二进制文件从构建阶段复制到运行阶段
|
||||||
COPY ./ /app
|
COPY --from=builder /app/server ./server
|
||||||
|
# 复制配置文件夹
|
||||||
|
COPY --from=builder /app/config ./config
|
||||||
|
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
# 设置容器启动时运行的命令
|
# 设置容器启动时运行的命令
|
||||||
|
|
||||||
|
|
||||||
CMD ["./bin/server"]
|
CMD ["./server"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
[https://p6-img.searchpstatp.com/tos-cn-i-vvloioitz3/6e5e76d274df2efabde9194a06f97e89~tplv-vvloioitz3-6:190:124.jpeg]
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file")
|
configPath := flag.String("config", "./config/config_test.yaml", "Path to configuration file")
|
||||||
|
onBot := flag.String("bot", "", "bot start")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
bc, err := config.LoadConfig(*configPath)
|
bc, err := config.LoadConfig(*configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -23,8 +24,8 @@ func main() {
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
app.DingBotServer.Run(context.Background(), *onBot)
|
||||||
|
|
||||||
log.Fatal(app.HttpServer.Listen(fmt.Sprintf(":%d", bc.Server.Port)))
|
log.Fatal(app.HttpServer.Listen(fmt.Sprintf(":%d", bc.Server.Port)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,19 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/biz"
|
"ai_scheduler/internal/biz"
|
||||||
|
"ai_scheduler/internal/biz/handle/dingtalk"
|
||||||
|
"ai_scheduler/internal/biz/tools_regis"
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/data/impl"
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/domain/component"
|
||||||
|
"ai_scheduler/internal/domain/repo"
|
||||||
|
"ai_scheduler/internal/domain/workflow"
|
||||||
"ai_scheduler/internal/pkg"
|
"ai_scheduler/internal/pkg"
|
||||||
"ai_scheduler/internal/server"
|
"ai_scheduler/internal/server"
|
||||||
"ai_scheduler/internal/services"
|
"ai_scheduler/internal/services"
|
||||||
|
|
||||||
|
// "ai_scheduler/internal/tool_callback"
|
||||||
"ai_scheduler/internal/tools"
|
"ai_scheduler/internal/tools"
|
||||||
"ai_scheduler/internal/tools_bot"
|
|
||||||
"ai_scheduler/utils"
|
"ai_scheduler/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
|
@ -22,13 +28,18 @@ import (
|
||||||
func InitializeApp(*config.Config, log.AllLogger) (*server.Servers, func(), error) {
|
func InitializeApp(*config.Config, log.AllLogger) (*server.Servers, func(), error) {
|
||||||
panic(wire.Build(
|
panic(wire.Build(
|
||||||
server.ProviderSetServer,
|
server.ProviderSetServer,
|
||||||
|
workflow.ProviderSetWorkflow,
|
||||||
tools.ProviderSetTools,
|
tools.ProviderSetTools,
|
||||||
pkg.ProviderSetClient,
|
pkg.ProviderSetClient,
|
||||||
services.ProviderSetServices,
|
services.ProviderSetServices,
|
||||||
biz.ProviderSetBiz,
|
biz.ProviderSetBiz,
|
||||||
impl.ProviderImpl,
|
impl.ProviderImpl,
|
||||||
utils.ProviderUtils,
|
utils.ProviderUtils,
|
||||||
tools_bot.ProviderSetBotTools,
|
dingtalk.ProviderSetDingTalk,
|
||||||
|
tools_regis.ProviderToolsRegis,
|
||||||
|
// tool_callback.ProviderSetCallBackTools,
|
||||||
|
component.ProviderSet,
|
||||||
|
repo.ProviderSet,
|
||||||
))
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ redis:
|
||||||
host: 47.97.27.195:6379
|
host: 47.97.27.195:6379
|
||||||
type: node
|
type: node
|
||||||
pass: lansexiongdi@666
|
pass: lansexiongdi@666
|
||||||
key: report-api-test
|
key: report-api
|
||||||
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
||||||
minIdleConns: 2 #最小空闲连接数
|
minIdleConns: 2 #最小空闲连接数
|
||||||
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
||||||
|
|
@ -65,6 +65,13 @@ tools:
|
||||||
enabled: true
|
enabled: true
|
||||||
base_url: "https://revcl.1688sup.com/api/admin/afterSales/reseller_pre_ai"
|
base_url: "https://revcl.1688sup.com/api/admin/afterSales/reseller_pre_ai"
|
||||||
|
|
||||||
|
dingtalk:
|
||||||
|
api_key: "dingsbbntrkeiyazcfdg"
|
||||||
|
api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu"
|
||||||
|
table_demand:
|
||||||
|
url: "https://alidocs.dingtalk.com/i/nodes/YQBnd5ExVE6qAbnOiANQg2KKJyeZqMmz"
|
||||||
|
base_id: "2Amq4vjg89RnYx9DTp66m2orW3kdP0wQ"
|
||||||
|
sheet_id_or_name: "数据表"
|
||||||
|
|
||||||
default_prompt:
|
default_prompt:
|
||||||
img_recognize:
|
img_recognize:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
# 服务器配置
|
||||||
|
server:
|
||||||
|
port: 8090
|
||||||
|
host: "0.0.0.0"
|
||||||
|
|
||||||
|
ollama:
|
||||||
|
base_url: "http://192.168.6.109:11434"
|
||||||
|
model: "qwen3-coder:480b-cloud"
|
||||||
|
generate_model: "qwen3-coder:480b-cloud"
|
||||||
|
mapping_model: "deepseek-v3.2:cloud"
|
||||||
|
vl_model: "qwen2.5vl:7b"
|
||||||
|
timeout: "120s"
|
||||||
|
level: "info"
|
||||||
|
format: "json"
|
||||||
|
|
||||||
|
vllm:
|
||||||
|
base_url: "http://117.175.169.61:16001/v1"
|
||||||
|
vl_model: "Qwen2.5-VL-3B-Instruct-AWQ"
|
||||||
|
timeout: "120s"
|
||||||
|
level: "info"
|
||||||
|
|
||||||
|
coze:
|
||||||
|
base_url: "https://api.coze.cn"
|
||||||
|
api_secret: "pat_guUSPk8KZFvIIbVReuaMlOBVAaIISSdkTEV8MaRgVPNv6UEYPHKTBUXznFcxl04H"
|
||||||
|
|
||||||
|
|
||||||
|
sys:
|
||||||
|
session_len: 6
|
||||||
|
channel_pool_len: 100
|
||||||
|
channel_pool_size: 32
|
||||||
|
llm_pool_len: 5
|
||||||
|
heartbeat_interval: 30
|
||||||
|
redis:
|
||||||
|
host: 47.97.27.195:6379
|
||||||
|
type: node
|
||||||
|
pass: lansexiongdi@666
|
||||||
|
key: report-api-test
|
||||||
|
pollSize: 5 #连接池大小,不配置,或配置为0表示不启用连接池
|
||||||
|
minIdleConns: 2 #最小空闲连接数
|
||||||
|
maxIdleTime: 30 #每个连接最大空闲时间,如果超过了这个时间会被关闭
|
||||||
|
tls: 30
|
||||||
|
db:
|
||||||
|
db:
|
||||||
|
driver: mysql
|
||||||
|
source: root:SD###sdf323r343@tcp(121.199.38.107:3306)/sys_ai_test?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
|
||||||
|
tools:
|
||||||
|
zltxOrderDetail:
|
||||||
|
enabled: true
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/ai/%s"
|
||||||
|
add_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/log/%s/%s"
|
||||||
|
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU4MDkxOTU4LCJuYmYiOjE3NTgwOTAxNTgsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.Bjsx9f8yfcrV9EWxb0n6POwnXVOq9XPRD78JFZnnf1_VAVMN78W4W570SZL27PWuDnkD7E4oUg6RzeZwZgl7BZrNpNr-a-QpNC5qCptqrqXeNfVStmX7pxWA8GqnzI8ybkZgbhQ58Gje7DzdJtBq_8zte_LDaYhTYXdIc5EAG0AbCzAk22nPTl47nkMeHtmisXQVLEsdibl1hW3ViFJlXwfXvUrOENItmL1_mRYkggUB0MaTu2nHJOYM6PaOVGLHx-74eepnmK2rm6konFEb6ed-Ukc6gVR-nM9yWZaYLYNGNKJLwZoCX3tRuerq74n4kzQgWmUEJeaVI1yIGSw1zw"
|
||||||
|
zltxProduct:
|
||||||
|
enabled: true
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/oursProduct"
|
||||||
|
add_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/platformProduct/getProductsByOfficialProductId"
|
||||||
|
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU2MTgyNTM1LCJuYmYiOjE3NTYxODA3MzUsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.N1xv1PYbcO8_jR5adaczc16YzGsr4z101gwEZdulkRaREBJNYTOnFrvRxTFx3RJTooXsqTqroE1MR84v_1WPX6BS6kKonA-kC1Jgot6yrt5rFWhGNGb2Cpr9rKIFCCQYmiGd3AUgDazEeaQ0_sodv3E-EXg9VfE1SX8nMcck9Yjnc8NCy7RTWaBIaSeOdZcEl-JfCD0S6GSx3oErp_hk-U9FKGwf60wAuDGTY1R0BP4BYpcEqS-C2LSnsSGyURi54Cuk5xH8r1WuF0Dm5bwAj5d7Hvs77-N_sUF-C5ONqyZJRAEhYLgcmN9RX_WQZfizdQJxizlTczdpzYfy-v-1eQ"
|
||||||
|
zltxOrderStatistics:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/direct/ai/search/"
|
||||||
|
enabled: true
|
||||||
|
api_key: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyQ2VudGVyIiwiZXhwIjoxNzU2MTgyNTM1LCJuYmYiOjE3NTYxODA3MzUsImp0aSI6IjEiLCJQaG9uZSI6IjE4MDAwMDAwMDAwIiwiVXNlck5hbWUiOiJsc3hkIiwiUmVhbE5hbWUiOiLotoXnuqfnrqHnkIblkZgiLCJBY2NvdW50VHlwZSI6MSwiR3JvdXBDb2RlcyI6IlZDTF9DQVNISUVSLFZDTF9PUEVSQVRFLFZDTF9BRE1JTixWQ0xfQUFBLFZDTF9WQ0xfT1BFUkFULFZDTF9JTlZPSUNFLENSTV9BRE1JTixMSUFOTElBTl9BRE1JTixNQVJLRVRNQUcyX0FETUlOLFBIT05FQklMTF9BRE1JTixRSUFOWkhVX1NVUFBFUl9BRE0sTUFSS0VUSU5HU0FBU19TVVBFUkFETUlOLENBUkRfQ09ERSxDQVJEX1BST0NVUkVNRU5ULE1BUktFVElOR1NZU1RFTV9TVVBFUixTVEFUSVNUSUNBTFNZU1RFTV9BRE1JTixaTFRYX0FETUlOLFpMVFhfT1BFUkFURSIsIkRpbmdVc2VySWQiOiIxNjIwMjYxMjMwMjg5MzM4MzQifQ.N1xv1PYbcO8_jR5adaczc16YzGsr4z101gwEZdulkRaREBJNYTOnFrvRxTFx3RJTooXsqTqroE1MR84v_1WPX6BS6kKonA-kC1Jgot6yrt5rFWhGNGb2Cpr9rKIFCCQYmiGd3AUgDazEeaQ0_sodv3E-EXg9VfE1SX8nMcck9Yjnc8NCy7RTWaBIaSeOdZcEl-JfCD0S6GSx3oErp_hk-U9FKGwf60wAuDGTY1R0BP4BYpcEqS-C2LSnsSGyURi54Cuk5xH8r1WuF0Dm5bwAj5d7Hvs77-N_sUF-C5ONqyZJRAEhYLgcmN9RX_WQZfizdQJxizlTczdpzYfy-v-1eQ"
|
||||||
|
knowledge:
|
||||||
|
base_url: "http://117.175.169.61:10000"
|
||||||
|
enabled: true
|
||||||
|
DingTalkBot:
|
||||||
|
enabled: true
|
||||||
|
api_key: "dingsbbntrkeiyazcfdg"
|
||||||
|
api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu"
|
||||||
|
zltxOrderAfterSaleSupplier:
|
||||||
|
enabled: true
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/directs"
|
||||||
|
zltxOrderAfterSaleReseller:
|
||||||
|
enabled: true
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai"
|
||||||
|
zltxOrderAfterSaleResellerBatch:
|
||||||
|
enabled: true
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/zltx_api/admin/afterSales/reseller_pre_ai"
|
||||||
|
|
||||||
|
# eino tool 配置
|
||||||
|
eino_tools:
|
||||||
|
# 货易通商品上传
|
||||||
|
hytProductUpload:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete"
|
||||||
|
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"
|
||||||
|
# 货易通商品添加
|
||||||
|
hytGoodsAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add"
|
||||||
|
add_url: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage"
|
||||||
|
# 货易通商品图片添加
|
||||||
|
hytGoodsMediaAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/media/add/batch"
|
||||||
|
# 货易通商品分类添加
|
||||||
|
hytGoodsCategoryAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/good/category/relation/add"
|
||||||
|
# 货易通商品分类查询
|
||||||
|
hytGoodsCategorySearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/category/list"
|
||||||
|
# 货易通商品品牌查询
|
||||||
|
hytGoodsBrandSearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
|
||||||
|
|
||||||
|
dingtalk:
|
||||||
|
api_key: "dingsbbntrkeiyazcfdg"
|
||||||
|
api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu"
|
||||||
|
table_demand:
|
||||||
|
url: "https://alidocs.dingtalk.com/i/nodes/YQBnd5ExVE6qAbnOiANQg2KKJyeZqMmz"
|
||||||
|
base_id: "YQBnd5ExVE6qAbnOiANQg2KKJyeZqMmz"
|
||||||
|
sheet_id_or_name: "数据表"
|
||||||
|
|
||||||
|
|
||||||
|
default_prompt:
|
||||||
|
img_recognize:
|
||||||
|
system_prompt:
|
||||||
|
'你是一个具备图像理解与用户意图分析能力的智能助手。当用户提供一张图片时,请完成以下任务:
|
||||||
|
1. 关键信息提取:
|
||||||
|
提取出图片中对用户可能有用的关键信息(例如金额、日期、标题、编号、联系信息、商品名称等)。
|
||||||
|
若图片为文档类(如合同、发票、收据),请结构化输出关键字段(如客户名称、金额、开票日期等)。
|
||||||
|
'
|
||||||
|
user_prompt: '识别图片内容'
|
||||||
|
# 权限配置
|
||||||
|
permissionConfig:
|
||||||
|
permission_url: "http://api.test.user.1688sup.cn:8001/v1/menu/myCodes?systemId="
|
||||||
|
|
@ -4,14 +4,24 @@ server:
|
||||||
host: "0.0.0.0"
|
host: "0.0.0.0"
|
||||||
|
|
||||||
ollama:
|
ollama:
|
||||||
base_url: "http://127.0.0.1:11434"
|
base_url: "http://host.docker.internal:11434"
|
||||||
model: "qwen3-coder:480b-cloud"
|
model: "qwen3-coder:480b-cloud"
|
||||||
generate_model: "qwen3-coder:480b-cloud"
|
generate_model: "qwen3-coder:480b-cloud"
|
||||||
vl_model: "qwen2.5vl:7b"
|
mapping_model: "deepseek-v3.2:cloud"
|
||||||
|
vl_model: "gemini-3-pro-preview"
|
||||||
timeout: "120s"
|
timeout: "120s"
|
||||||
level: "info"
|
level: "info"
|
||||||
format: "json"
|
format: "json"
|
||||||
|
|
||||||
|
vllm:
|
||||||
|
base_url: "http://host.docker.internal:8001/v1"
|
||||||
|
vl_model: "Qwen2.5-VL-3B-Instruct-AWQ"
|
||||||
|
timeout: "120s"
|
||||||
|
level: "info"
|
||||||
|
|
||||||
|
coze:
|
||||||
|
base_url: "https://api.coze.cn"
|
||||||
|
api_secret: "sat_AqvFcdNgesP8megy1ItTscWFXRcsHRzmM4NJ1KNavfcdT0EPwYuCPkDqGhItpx13"
|
||||||
|
|
||||||
|
|
||||||
sys:
|
sys:
|
||||||
|
|
@ -19,6 +29,7 @@ sys:
|
||||||
channel_pool_len: 100
|
channel_pool_len: 100
|
||||||
channel_pool_size: 32
|
channel_pool_size: 32
|
||||||
llm_pool_len: 5
|
llm_pool_len: 5
|
||||||
|
heartbeat_interval: 30
|
||||||
redis:
|
redis:
|
||||||
host: 47.97.27.195:6379
|
host: 47.97.27.195:6379
|
||||||
type: node
|
type: node
|
||||||
|
|
@ -64,8 +75,57 @@ 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"
|
||||||
|
weather:
|
||||||
|
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"
|
||||||
|
|
||||||
|
# eino tool 配置
|
||||||
|
eino_tools:
|
||||||
|
# 货易通商品上传
|
||||||
|
hytProductUpload:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/supplier/batch/add/complete"
|
||||||
|
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"
|
||||||
|
# 货易通商品添加
|
||||||
|
hytGoodsAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add"
|
||||||
|
add_url: "https://gateway.dev.cdlsxd.cn/sw//#/goods/goodsManage"
|
||||||
|
# 货易通商品图片添加
|
||||||
|
hytGoodsMediaAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/media/add/batch"
|
||||||
|
# 货易通商品分类添加
|
||||||
|
hytGoodsCategoryAdd:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/good/category/relation/add"
|
||||||
|
# 货易通商品分类查询
|
||||||
|
hytGoodsCategorySearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/category/list"
|
||||||
|
# 货易通商品品牌查询
|
||||||
|
hytGoodsBrandSearch:
|
||||||
|
base_url: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list"
|
||||||
|
|
||||||
|
dingtalk:
|
||||||
|
api_key: "dingsbbntrkeiyazcfdg"
|
||||||
|
api_secret: "ObqxwyR20r9rVNhju0sCPQyQA98_FZSc32W4vgxnGFH_b02HZr1BPCJsOAF816nu"
|
||||||
|
table_demand:
|
||||||
|
url: "https://alidocs.dingtalk.com/i/nodes/YQBnd5ExVE6qAbnOiANQg2KKJyeZqMmz"
|
||||||
|
base_id: "YQBnd5ExVE6qAbnOiANQg2KKJyeZqMmz"
|
||||||
|
sheet_id_or_name: "数据表"
|
||||||
|
|
||||||
default_prompt:
|
default_prompt:
|
||||||
img_recognize:
|
img_recognize:
|
||||||
|
|
@ -79,3 +139,56 @@ default_prompt:
|
||||||
# 权限配置
|
# 权限配置
|
||||||
permissionConfig:
|
permissionConfig:
|
||||||
permission_url: "http://api.test.user.1688sup.cn:8001/v1/menu/myCodes?systemId="
|
permission_url: "http://api.test.user.1688sup.cn:8001/v1/menu/myCodes?systemId="
|
||||||
|
|
||||||
|
# llm 服务配置
|
||||||
|
llm:
|
||||||
|
providers:
|
||||||
|
ollama:
|
||||||
|
endpoint: http://host.docker.internal:11434
|
||||||
|
timeout: 60s
|
||||||
|
models:
|
||||||
|
- id: qwen3-coder:480b-cloud
|
||||||
|
name: qwen3-coder:480b-cloud
|
||||||
|
streaming: true
|
||||||
|
modalities: [text]
|
||||||
|
max_tokens: 4096
|
||||||
|
|
||||||
|
vllm:
|
||||||
|
endpoint: http://117.175.169.61:16001
|
||||||
|
timeout: 60s
|
||||||
|
models:
|
||||||
|
- id: models/Qwen2.5-VL-3B-Instruct-AWQ
|
||||||
|
name: qwen2.5-vl-3b
|
||||||
|
streaming: true
|
||||||
|
modalities: [text, image]
|
||||||
|
max_tokens: 4096
|
||||||
|
|
||||||
|
# 每个能力只绑定一个 provider+model,不做自动回退
|
||||||
|
capabilities:
|
||||||
|
intent:
|
||||||
|
provider: vllm
|
||||||
|
model: qwen2.5-vl-3b
|
||||||
|
parameters:
|
||||||
|
temperature: 0.2
|
||||||
|
max_tokens: 4096
|
||||||
|
stream: false
|
||||||
|
|
||||||
|
vision:
|
||||||
|
provider: vllm
|
||||||
|
model: qwen2.5-vl-3b
|
||||||
|
parameters:
|
||||||
|
temperature: 0.5
|
||||||
|
max_tokens: 4096
|
||||||
|
stream: true
|
||||||
|
|
||||||
|
chat:
|
||||||
|
provider: ollama
|
||||||
|
model: qwen3-coder:480b-cloud
|
||||||
|
parameters:
|
||||||
|
temperature: 0.7
|
||||||
|
max_tokens: 4096
|
||||||
|
stream: true
|
||||||
|
#ding_talk_bots:
|
||||||
|
# public:
|
||||||
|
# client_id: "dingchg59zwwvmuuvldx",
|
||||||
|
# client_secret: "ZwetAnRiTQobNFVlNrshRagSMAJIFpBAepWkWI7on7Tt_o617KHtTjBLp8fQfplz",
|
||||||
|
|
|
||||||
18
deploy.sh
18
deploy.sh
|
|
@ -1,9 +1,9 @@
|
||||||
export GO111MODULE=on
|
#export GO111MODULE=on
|
||||||
export GOPROXY=https://goproxy.cn,direct
|
#export GOPROXY=https://goproxy.cn,direct
|
||||||
export GOPATH=/root/go
|
#export GOPATH=/root/go
|
||||||
export GOCACHE=/root/.cache/go-build
|
#export GOCACHE=/root/.cache/go-build
|
||||||
export CONTAINER_NAME=ai_scheduler
|
export CONTAINER_NAME=ai_scheduler
|
||||||
export CGO_ENABLED='0'
|
#export CGO_ENABLED='0'
|
||||||
|
|
||||||
|
|
||||||
MODE="$1"
|
MODE="$1"
|
||||||
|
|
@ -14,16 +14,18 @@ fi
|
||||||
|
|
||||||
CONFIG_FILE="config/config.yaml"
|
CONFIG_FILE="config/config.yaml"
|
||||||
BRANCH="master"
|
BRANCH="master"
|
||||||
|
BOT="ALL"
|
||||||
if [ "$MODE" = "dev" ]; then
|
if [ "$MODE" = "dev" ]; then
|
||||||
CONFIG_FILE="config/config_test.yaml"
|
CONFIG_FILE="config/config_test.yaml"
|
||||||
|
BOT="zltx"
|
||||||
BRANCH="test"
|
BRANCH="test"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git fetch origin
|
git fetch origin
|
||||||
git checkout "$BRANCH"
|
git checkout "$BRANCH"
|
||||||
git pull origin "$BRANCH"
|
git pull origin "$BRANCH"
|
||||||
go mod tidy
|
#go mod tidy
|
||||||
make build
|
#make build
|
||||||
docker build -t ${CONTAINER_NAME} .
|
docker build -t ${CONTAINER_NAME} .
|
||||||
docker stop ${CONTAINER_NAME}
|
docker stop ${CONTAINER_NAME}
|
||||||
docker rm -f ${CONTAINER_NAME}
|
docker rm -f ${CONTAINER_NAME}
|
||||||
|
|
@ -34,6 +36,6 @@ docker run -itd \
|
||||||
-e "OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}" \
|
-e "OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}" \
|
||||||
-e "MODE=${MODE}" \
|
-e "MODE=${MODE}" \
|
||||||
-p 8090:8090 \
|
-p 8090:8090 \
|
||||||
"${CONTAINER_NAME}" ./bin/server --config "./${CONFIG_FILE}"
|
"${CONTAINER_NAME}" ./server --config "./${CONFIG_FILE}" --bot "./${BOT}"
|
||||||
|
|
||||||
docker logs -f ${CONTAINER_NAME}
|
docker logs -f ${CONTAINER_NAME}
|
||||||
50
go.mod
50
go.mod
|
|
@ -1,19 +1,26 @@
|
||||||
module ai_scheduler
|
module ai_scheduler
|
||||||
|
|
||||||
go 1.24.0
|
go 1.24.7
|
||||||
|
|
||||||
toolchain go1.24.7
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go v0.9.3
|
||||||
gitea.cdlsxd.cn/self-tools/l_request v1.0.8
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12
|
||||||
github.com/alibabacloud-go/dingtalk v1.6.96
|
github.com/alibabacloud-go/dingtalk v1.6.96
|
||||||
github.com/alibabacloud-go/tea v1.2.2
|
github.com/alibabacloud-go/tea v1.2.2
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
||||||
|
github.com/cloudwego/eino v0.7.7
|
||||||
|
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6
|
||||||
|
github.com/cloudwego/eino-ext/components/model/openai v0.1.5
|
||||||
|
github.com/coze-dev/coze-go v0.0.0-20251029161603-312b7fd62d20
|
||||||
github.com/emirpasic/gods v1.18.1
|
github.com/emirpasic/gods v1.18.1
|
||||||
github.com/faabiosr/cachego v0.26.0
|
github.com/faabiosr/cachego v0.26.0
|
||||||
github.com/fastwego/dingding v1.0.0-beta.4
|
github.com/fastwego/dingding v1.0.0-beta.4
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.11
|
||||||
github.com/go-kratos/kratos/v2 v2.9.1
|
github.com/go-kratos/kratos/v2 v2.9.1
|
||||||
|
github.com/go-playground/locales v0.14.1
|
||||||
|
github.com/go-playground/universal-translator v0.18.1
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0
|
||||||
github.com/gofiber/fiber/v2 v2.52.9
|
github.com/gofiber/fiber/v2 v2.52.9
|
||||||
github.com/gofiber/websocket/v2 v2.2.1
|
github.com/gofiber/websocket/v2 v2.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
|
@ -22,9 +29,8 @@ require (
|
||||||
github.com/redis/go-redis/v9 v9.16.0
|
github.com/redis/go-redis/v9 v9.16.0
|
||||||
github.com/spf13/viper v1.17.0
|
github.com/spf13/viper v1.17.0
|
||||||
github.com/tmc/langchaingo v0.1.13
|
github.com/tmc/langchaingo v0.1.13
|
||||||
|
golang.org/x/sync v0.15.0
|
||||||
google.golang.org/grpc v1.64.0
|
google.golang.org/grpc v1.64.0
|
||||||
google.golang.org/protobuf v1.34.1
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
gorm.io/driver/mysql v1.6.0
|
gorm.io/driver/mysql v1.6.0
|
||||||
gorm.io/gorm v1.31.0
|
gorm.io/gorm v1.31.0
|
||||||
xorm.io/builder v0.3.13
|
xorm.io/builder v0.3.13
|
||||||
|
|
@ -39,31 +45,53 @@ require (
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
github.com/aliyun/credentials-go v1.4.6 // indirect
|
github.com/aliyun/credentials-go v1.4.6 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.14.1 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/clbanning/mxj/v2 v2.5.5 // indirect
|
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/eino-contrib/jsonschema v1.0.3 // indirect
|
||||||
|
github.com/eino-contrib/ollama v0.1.0 // indirect
|
||||||
|
github.com/evanphx/json-patch v0.5.2 // indirect
|
||||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // 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.3 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/meguminnnnnnnnn/go-openai v0.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/nikolalohinski/gonja v1.5.3 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
|
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.10.0 // indirect
|
github.com/spf13/afero v1.10.0 // indirect
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
github.com/spf13/cast v1.5.1 // indirect
|
||||||
|
|
@ -71,16 +99,22 @@ require (
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
|
github.com/yargevad/filepathx v1.0.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/arch v0.11.0 // indirect
|
||||||
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
|
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
132
go.sum
132
go.sum
|
|
@ -38,12 +38,15 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go v0.9.3 h1:qaSPxVz5kHCs2AWvShnOG8mUgrUP9Gc3uUB4ZX1BF5A=
|
||||||
|
gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go v0.9.3/go.mod h1:5mCPTjBxOk69LRJPHWJRNTkfxcffqlQSOBMD4M5JVnE=
|
||||||
gitea.cdlsxd.cn/self-tools/l_request v1.0.8 h1:FaKRql9mCVcSoaGqPeBOAruZ52slzRngQ6VRTYKNSsA=
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8 h1:FaKRql9mCVcSoaGqPeBOAruZ52slzRngQ6VRTYKNSsA=
|
||||||
gitea.cdlsxd.cn/self-tools/l_request v1.0.8/go.mod h1:Qf4hVXm2Eu5vOvwXk8D7U0q/aekMCkZ4Fg9wnRKlasQ=
|
gitea.cdlsxd.cn/self-tools/l_request v1.0.8/go.mod h1:Qf4hVXm2Eu5vOvwXk8D7U0q/aekMCkZ4Fg9wnRKlasQ=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
|
|
@ -96,12 +99,29 @@ github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8
|
||||||
github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
|
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||||
|
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||||
|
github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||||
|
github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||||
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
|
github.com/bytedance/mockey v1.2.14 h1:KZaFgPdiUwW+jOWFieo3Lr7INM1P+6adO3hxZhDswY8=
|
||||||
|
github.com/bytedance/mockey v1.2.14/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY=
|
||||||
|
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
||||||
|
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
|
@ -110,9 +130,21 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/cloudwego/eino v0.7.7 h1:WhP0SMWWPgLdOH03HrKUxtP9/Q96NhziMZNEQl9lxpU=
|
||||||
|
github.com/cloudwego/eino v0.7.7/go.mod h1:nA8Vacmuqv3pqKBQbTWENBLQ8MmGmPt/WqiyLeB8ohQ=
|
||||||
|
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6 h1:ZbrhV91uE0hGIOYXhb2i3G6tQJ/rK2SLYtoYrmocZXM=
|
||||||
|
github.com/cloudwego/eino-ext/components/model/ollama v0.1.6/go.mod h1:GDXrvorGdRNV6g2mK5jdla2D8Xc/hh7XDrTeGDteLLo=
|
||||||
|
github.com/cloudwego/eino-ext/components/model/openai v0.1.5 h1:+yvGbTPw93li9GSmdm6Rix88Yy8AXg5NNBcRbWx3CQU=
|
||||||
|
github.com/cloudwego/eino-ext/components/model/openai v0.1.5/go.mod h1:IPVYMFoZcuHeVEsDTGN6SZjvue0xr1iZFhdpq1SBWdQ=
|
||||||
|
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2 h1:r9Id2wzJ05PoHl+Km7jQgNMgciaZI93TVnUYso89esM=
|
||||||
|
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.2/go.mod h1:S4OkvglPY9hsm9tXeShODrf/WN1Cgu4bqu4nn/CnIic=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/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.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.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
|
@ -121,6 +153,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0=
|
||||||
|
github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4=
|
||||||
|
github.com/eino-contrib/ollama v0.1.0 h1:z1NaMdKW6X1ftP8g5xGGR5zDRPUtuTKFq35vBQgxsN4=
|
||||||
|
github.com/eino-contrib/ollama v0.1.0/go.mod h1:mYsQ7b3DeqY8bHPuD3MZJYTqkgyL6LoemxoP/B7ZNhA=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
|
@ -129,6 +167,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
|
||||||
|
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
|
||||||
github.com/faabiosr/cachego v0.15.0/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI=
|
github.com/faabiosr/cachego v0.15.0/go.mod h1:L2EomlU3/rUWjzFavY9Fwm8B4zZmX2X6u8kTMkETrwI=
|
||||||
github.com/faabiosr/cachego v0.26.0 h1:EDDv2y9T0XJ4Cx3tUhbKSUayGWxCGkkZUivNLceHRWY=
|
github.com/faabiosr/cachego v0.26.0 h1:EDDv2y9T0XJ4Cx3tUhbKSUayGWxCGkkZUivNLceHRWY=
|
||||||
github.com/faabiosr/cachego v0.26.0/go.mod h1:p54WXVzeB1CctH1ix/rjqv1EotNzD0Xoxk2IsR1PQX8=
|
github.com/faabiosr/cachego v0.26.0/go.mod h1:p54WXVzeB1CctH1ix/rjqv1EotNzD0Xoxk2IsR1PQX8=
|
||||||
|
|
@ -142,18 +182,34 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
|
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||||
|
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0=
|
github.com/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0=
|
||||||
github.com/go-kratos/kratos/v2 v2.9.1/go.mod h1:a1MQLjMhIh7R0kcJS9SzJYR43BRI7EPzzN0J1Ksu2bA=
|
github.com/go-kratos/kratos/v2 v2.9.1/go.mod h1:a1MQLjMhIh7R0kcJS9SzJYR43BRI7EPzzN0J1Ksu2bA=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
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/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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
|
@ -215,8 +271,14 @@ github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
|
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
|
||||||
|
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
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.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/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
|
@ -224,19 +286,26 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
|
@ -245,8 +314,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
|
@ -255,6 +328,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/meguminnnnnnnnn/go-openai v0.1.0 h1:BGzB1PlS2Epq0mBB2TGLwzMihbR7BANrlMH3w4ZnY88=
|
||||||
|
github.com/meguminnnnnnnnn/go-openai v0.1.0/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|
@ -265,16 +342,22 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
|
||||||
|
github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/ollama/ollama v0.12.7 h1:dxokli1UyO/a0Aun5sE4+0Gg+A9oMUAPiFQhxrXOIXA=
|
github.com/ollama/ollama v0.12.7 h1:dxokli1UyO/a0Aun5sE4+0Gg+A9oMUAPiFQhxrXOIXA=
|
||||||
github.com/ollama/ollama v0.12.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
github.com/ollama/ollama v0.12.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
|
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
|
||||||
|
|
@ -290,6 +373,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
|
||||||
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||||
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
|
@ -297,9 +381,18 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI=
|
||||||
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg=
|
||||||
|
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||||
|
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||||
|
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||||
|
|
@ -311,16 +404,19 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||||
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
||||||
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
|
@ -332,12 +428,20 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1CaA=
|
github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1CaA=
|
||||||
github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg=
|
github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||||
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||||
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||||
|
github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
|
||||||
|
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
@ -353,8 +457,13 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||||
|
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
@ -370,8 +479,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -475,9 +584,10 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -519,6 +629,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -529,8 +640,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
|
@ -539,8 +650,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
@ -553,8 +664,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
@ -721,6 +832,7 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
|
gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,115 @@
|
||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
errors "ai_scheduler/internal/data/error"
|
||||||
"ai_scheduler/internal/data/impl"
|
"ai_scheduler/internal/data/impl"
|
||||||
"ai_scheduler/internal/data/model"
|
"ai_scheduler/internal/data/model"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatHistoryBiz struct {
|
type ChatHistoryBiz struct {
|
||||||
chatRepo *impl.ChatImpl
|
chatHiRepo *impl.ChatHisImpl
|
||||||
|
taskRepo *impl.TaskImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatHistoryBiz(chatRepo *impl.ChatImpl) *ChatHistoryBiz {
|
func NewChatHistoryBiz(chatHiRepo *impl.ChatHisImpl, taskRepo *impl.TaskImpl) *ChatHistoryBiz {
|
||||||
s := &ChatHistoryBiz{
|
s := &ChatHistoryBiz{
|
||||||
chatRepo: chatRepo,
|
chatHiRepo: chatHiRepo,
|
||||||
|
taskRepo: taskRepo,
|
||||||
}
|
}
|
||||||
//go s.AsyncProcess(context.Background())
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (s *ChatHistoryBiz) create(ctx context.Context, sessionID, role, content string) error {
|
// 查询会话历史
|
||||||
// chat := model.AiChatHi{
|
func (s *ChatHistoryBiz) List(ctx context.Context, query *entitys.ChatHistQuery) ([]entitys.ChatHisQueryResponse, error) {
|
||||||
// SessionID: sessionID,
|
|
||||||
// Role: role,
|
con := []impl.CondFunc{
|
||||||
// Content: content,
|
s.chatHiRepo.WithSessionId(query.SessionID),
|
||||||
// }
|
s.chatHiRepo.PaginateScope(query.Page, query.PageSize),
|
||||||
//
|
s.chatHiRepo.OrderByDesc("his_id"),
|
||||||
// return s.chatRepo.Create(&chat)
|
}
|
||||||
//}
|
if query.HisID > 0 {
|
||||||
//
|
con = append(con, s.chatHiRepo.WithHisId(query.HisID))
|
||||||
|
}
|
||||||
|
|
||||||
|
chats, err := s.chatHiRepo.FindAll(
|
||||||
|
con...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
taskIds := make([]int32, 0, len(chats))
|
||||||
|
for _, chat := range chats {
|
||||||
|
// 去重任务ID
|
||||||
|
if !util.Contains(taskIds, chat.TaskID) {
|
||||||
|
taskIds = append(taskIds, chat.TaskID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询任务名称
|
||||||
|
tasks, err := s.taskRepo.FindAll(s.taskRepo.In("task_id", taskIds))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskMap := make(map[int32]model.AiTask)
|
||||||
|
for _, task := range tasks {
|
||||||
|
taskMap[task.TaskID] = task
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建结果
|
||||||
|
result := make([]entitys.ChatHisQueryResponse, 0, len(chats))
|
||||||
|
for _, chat := range chats {
|
||||||
|
item := entitys.ChatHisQueryResponse{}
|
||||||
|
item.FromModel(chat, taskMap[chat.TaskID])
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
//// 添加会话历史
|
//// 添加会话历史
|
||||||
//func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error {
|
//func (s *ChatHistoryBiz) Create(ctx context.Context, chat entitys.ChatHistory) error {
|
||||||
// return s.create(ctx, chat.SessionID, chat.Role.String(), chat.Content)
|
// return s.chatHiRepo.Create(&model.AiChatHi{
|
||||||
|
// SessionID: chat.SessionID,
|
||||||
|
// Ques: chat.Role.String(),
|
||||||
|
// Ans: chat.Content,
|
||||||
|
// })
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
// 更新会话历史内容, 追加内容, 不覆盖原有内容, content 使用json格式存储
|
||||||
|
func (c *ChatHistoryBiz) UpdateContent(ctx context.Context, chat *entitys.UpdateContentRequest) error {
|
||||||
|
var contents []string
|
||||||
|
chatHi, has, err := c.chatHiRepo.FindOne(c.chatHiRepo.WithHisId(chat.HisID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if !has {
|
||||||
|
return errors.NewBusinessErr(errors.InvalidParamCode, "chat history not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" != chatHi.Content {
|
||||||
|
// 解析历史内容
|
||||||
|
err = json.Unmarshal([]byte(chatHi.Content), &contents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contents = append(contents, chat.Content)
|
||||||
|
|
||||||
|
b, err := json.Marshal(contents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chatHi.Content = string(b)
|
||||||
|
return c.chatHiRepo.Update(&chatHi,
|
||||||
|
c.chatHiRepo.Select("content"),
|
||||||
|
c.chatHiRepo.WithHisId(chatHi.HisID))
|
||||||
|
}
|
||||||
|
|
||||||
// 异步添加会话历史
|
// 异步添加会话历史
|
||||||
//func (s *ChatHistoryBiz) AsyncCreate(ctx context.Context, chat entitys.ChatHistory) {
|
//func (s *ChatHistoryBiz) AsyncCreate(ctx context.Context, chat entitys.ChatHistory) {
|
||||||
// s.chatRepo.AsyncCreate(ctx, model.AiChatHi{
|
// s.chatRepo.AsyncCreate(ctx, model.AiChatHi{
|
||||||
|
|
@ -53,5 +127,5 @@ func NewChatHistoryBiz(chatRepo *impl.ChatImpl) *ChatHistoryBiz {
|
||||||
func (s *ChatHistoryBiz) Update(ctx context.Context, chat *entitys.UseFulRequest) error {
|
func (s *ChatHistoryBiz) Update(ctx context.Context, chat *entitys.UseFulRequest) error {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
cond = cond.And(builder.Eq{"his_id": chat.HisId})
|
cond = cond.And(builder.Eq{"his_id": chat.HisId})
|
||||||
return s.chatRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful})
|
return s.chatHiRepo.UpdateByCond(&cond, &model.AiChatHi{HisID: chat.HisId, Useful: chat.Useful})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,564 @@
|
||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/biz/do"
|
||||||
|
"ai_scheduler/internal/biz/handle/dingtalk"
|
||||||
|
"ai_scheduler/internal/biz/tools_regis"
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/internal/tools"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
|
||||||
|
"github.com/coze-dev/coze-go"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AiRouterBiz 智能路由服务
|
||||||
|
type DingTalkBotBiz struct {
|
||||||
|
do *do.Do
|
||||||
|
handle *do.Handle
|
||||||
|
botConfigImpl *impl.BotConfigImpl
|
||||||
|
replier *chatbot.ChatbotReplier
|
||||||
|
log log.Logger
|
||||||
|
dingTalkUser *dingtalk.User
|
||||||
|
botTools []model.AiBotTool
|
||||||
|
botGroupImpl *impl.BotGroupImpl
|
||||||
|
toolManager *tools.Manager
|
||||||
|
chatHis *impl.BotChatHisImpl
|
||||||
|
conf *config.Config
|
||||||
|
cardSend *dingtalk.SendCardClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDingTalkBotBiz
|
||||||
|
func NewDingTalkBotBiz(
|
||||||
|
do *do.Do,
|
||||||
|
handle *do.Handle,
|
||||||
|
botConfigImpl *impl.BotConfigImpl,
|
||||||
|
botGroupImpl *impl.BotGroupImpl,
|
||||||
|
dingTalkUser *dingtalk.User,
|
||||||
|
tools *tools_regis.ToolRegis,
|
||||||
|
chatHis *impl.BotChatHisImpl,
|
||||||
|
toolManager *tools.Manager,
|
||||||
|
conf *config.Config,
|
||||||
|
cardSend *dingtalk.SendCardClient,
|
||||||
|
) *DingTalkBotBiz {
|
||||||
|
return &DingTalkBotBiz{
|
||||||
|
do: do,
|
||||||
|
handle: handle,
|
||||||
|
botConfigImpl: botConfigImpl,
|
||||||
|
replier: chatbot.NewChatbotReplier(),
|
||||||
|
dingTalkUser: dingTalkUser,
|
||||||
|
botTools: tools.BootTools,
|
||||||
|
botGroupImpl: botGroupImpl,
|
||||||
|
toolManager: toolManager,
|
||||||
|
chatHis: chatHis,
|
||||||
|
conf: conf,
|
||||||
|
cardSend: cardSend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) GetDingTalkBotCfgList() (dingBotList []entitys.DingTalkBot, err error) {
|
||||||
|
botConfig := make([]model.AiBotConfig, 0)
|
||||||
|
|
||||||
|
cond := builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"status": constants.Enable})
|
||||||
|
cond = cond.And(builder.Eq{"bot_type": constants.BotTypeDingTalk})
|
||||||
|
err = d.botConfigImpl.GetRangeToMapStruct(&cond, &botConfig)
|
||||||
|
for _, v := range botConfig {
|
||||||
|
var config entitys.DingTalkBot
|
||||||
|
err = json.Unmarshal([]byte(v.BotConfig), &config)
|
||||||
|
if err != nil {
|
||||||
|
d.log.Info("初始化“%s”失败:%s", v.BotName, err.Error())
|
||||||
|
}
|
||||||
|
config.BotIndex = v.RobotCode
|
||||||
|
dingBotList = append(dingBotList, config)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) InitRequire(ctx context.Context, data *chatbot.BotCallbackDataModel) (requireData *entitys.RequireDataDingTalkBot, err error) {
|
||||||
|
requireData = &entitys.RequireDataDingTalkBot{
|
||||||
|
Req: data,
|
||||||
|
Ch: make(chan entitys.Response, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) Do(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||||
|
//entitys.ResLoading(requireData.Ch, "", "收到消息,正在处理中,请稍等")
|
||||||
|
//defer close(requireData.Ch)
|
||||||
|
switch constants.ConversationType(requireData.Req.ConversationType) {
|
||||||
|
case constants.ConversationTypeSingle:
|
||||||
|
err = d.handleSingleChat(ctx, requireData)
|
||||||
|
case constants.ConversationTypeGroup:
|
||||||
|
err = d.handleGroupChat(ctx, requireData)
|
||||||
|
default:
|
||||||
|
err = errors.New("未知的聊天类型:" + requireData.Req.ConversationType)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
entitys.ResText(requireData.Ch, "", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||||
|
entitys.ResLog(requireData.Ch, "", "个人聊天暂未开启,请期待后续更新")
|
||||||
|
return
|
||||||
|
//requireData.UserInfo, err = d.dingTalkUser.GetUserInfoFromBot(ctx, requireData.Req.SenderStaffId, dingtalk.WithId(1))
|
||||||
|
//if err != nil {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//requireData.ID=requireData.UserInfo.UserID
|
||||||
|
////如果不是管理或者不是老板,则进行权限判断
|
||||||
|
//if requireData.UserInfo.IsSenior == constants.IsSeniorFalse && requireData.UserInfo.IsBoss == constants.IsBossFalse {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) {
|
||||||
|
group, err := d.initGroup(ctx, requireData.Req.ConversationId, requireData.Req.ConversationTitle, requireData.Req.RobotCode)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requireData.ID = group.GroupID
|
||||||
|
groupTools, err := d.getGroupTools(ctx, group)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec, err := d.recognize(ctx, requireData, groupTools)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.handleMatch(ctx, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) initGroup(ctx context.Context, conversationId string, conversationTitle string, robotCode string) (group *model.AiBotGroup, err error) {
|
||||||
|
group, err = d.botGroupImpl.GetByConversationIdAndRobotCode(conversationId, robotCode)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.GroupID == 0 {
|
||||||
|
group = &model.AiBotGroup{
|
||||||
|
ConversationID: conversationId,
|
||||||
|
Title: conversationTitle,
|
||||||
|
RobotCode: robotCode,
|
||||||
|
ToolList: "",
|
||||||
|
}
|
||||||
|
//如果不存在则创建
|
||||||
|
_, err = d.botGroupImpl.Add(group)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) getGroupTools(ctx context.Context, group *model.AiBotGroup) (tools []model.AiBotTool, err error) {
|
||||||
|
if len(d.botTools) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
groupRegisTools = make(map[int]struct{})
|
||||||
|
)
|
||||||
|
if group.ToolList != "" {
|
||||||
|
groupToolList := strings.Split(group.ToolList, ",")
|
||||||
|
for _, tool := range groupToolList {
|
||||||
|
if tool == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
num, _err := strconv.Atoi(tool)
|
||||||
|
if _err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
groupRegisTools[num] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range d.botTools {
|
||||||
|
if v.PermissionType == constants.PermissionTypeNone {
|
||||||
|
tools = append(tools, v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ex := groupRegisTools[int(v.ToolID)]; ex {
|
||||||
|
tools = append(tools, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (d *DingTalkBotBiz) recognize(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, tools []model.AiBotTool) (rec *entitys.Recognize, err error) {
|
||||||
|
|
||||||
|
userContent, err := d.getUserContent(requireData.Req.Msgtype, requireData.Req.Text.Content)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rec = &entitys.Recognize{
|
||||||
|
Ch: requireData.Ch,
|
||||||
|
SystemPrompt: d.defaultPrompt(),
|
||||||
|
UserContent: userContent,
|
||||||
|
}
|
||||||
|
//历史记录
|
||||||
|
rec.ChatHis, err = d.getHis(ctx, constants.ConversationType(requireData.Req.ConversationType), requireData.ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//工具注册
|
||||||
|
if len(tools) > 0 {
|
||||||
|
rec.Tasks = make([]entitys.RegistrationTask, 0, len(tools))
|
||||||
|
for _, task := range tools {
|
||||||
|
taskConfig := entitys.TaskConfigDetail{}
|
||||||
|
if err = json.Unmarshal([]byte(task.Config), &taskConfig); err != nil {
|
||||||
|
log.Errorf("解析任务配置失败: %s, 任务ID: %s", err.Error(), task.Index)
|
||||||
|
continue // 解析失败时跳过该任务,而不是直接返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.Tasks = append(rec.Tasks, entitys.RegistrationTask{
|
||||||
|
Name: task.Index,
|
||||||
|
Desc: task.Desc,
|
||||||
|
TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = d.handle.Recognize(ctx, rec, &do.WithDingTalkBot{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) getHis(ctx context.Context, conversationType constants.ConversationType, Id int32) (content entitys.ChatHis, err error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
his []model.AiBotChatHi
|
||||||
|
)
|
||||||
|
cond := builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"his_type": conversationType})
|
||||||
|
cond = cond.And(builder.Eq{"id": Id})
|
||||||
|
_, err = d.chatHis.GetListToStruct(&cond, &dataTemp.ReqPageBo{Limit: d.conf.Sys.SessionLen}, &his, "his_id desc")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messages := make([]entitys.HisMessage, 0, len(his))
|
||||||
|
for _, v := range his {
|
||||||
|
messages = append(messages, entitys.HisMessage{
|
||||||
|
Role: constants.Caller(v.Role), // 用户角色
|
||||||
|
Content: v.Content, // 用户输入内容
|
||||||
|
Timestamp: v.CreateAt.Format(time.DateTime),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return entitys.ChatHis{
|
||||||
|
SessionId: fmt.Sprintf("%s_%d", conversationType, Id),
|
||||||
|
Messages: messages,
|
||||||
|
Context: entitys.HisContext{
|
||||||
|
UserLanguage: constants.LangZhCN, // 默认中文
|
||||||
|
SystemMode: constants.SystemModeTechnicalSupport, // 默认技术支持模式
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) getUserContent(msgType string, msgContent interface{}) (content *entitys.RecognizeUserContent, err error) {
|
||||||
|
switch constants.BotMsgType(msgType) {
|
||||||
|
case constants.BotMsgTypeText:
|
||||||
|
content = &entitys.RecognizeUserContent{
|
||||||
|
Text: msgContent.(string),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("未知的消息类型:" + msgType)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) handleMatch(ctx context.Context, rec *entitys.Recognize) (err error) {
|
||||||
|
|
||||||
|
if !rec.Match.IsMatch {
|
||||||
|
if len(rec.Match.Chat) != 0 {
|
||||||
|
entitys.ResText(rec.Ch, "", rec.Match.Chat)
|
||||||
|
} else {
|
||||||
|
entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pointTask *model.AiBotTool
|
||||||
|
for _, task := range d.botTools {
|
||||||
|
if task.Index == rec.Match.Index {
|
||||||
|
pointTask = &task
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pointTask == nil || pointTask.Index == "other" {
|
||||||
|
return d.otherTask(ctx, rec)
|
||||||
|
}
|
||||||
|
switch constants.TaskType(pointTask.Type) {
|
||||||
|
case constants.TaskTypeFunc:
|
||||||
|
return d.handleTask(ctx, rec, pointTask)
|
||||||
|
case constants.TaskTypeCozeWorkflow:
|
||||||
|
return d.handleCozeWorkflow(ctx, rec, pointTask)
|
||||||
|
default:
|
||||||
|
return d.otherTask(ctx, rec)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) handleCozeWorkflow(ctx context.Context, rec *entitys.Recognize, task *model.AiBotTool) (err error) {
|
||||||
|
entitys.ResLoading(rec.Ch, task.Index, "正在执行工作流(coze)\n")
|
||||||
|
|
||||||
|
customClient := &http.Client{
|
||||||
|
Timeout: time.Minute * 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
authCli := coze.NewTokenAuth(d.conf.Coze.ApiSecret)
|
||||||
|
cozeCli := coze.NewCozeAPI(
|
||||||
|
authCli,
|
||||||
|
coze.WithBaseURL(d.conf.Coze.BaseURL),
|
||||||
|
coze.WithHttpClient(customClient),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从参数中获取workflowID
|
||||||
|
type requestParams struct {
|
||||||
|
Request l_request.Request `json:"request"`
|
||||||
|
}
|
||||||
|
var config requestParams
|
||||||
|
err = json.Unmarshal([]byte(task.Config), &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
workflowId, ok := config.Request.Json["workflow_id"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("workflow_id不能为空")
|
||||||
|
}
|
||||||
|
// 提取参数
|
||||||
|
var data map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(rec.Match.Parameters), &data)
|
||||||
|
|
||||||
|
req := &coze.RunWorkflowsReq{
|
||||||
|
WorkflowID: workflowId,
|
||||||
|
Parameters: data,
|
||||||
|
// IsAsync: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := config.Request.Json["stream"].(bool)
|
||||||
|
|
||||||
|
entitys.ResLog(rec.Ch, task.Index, "工作流执行中...")
|
||||||
|
|
||||||
|
if stream {
|
||||||
|
streamResp, err := cozeCli.Workflows.Runs.Stream(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCozeWorkflowEvents(ctx, streamResp, cozeCli, workflowId, rec.Ch, task.Index)
|
||||||
|
} else {
|
||||||
|
resp, err := cozeCli.Workflows.Runs.Create(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entitys.ResJson(rec.Ch, task.Index, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCozeWorkflowEvents 处理 coze 工作流事件
|
||||||
|
func handleCozeWorkflowEvents(ctx context.Context, resp coze.Stream[coze.WorkflowEvent], cozeCli coze.CozeAPI, workflowID string, ch chan entitys.Response, index string) {
|
||||||
|
defer resp.Close()
|
||||||
|
for {
|
||||||
|
event, err := resp.Recv()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
fmt.Println("Stream finished")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error receiving event:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event.Event {
|
||||||
|
case coze.WorkflowEventTypeMessage:
|
||||||
|
entitys.ResStream(ch, index, event.Message.Content)
|
||||||
|
case coze.WorkflowEventTypeError:
|
||||||
|
entitys.ResError(ch, index, fmt.Sprintf("工作流执行错误: %s", event.Error))
|
||||||
|
case coze.WorkflowEventTypeDone:
|
||||||
|
entitys.ResEnd(ch, index, "工作流执行完成")
|
||||||
|
case coze.WorkflowEventTypeInterrupt:
|
||||||
|
resumeReq := &coze.ResumeRunWorkflowsReq{
|
||||||
|
WorkflowID: workflowID,
|
||||||
|
EventID: event.Interrupt.InterruptData.EventID,
|
||||||
|
ResumeData: "your data",
|
||||||
|
InterruptType: event.Interrupt.InterruptData.Type,
|
||||||
|
}
|
||||||
|
newResp, err := cozeCli.Workflows.Runs.Resume(ctx, resumeReq)
|
||||||
|
if err != nil {
|
||||||
|
entitys.ResError(ch, index, fmt.Sprintf("工作流恢复执行错误: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entitys.ResLog(ch, index, "工作流恢复执行中...")
|
||||||
|
handleCozeWorkflowEvents(ctx, newResp, cozeCli, workflowID, ch, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("done, log:%s\n", resp.Response().LogID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) handleTask(ctx context.Context, rec *entitys.Recognize, task *model.AiBotTool) (err error) {
|
||||||
|
var configData entitys.ConfigDataTool
|
||||||
|
err = json.Unmarshal([]byte(task.Config), &configData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.toolManager.ExecuteTool(ctx, configData.Tool, rec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) otherTask(ctx context.Context, rec *entitys.Recognize) (err error) {
|
||||||
|
entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *DingTalkBotBiz) HandleRes(ctx context.Context, data *chatbot.BotCallbackDataModel, resp entitys.Response, ch chan string) error {
|
||||||
|
// switch resp.Type {
|
||||||
|
// case entitys.ResponseText:
|
||||||
|
// return d.replyText(ctx, data.SessionWebhook, resp.Content)
|
||||||
|
// case entitys.ResponseStream:
|
||||||
|
//
|
||||||
|
// return d.replySteam(ctx, data, ch)
|
||||||
|
// case entitys.ResponseImg:
|
||||||
|
// return d.replyImg(ctx, data.SessionWebhook, resp.Content)
|
||||||
|
// case entitys.ResponseFile:
|
||||||
|
// return d.replyFile(ctx, data.SessionWebhook, resp.Content)
|
||||||
|
// case entitys.ResponseMarkdown:
|
||||||
|
// return d.replyMarkdown(ctx, data.SessionWebhook, resp.Content)
|
||||||
|
// case entitys.ResponseActionCard:
|
||||||
|
// return d.replyActionCard(ctx, data.SessionWebhook, resp.Content)
|
||||||
|
// default:
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) HandleStreamRes(ctx context.Context, data *chatbot.BotCallbackDataModel, content chan string) (err error) {
|
||||||
|
err = d.cardSend.NewCard(ctx, &dingtalk.CardSend{
|
||||||
|
RobotCode: data.RobotCode,
|
||||||
|
ConversationType: constants.ConversationType(data.ConversationType),
|
||||||
|
Template: constants.CardTempDefault,
|
||||||
|
ContentChannel: content, // 指定内容通道
|
||||||
|
ConversationId: data.ConversationId,
|
||||||
|
SenderStaffId: data.SenderStaffId,
|
||||||
|
Title: data.Text.Content,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) ReplyText(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
|
msg := content
|
||||||
|
if len(arg) > 0 {
|
||||||
|
msg = fmt.Sprintf(content, arg)
|
||||||
|
}
|
||||||
|
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) replyImg(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
|
msg := content
|
||||||
|
if len(arg) > 0 {
|
||||||
|
msg = fmt.Sprintf(content, arg)
|
||||||
|
}
|
||||||
|
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) replyFile(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
|
msg := content
|
||||||
|
if len(arg) > 0 {
|
||||||
|
msg = fmt.Sprintf(content, arg)
|
||||||
|
}
|
||||||
|
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) replyMarkdown(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
|
msg := content
|
||||||
|
if len(arg) > 0 {
|
||||||
|
msg = fmt.Sprintf(content, arg)
|
||||||
|
}
|
||||||
|
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) replyActionCard(ctx context.Context, SessionWebhook string, content string, arg ...string) error {
|
||||||
|
msg := content
|
||||||
|
if len(arg) > 0 {
|
||||||
|
msg = fmt.Sprintf(content, arg)
|
||||||
|
}
|
||||||
|
return d.replier.SimpleReplyText(ctx, SessionWebhook, []byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) SaveHis(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, chat []string) (err error) {
|
||||||
|
if len(chat) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
his := []*model.AiBotChatHi{
|
||||||
|
{
|
||||||
|
HisType: requireData.Req.ConversationType,
|
||||||
|
ID: requireData.ID,
|
||||||
|
Role: "user",
|
||||||
|
Content: requireData.Req.Text.Content,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
HisType: requireData.Req.ConversationType,
|
||||||
|
ID: requireData.ID,
|
||||||
|
Role: "system",
|
||||||
|
Content: strings.Join(chat, "\n"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = d.chatHis.Add(his)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DingTalkBotBiz) defaultPrompt() string {
|
||||||
|
|
||||||
|
return `[system] 你是一个智能路由系统,核心职责是 **精准解析用户意图并路由至对应任务模块**。请严格遵循以下规则:
|
||||||
|
[rule]
|
||||||
|
1. **返回格式**:
|
||||||
|
仅输出以下 **严格格式化的 JSON 字符串**(禁用 Markdown):
|
||||||
|
{ "index": "工具索引index", "confidence": 0.0-1.0,"reasoning": "判断理由","parameters":"jsonstring |提取参数","is_match":true||false,"chat": "追问内容"}
|
||||||
|
关键规则(按优先级排序):
|
||||||
|
|
||||||
|
2. **工具匹配**:
|
||||||
|
|
||||||
|
- 若匹配到工具,使用工具的 parameters 作为模板做参数匹配
|
||||||
|
- 注意区分 parameters 中的 必须参数(required) 和 可选参数(optional),按下述参数提取规则处理。
|
||||||
|
- 若**完全无法匹配**,立即设置 is_match: false,并在 chat 中已第一人称的角度提醒用户需要适用何种工具(例:"请问您是要查询订单还是商品呢")。
|
||||||
|
|
||||||
|
1. **参数提取**:
|
||||||
|
|
||||||
|
- 根据 parameters 字段列出的参数名,从用户输入中提取对应值。
|
||||||
|
- **仅提取**明确提及的参数,忽略未列出的内容。
|
||||||
|
- 必须参数仅使用用户直接提及的参数,不允许从上下文推断。
|
||||||
|
- 若必须参数缺失,立即设置 is_match: false,并在 chat 中已第一人称的角度提醒用户提供缺少的参数追问(例:"需要您补充XX信息")。
|
||||||
|
|
||||||
|
4. 格式强制要求:
|
||||||
|
-所有字段值必须是**字符串**(包括 confidence)。
|
||||||
|
-parameters 必须是 **转义后的 JSON 字符串**(如 "{\"product_name\": \"京东月卡\"}")。`
|
||||||
|
}
|
||||||
|
|
@ -19,8 +19,6 @@ import (
|
||||||
|
|
||||||
"gitea.cdlsxd.cn/self-tools/l_request"
|
"gitea.cdlsxd.cn/self-tools/l_request"
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"github.com/gofiber/websocket/v2"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,14 +27,14 @@ type Do struct {
|
||||||
sessionImpl *impl.SessionImpl
|
sessionImpl *impl.SessionImpl
|
||||||
sysImpl *impl.SysImpl
|
sysImpl *impl.SysImpl
|
||||||
taskImpl *impl.TaskImpl
|
taskImpl *impl.TaskImpl
|
||||||
hisImpl *impl.ChatImpl
|
hisImpl *impl.ChatHisImpl
|
||||||
conf *config.Config
|
conf *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDo(
|
func NewDo(
|
||||||
sysImpl *impl.SysImpl,
|
sysImpl *impl.SysImpl,
|
||||||
taskImpl *impl.TaskImpl,
|
taskImpl *impl.TaskImpl,
|
||||||
hisImpl *impl.ChatImpl,
|
hisImpl *impl.ChatHisImpl,
|
||||||
conf *config.Config,
|
conf *config.Config,
|
||||||
) *Do {
|
) *Do {
|
||||||
return &Do{
|
return &Do{
|
||||||
|
|
@ -81,6 +79,20 @@ func (d *Do) DataAuth(ctx context.Context, client *gateway.Client, requireData *
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Do) DataAuthForBot(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) (err error) {
|
||||||
|
|
||||||
|
// 2. 加载系统信息
|
||||||
|
if err = d.loadSystemInfo(ctx, client, requireData); err != nil {
|
||||||
|
return fmt.Errorf("获取系统信息失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 加载任务列表
|
||||||
|
if err = d.loadTaskList(ctx, client, requireData); err != nil {
|
||||||
|
return fmt.Errorf("获取任务列表失败: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 提取数据验证为单独函数
|
// 提取数据验证为单独函数
|
||||||
func (d *Do) validateClientData(client *gateway.Client, requireData *entitys.RequireData) error {
|
func (d *Do) validateClientData(client *gateway.Client, requireData *entitys.RequireData) error {
|
||||||
requireData.Session = client.GetSession()
|
requireData.Session = client.GetSession()
|
||||||
|
|
@ -104,7 +116,7 @@ func (d *Do) validateClientData(client *gateway.Client, requireData *entitys.Req
|
||||||
// 获取系统信息的辅助函数
|
// 获取系统信息的辅助函数
|
||||||
func (d *Do) loadSystemInfo(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) error {
|
func (d *Do) loadSystemInfo(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) error {
|
||||||
if sysInfo := client.GetSysInfo(); sysInfo == nil {
|
if sysInfo := client.GetSysInfo(); sysInfo == nil {
|
||||||
sys, err := d.getSysInfo(requireData)
|
sys, err := d.GetSysInfo(requireData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +131,8 @@ func (d *Do) loadSystemInfo(ctx context.Context, client *gateway.Client, require
|
||||||
// 获取任务列表的辅助函数
|
// 获取任务列表的辅助函数
|
||||||
func (d *Do) loadTaskList(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) error {
|
func (d *Do) loadTaskList(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) error {
|
||||||
if taskInfo := client.GetTasks(); len(taskInfo) == 0 {
|
if taskInfo := client.GetTasks(); len(taskInfo) == 0 {
|
||||||
tasks, err := d.getTasks(requireData.Sys.SysID)
|
// 从数据库获取任务列表, 0 表示获取公共的任务
|
||||||
|
tasks, err := d.GetTasks(requireData.Sys.SysID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +141,7 @@ func (d *Do) loadTaskList(ctx context.Context, client *gateway.Client, requireDa
|
||||||
} else {
|
} else {
|
||||||
requireData.Tasks = taskInfo
|
requireData.Tasks = taskInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,10 +155,10 @@ func (d *Do) loadChatHistory(ctx context.Context, requireData *entitys.RequireDa
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Do) MakeCh(c *websocket.Conn, requireData *entitys.RequireData) (ctx context.Context, deferFunc func()) {
|
func (d *Do) MakeCh(client *gateway.Client, requireData *entitys.RequireData) (ctx context.Context, deferFunc func()) {
|
||||||
requireData.Ch = make(chan entitys.Response)
|
requireData.Ch = make(chan entitys.Response)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
done := d.startMessageHandler(ctx, c, requireData)
|
done := d.startMessageHandler(ctx, client, requireData)
|
||||||
return ctx, func() {
|
return ctx, func() {
|
||||||
close(requireData.Ch) //关闭主通道
|
close(requireData.Ch) //关闭主通道
|
||||||
<-done // 等待消息处理完成
|
<-done // 等待消息处理完成
|
||||||
|
|
@ -202,7 +216,7 @@ func (d *Do) getRequireData() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Do) getSysInfo(requireData *entitys.RequireData) (sysInfo model.AiSy, err error) {
|
func (d *Do) GetSysInfo(requireData *entitys.RequireData) (sysInfo model.AiSy, err error) {
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
cond = cond.And(builder.Eq{"app_key": requireData.Key})
|
cond = cond.And(builder.Eq{"app_key": requireData.Key})
|
||||||
cond = cond.And(builder.IsNull{"delete_at"})
|
cond = cond.And(builder.IsNull{"delete_at"})
|
||||||
|
|
@ -221,12 +235,12 @@ func (d *Do) getSessionChatHis(requireData *entitys.RequireData) (his []model.Ai
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Do) getTasks(sysId int32) (tasks []model.AiTask, err error) {
|
func (d *Do) GetTasks(sysId ...int32) (tasks []model.AiTask, err error) {
|
||||||
|
|
||||||
cond := builder.NewCond()
|
cond := builder.NewCond()
|
||||||
cond = cond.And(builder.Eq{"sys_id": sysId})
|
//cond = cond.And(builder.Eq{"sys_id": sysId})
|
||||||
cond = cond.And(builder.IsNull{"delete_at"})
|
cond = cond.And(builder.IsNull{"delete_at"})
|
||||||
cond = cond.And(builder.Eq{"status": 1})
|
cond = cond.And(builder.Eq{"status": 1}.And(builder.In("sys_id", sysId)))
|
||||||
_, err = d.taskImpl.GetListToStruct(&cond, nil, &tasks, "")
|
_, err = d.taskImpl.GetListToStruct(&cond, nil, &tasks, "")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
@ -235,7 +249,7 @@ func (d *Do) getTasks(sysId int32) (tasks []model.AiTask, err error) {
|
||||||
// startMessageHandler 启动独立的消息处理协程
|
// startMessageHandler 启动独立的消息处理协程
|
||||||
func (d *Do) startMessageHandler(
|
func (d *Do) startMessageHandler(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
c *websocket.Conn,
|
client *gateway.Client,
|
||||||
requireData *entitys.RequireData,
|
requireData *entitys.RequireData,
|
||||||
) <-chan struct{} {
|
) <-chan struct{} {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
@ -254,12 +268,13 @@ func (d *Do) startMessageHandler(
|
||||||
Ques: requireData.Req.Text,
|
Ques: requireData.Req.Text,
|
||||||
Ans: strings.Join(chat, ""),
|
Ans: strings.Join(chat, ""),
|
||||||
Files: requireData.Req.Img,
|
Files: requireData.Req.Img,
|
||||||
|
TaskID: requireData.Task.TaskID,
|
||||||
}
|
}
|
||||||
d.hisImpl.AddWithData(AiRes)
|
d.hisImpl.AddWithData(AiRes)
|
||||||
hisLog.HisId = AiRes.HisID
|
hisLog.HisId = AiRes.HisID
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = entitys.MsgSend(c, entitys.Response{
|
_ = entitys.MsgSend(client, entitys.Response{
|
||||||
Content: pkg.JsonStringIgonErr(hisLog),
|
Content: pkg.JsonStringIgonErr(hisLog),
|
||||||
Type: entitys.ResponseEnd,
|
Type: entitys.ResponseEnd,
|
||||||
})
|
})
|
||||||
|
|
@ -267,7 +282,7 @@ func (d *Do) startMessageHandler(
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for v := range requireData.Ch { // 自动检测通道关闭
|
for v := range requireData.Ch { // 自动检测通道关闭
|
||||||
if err := sendWithTimeout(c, v, 2*time.Second); err != nil {
|
if err := sendWithTimeout(client, v, 10*time.Second); err != nil {
|
||||||
log.Errorf("Send error: %v", err)
|
log.Errorf("Send error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +296,7 @@ func (d *Do) startMessageHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:带超时的 WebSocket 发送
|
// 辅助函数:带超时的 WebSocket 发送
|
||||||
func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Duration) error {
|
func sendWithTimeout(client *gateway.Client, data entitys.Response, timeout time.Duration) error {
|
||||||
sendCtx, cancel := context.WithTimeout(context.Background(), timeout)
|
sendCtx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -294,7 +309,7 @@ func sendWithTimeout(c *websocket.Conn, data entitys.Response, timeout time.Dura
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
// 如果 MsgSend 阻塞,这里会卡住
|
// 如果 MsgSend 阻塞,这里会卡住
|
||||||
err := entitys.MsgSend(c, data)
|
err := entitys.MsgSend(client, data)
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -334,7 +349,7 @@ func (d *Do) LoadUserPermission(client *gateway.Client, requireData *entitys.Req
|
||||||
|
|
||||||
// 检查响应状态码
|
// 检查响应状态码
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
err = errors.SysErr("获取用户权限失败")
|
err = errors.SysErrf("获取用户权限失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,45 @@ import (
|
||||||
"ai_scheduler/internal/biz/llm_service"
|
"ai_scheduler/internal/biz/llm_service"
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/data/constants"
|
"ai_scheduler/internal/data/constants"
|
||||||
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
errors "ai_scheduler/internal/data/error"
|
errors "ai_scheduler/internal/data/error"
|
||||||
"ai_scheduler/internal/data/impl"
|
"ai_scheduler/internal/data/impl"
|
||||||
"ai_scheduler/internal/data/model"
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/internal/domain/workflow/runtime"
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"ai_scheduler/internal/gateway"
|
"ai_scheduler/internal/gateway"
|
||||||
"ai_scheduler/internal/pkg"
|
"ai_scheduler/internal/pkg"
|
||||||
|
"ai_scheduler/internal/pkg/dingtalk"
|
||||||
"ai_scheduler/internal/pkg/l_request"
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
"ai_scheduler/internal/pkg/mapstructure"
|
"ai_scheduler/internal/pkg/mapstructure"
|
||||||
|
"ai_scheduler/internal/pkg/rec_extra"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
"ai_scheduler/internal/tools"
|
"ai_scheduler/internal/tools"
|
||||||
"ai_scheduler/internal/tools_bot"
|
"ai_scheduler/internal/tools/public"
|
||||||
|
errorsSpecial "errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gorm.io/gorm/utils"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coze-dev/coze-go"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"gorm.io/gorm/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handle struct {
|
type Handle struct {
|
||||||
Ollama *llm_service.OllamaService
|
Ollama *llm_service.OllamaService
|
||||||
toolManager *tools.Manager
|
toolManager *tools.Manager
|
||||||
Bot *tools_bot.BotTool
|
|
||||||
conf *config.Config
|
conf *config.Config
|
||||||
sessionImpl *impl.SessionImpl
|
sessionImpl *impl.SessionImpl
|
||||||
|
workflowManager *runtime.Registry
|
||||||
|
dingtalkOldClient *dingtalk.OldClient
|
||||||
|
dingtalkContactClient *dingtalk.ContactClient
|
||||||
|
dingtalkNotableClient *dingtalk.NotableClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandle(
|
func NewHandle(
|
||||||
|
|
@ -34,34 +50,44 @@ func NewHandle(
|
||||||
toolManager *tools.Manager,
|
toolManager *tools.Manager,
|
||||||
conf *config.Config,
|
conf *config.Config,
|
||||||
sessionImpl *impl.SessionImpl,
|
sessionImpl *impl.SessionImpl,
|
||||||
dTalkBot *tools_bot.BotTool,
|
workflowManager *runtime.Registry,
|
||||||
|
dingtalkOldClient *dingtalk.OldClient,
|
||||||
|
dingtalkContactClient *dingtalk.ContactClient,
|
||||||
|
dingtalkNotableClient *dingtalk.NotableClient,
|
||||||
) *Handle {
|
) *Handle {
|
||||||
return &Handle{
|
return &Handle{
|
||||||
Ollama: Ollama,
|
Ollama: Ollama,
|
||||||
toolManager: toolManager,
|
toolManager: toolManager,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
sessionImpl: sessionImpl,
|
sessionImpl: sessionImpl,
|
||||||
Bot: dTalkBot,
|
workflowManager: workflowManager,
|
||||||
|
dingtalkOldClient: dingtalkOldClient,
|
||||||
|
dingtalkContactClient: dingtalkContactClient,
|
||||||
|
dingtalkNotableClient: dingtalkNotableClient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Handle) Recognize(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
func (r *Handle) Recognize(ctx context.Context, rec *entitys.Recognize, promptProcessor PromptOption) (err error) {
|
||||||
entitys.ResLog(requireData.Ch, "recognize_start", "准备意图识别")
|
entitys.ResLog(rec.Ch, "recognize_start", "准备意图识别")
|
||||||
|
|
||||||
|
prompt, err := promptProcessor.CreatePrompt(ctx, rec)
|
||||||
//意图识别
|
//意图识别
|
||||||
recognizeMsg, err := r.Ollama.IntentRecognize(ctx, requireData)
|
recognizeMsg, err := r.Ollama.IntentRecognize(ctx, &entitys.ToolSelect{
|
||||||
|
Prompt: prompt,
|
||||||
|
Tools: rec.Tasks,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entitys.ResLog(requireData.Ch, "recognize", recognizeMsg)
|
entitys.ResLog(rec.Ch, "recognize", recognizeMsg)
|
||||||
entitys.ResLog(requireData.Ch, "recognize_end", "意图识别结束")
|
entitys.ResLog(rec.Ch, "recognize_end", "意图识别结束")
|
||||||
|
|
||||||
var match entitys.Match
|
var match entitys.Match
|
||||||
if err = json.Unmarshal([]byte(recognizeMsg), &match); err != nil {
|
if err = json.Unmarshal([]byte(recognizeMsg), &match); err != nil {
|
||||||
err = errors.SysErr("数据结构错误:%v", err.Error())
|
err = errors.SysErrf("数据结构错误:%v", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requireData.Match = &match
|
rec.Match = &match
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,27 +96,27 @@ func (r *Handle) handleOtherTask(ctx context.Context, requireData *entitys.Requi
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Handle) HandleMatch(ctx context.Context, client *gateway.Client, requireData *entitys.RequireData) (err error) {
|
func (r *Handle) HandleMatch(ctx context.Context, client *gateway.Client, rec *entitys.Recognize, requireData *entitys.RequireData) (err error) {
|
||||||
|
|
||||||
if !requireData.Match.IsMatch {
|
if !rec.Match.IsMatch {
|
||||||
if len(requireData.Match.Chat) != 0 {
|
if len(rec.Match.Chat) != 0 {
|
||||||
entitys.ResText(requireData.Ch, "", requireData.Match.Chat)
|
entitys.ResText(rec.Ch, "", rec.Match.Chat)
|
||||||
} else {
|
} else {
|
||||||
entitys.ResText(requireData.Ch, "", requireData.Match.Reasoning)
|
entitys.ResText(rec.Ch, "", rec.Match.Reasoning)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var pointTask *model.AiTask
|
var pointTask *model.AiTask
|
||||||
for _, task := range requireData.Tasks {
|
for _, task := range requireData.Tasks {
|
||||||
if task.Index == requireData.Match.Index {
|
if task.Index == rec.Match.Index {
|
||||||
pointTask = &task
|
pointTask = &task
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pointTask == nil || pointTask.Index == "other" {
|
if pointTask == nil || pointTask.Index == "other" {
|
||||||
return r.OtherTask(ctx, requireData)
|
return r.OtherTask(ctx, rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验用户权限
|
// 校验用户权限
|
||||||
|
|
@ -101,45 +127,35 @@ func (r *Handle) HandleMatch(ctx context.Context, client *gateway.Client, requir
|
||||||
|
|
||||||
switch constants.TaskType(pointTask.Type) {
|
switch constants.TaskType(pointTask.Type) {
|
||||||
case constants.TaskTypeApi:
|
case constants.TaskTypeApi:
|
||||||
return r.handleApiTask(ctx, requireData, pointTask)
|
return r.handleApiTask(ctx, rec, pointTask)
|
||||||
case constants.TaskTypeFunc:
|
|
||||||
return r.handleTask(ctx, requireData, pointTask)
|
|
||||||
case constants.TaskTypeKnowle:
|
case constants.TaskTypeKnowle:
|
||||||
return r.handleKnowle(ctx, requireData, pointTask)
|
return r.handleKnowle(ctx, rec, pointTask)
|
||||||
|
case constants.TaskTypeFunc:
|
||||||
|
return r.handleTask(ctx, rec, pointTask)
|
||||||
case constants.TaskTypeBot:
|
case constants.TaskTypeBot:
|
||||||
return r.handleBot(ctx, requireData, pointTask)
|
return r.handleBot(ctx, rec, pointTask)
|
||||||
|
case constants.TaskTypeEinoWorkflow:
|
||||||
|
return r.handleEinoWorkflow(ctx, rec, pointTask)
|
||||||
|
case constants.TaskTypeCozeWorkflow:
|
||||||
|
return r.handleCozeWorkflow(ctx, rec, pointTask)
|
||||||
default:
|
default:
|
||||||
return r.handleOtherTask(ctx, requireData)
|
return r.handleOtherTask(ctx, requireData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Handle) OtherTask(ctx context.Context, requireData *entitys.RequireData) (err error) {
|
func (r *Handle) OtherTask(ctx context.Context, requireData *entitys.Recognize) (err error) {
|
||||||
entitys.ResText(requireData.Ch, "", requireData.Match.Reasoning)
|
entitys.ResText(requireData.Ch, "", requireData.Match.Reasoning)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Handle) handleBot(ctx context.Context, requireData *entitys.RequireData, task *model.AiTask) (err error) {
|
func (r *Handle) handleTask(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
var configData entitys.ConfigDataTool
|
|
||||||
err = json.Unmarshal([]byte(task.Config), &configData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = r.Bot.Execute(ctx, configData.Tool, requireData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Handle) handleTask(ctx context.Context, requireData *entitys.RequireData, task *model.AiTask) (err error) {
|
|
||||||
var configData entitys.ConfigDataTool
|
var configData entitys.ConfigDataTool
|
||||||
err = json.Unmarshal([]byte(task.Config), &configData)
|
err = json.Unmarshal([]byte(task.Config), &configData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.toolManager.ExecuteTool(ctx, configData.Tool, requireData)
|
err = r.toolManager.ExecuteTool(ctx, configData.Tool, rec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +164,7 @@ func (r *Handle) handleTask(ctx context.Context, requireData *entitys.RequireDat
|
||||||
}
|
}
|
||||||
|
|
||||||
// 知识库
|
// 知识库
|
||||||
func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireData, task *model.AiTask) (err error) {
|
func (r *Handle) handleKnowle(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configData entitys.ConfigDataTool
|
configData entitys.ConfigDataTool
|
||||||
|
|
@ -160,13 +176,16 @@ func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireD
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ext, err := rec_extra.GetTaskRecExt(rec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
// 通过session 找到知识库session
|
// 通过session 找到知识库session
|
||||||
var has bool
|
var has bool
|
||||||
if len(requireData.Session) == 0 {
|
if len(ext.Session) == 0 {
|
||||||
return errors.SessionNotFound
|
return errors.SessionNotFound
|
||||||
}
|
}
|
||||||
requireData.SessionInfo, has, err = r.sessionImpl.FindOne(r.sessionImpl.WithSessionId(requireData.Session))
|
ext.SessionInfo, has, err = r.sessionImpl.FindOne(r.sessionImpl.WithSessionId(ext.Session))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
} else if !has {
|
} else if !has {
|
||||||
|
|
@ -180,7 +199,7 @@ func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireD
|
||||||
return fmt.Errorf("tool not found: %s", configData.Tool)
|
return fmt.Errorf("tool not found: %s", configData.Tool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if knowledgeTool, ok := tool.(*tools.KnowledgeBaseTool); !ok {
|
if knowledgeTool, ok := tool.(*public.KnowledgeBaseTool); !ok {
|
||||||
return fmt.Errorf("未找到知识库Tool: %s", configData.Tool)
|
return fmt.Errorf("未找到知识库Tool: %s", configData.Tool)
|
||||||
} else {
|
} else {
|
||||||
host = knowledgeTool.GetConfig().BaseURL
|
host = knowledgeTool.GetConfig().BaseURL
|
||||||
|
|
@ -189,15 +208,15 @@ func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireD
|
||||||
}
|
}
|
||||||
|
|
||||||
// 知识库的session为空,请求知识库获取, 并绑定
|
// 知识库的session为空,请求知识库获取, 并绑定
|
||||||
if requireData.SessionInfo.KnowlegeSessionID == "" {
|
if ext.SessionInfo.KnowlegeSessionID == "" {
|
||||||
// 请求知识库
|
// 请求知识库
|
||||||
if sessionIdKnowledge, err = tools.GetKnowledgeBaseSession(host, requireData.Sys.KnowlegeBaseID, requireData.Sys.KnowlegeTenantKey); err != nil {
|
if sessionIdKnowledge, err = public.GetKnowledgeBaseSession(host, ext.Sys.KnowlegeBaseID, ext.Sys.KnowlegeTenantKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定知识库session,下次可以使用
|
// 绑定知识库session,下次可以使用
|
||||||
requireData.SessionInfo.KnowlegeSessionID = sessionIdKnowledge
|
ext.SessionInfo.KnowlegeSessionID = sessionIdKnowledge
|
||||||
if err = r.sessionImpl.Update(&requireData.SessionInfo, r.sessionImpl.WithSessionId(requireData.SessionInfo.SessionID)); err != nil {
|
if err = r.sessionImpl.Update(&ext.SessionInfo, r.sessionImpl.WithSessionId(ext.SessionInfo.SessionID)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,21 +224,21 @@ func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireD
|
||||||
// 用户输入解析
|
// 用户输入解析
|
||||||
var ok bool
|
var ok bool
|
||||||
input := make(map[string]string)
|
input := make(map[string]string)
|
||||||
if err = json.Unmarshal([]byte(requireData.Match.Parameters), &input); err != nil {
|
if err = json.Unmarshal([]byte(rec.Match.Parameters), &input); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if query, ok = input["query"]; !ok {
|
if query, ok = input["query"]; !ok {
|
||||||
return fmt.Errorf("query不能为空")
|
return fmt.Errorf("query不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
requireData.KnowledgeConf = entitys.KnowledgeBaseRequest{
|
ext.KnowledgeConf = entitys.KnowledgeBaseRequest{
|
||||||
Session: requireData.SessionInfo.KnowlegeSessionID,
|
Session: ext.SessionInfo.KnowlegeSessionID,
|
||||||
ApiKey: requireData.Sys.KnowlegeTenantKey,
|
ApiKey: ext.Sys.KnowlegeTenantKey,
|
||||||
Query: query,
|
Query: query,
|
||||||
}
|
}
|
||||||
|
rec.Ext = pkg.JsonByteIgonErr(ext)
|
||||||
// 执行工具
|
// 执行工具
|
||||||
err = r.toolManager.ExecuteTool(ctx, configData.Tool, requireData)
|
err = r.toolManager.ExecuteTool(ctx, configData.Tool, rec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -227,18 +246,113 @@ func (r *Handle) handleKnowle(ctx context.Context, requireData *entitys.RequireD
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Handle) handleApiTask(ctx context.Context, requireData *entitys.RequireData, task *model.AiTask) (err error) {
|
// bot 临时实现,后续转到 eino 工作流
|
||||||
|
func (r *Handle) handleBot(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
|
if task.Index == "bug_optimization_submit" {
|
||||||
|
// Ext 中获取 sessionId
|
||||||
|
sessionID := rec.GetSession()
|
||||||
|
// 获取dingtalk accessToken
|
||||||
|
accessToken, _ := r.dingtalkOldClient.GetAccessToken()
|
||||||
|
// 获取创建者 dingtalk unionId
|
||||||
|
unionId := r.getUserDingtalkUnionId(ctx, accessToken, sessionID)
|
||||||
|
// 附件url
|
||||||
|
var attachmentUrl string
|
||||||
|
for _, file := range rec.UserContent.File {
|
||||||
|
attachmentUrl = file.FileUrl
|
||||||
|
break
|
||||||
|
}
|
||||||
|
recordId, err := r.dingtalkNotableClient.InsertRecord(accessToken, &dingtalk.InsertRecordReq{
|
||||||
|
BaseId: r.conf.Dingtalk.TableDemand.BaseId,
|
||||||
|
SheetIdOrName: r.conf.Dingtalk.TableDemand.SheetIdOrName,
|
||||||
|
// OperatorId: tool_callback.BotBugOptimizationSubmitAdminUnionId,
|
||||||
|
OperatorId: unionId,
|
||||||
|
CreatorUnionId: unionId,
|
||||||
|
Content: rec.UserContent.Text,
|
||||||
|
AttachmentUrl: attachmentUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errCode := r.dingtalkNotableClient.GetHTTPStatus(err)
|
||||||
|
// 权限不足
|
||||||
|
if errCode == 403 {
|
||||||
|
return errorcode.ForbiddenErr("您当前没有AI需求表编辑权限,请联系管理员添加权限")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if recordId == "" {
|
||||||
|
return errors.NewBusinessErr(422, "创建记录失败,请联系管理员")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建跳转链接
|
||||||
|
detailPage := util.BuildJumpLink(r.conf.Dingtalk.TableDemand.Url, "去查看")
|
||||||
|
|
||||||
|
entitys.ResText(rec.Ch, "", fmt.Sprintf("问题已记录,正在分配相关人员处理,请您耐心等待处理结果。点击查看工单进度:%s", detailPage))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.NewBusinessErr(422, "bot 任务未实现")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserDingtalkUnionId 获取用户的 dingtalk unionId
|
||||||
|
func (r *Handle) getUserDingtalkUnionId(ctx context.Context, accessToken, sessionID string) (unionId string) {
|
||||||
|
// 查询用户名
|
||||||
|
session, has, err := r.sessionImpl.FindOne(r.sessionImpl.WithSessionId(sessionID))
|
||||||
|
if err != nil || !has {
|
||||||
|
log.Warnf("session not found: %s", sessionID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
creatorName := session.UserName
|
||||||
|
|
||||||
|
// 获取创建者uid 用户名 -> dingtalk uid
|
||||||
|
creatorId, err := r.dingtalkContactClient.SearchUserOne(accessToken, creatorName)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("search dingtalk user one failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户详情 dingtalk uid -> dingtalk unionId
|
||||||
|
userDetails, err := r.dingtalkOldClient.QueryUserDetails(ctx, creatorId)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("query user dingtalk details failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userDetails == nil {
|
||||||
|
log.Warnf("user details not found: %s", creatorId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unionId = userDetails.UnionID
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Handle) handleApiTask(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
var (
|
var (
|
||||||
request l_request.Request
|
request l_request.Request
|
||||||
requestParam map[string]interface{}
|
requestParam map[string]interface{}
|
||||||
)
|
)
|
||||||
err = json.Unmarshal([]byte(requireData.Match.Parameters), &requestParam)
|
ext, err := rec_extra.GetTaskRecExt(rec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
request.Url = strings.ReplaceAll(task.Config, "${authorization}", requireData.Auth)
|
err = json.Unmarshal([]byte(rec.Match.Parameters), &requestParam)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// request.Url = strings.ReplaceAll(task.Config, "${authorization}", requireData.Auth)
|
||||||
|
task.Config = strings.ReplaceAll(task.Config, "${authorization}", ext.Auth)
|
||||||
for k, v := range requestParam {
|
for k, v := range requestParam {
|
||||||
task.Config = strings.ReplaceAll(task.Config, "${"+k+"}", fmt.Sprintf("%v", v))
|
if vStr, ok := v.(string); ok {
|
||||||
|
task.Config = strings.ReplaceAll(task.Config, "${"+k+"}", vStr)
|
||||||
|
} else {
|
||||||
|
var jsonStr []byte
|
||||||
|
jsonStr, err = json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return errors.NewBusinessErr(422, "请求参数解析失败")
|
||||||
|
}
|
||||||
|
task.Config = strings.ReplaceAll(task.Config, "\"${"+k+"}\"", string(jsonStr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var configData entitys.ConfigDataHttp
|
var configData entitys.ConfigDataHttp
|
||||||
err = json.Unmarshal([]byte(task.Config), &configData)
|
err = json.Unmarshal([]byte(task.Config), &configData)
|
||||||
|
|
@ -253,15 +367,139 @@ func (r *Handle) handleApiTask(ctx context.Context, requireData *entitys.Require
|
||||||
err = errors.NewBusinessErr(422, "api地址获取失败")
|
err = errors.NewBusinessErr(422, "api地址获取失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entitys.ResLoading(rec.Ch, task.Index, "正在请求数据")
|
||||||
|
|
||||||
res, err := request.Send()
|
res, err := request.Send()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entitys.ResJson(requireData.Ch, "", pkg.JsonStringIgonErr(res.Text))
|
entitys.ResJson(rec.Ch, task.Index, res.Text)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eino 工作流
|
||||||
|
func (r *Handle) handleEinoWorkflow(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
|
// token 写入ctx
|
||||||
|
ext, err := rec_extra.GetTaskRecExt(rec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = util.SetTokenToContext(ctx, ext.Auth)
|
||||||
|
|
||||||
|
entitys.ResLoading(rec.Ch, task.Index, "正在执行工作流")
|
||||||
|
|
||||||
|
// 工作流内部输出
|
||||||
|
workflowId := task.Index
|
||||||
|
_, err = r.workflowManager.Invoke(ctx, workflowId, rec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Handle) handleCozeWorkflow(ctx context.Context, rec *entitys.Recognize, task *model.AiTask) (err error) {
|
||||||
|
entitys.ResLoading(rec.Ch, task.Index, "正在执行工作流(coze)")
|
||||||
|
|
||||||
|
customClient := &http.Client{
|
||||||
|
Timeout: time.Minute * 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
authCli := coze.NewTokenAuth(r.conf.Coze.ApiSecret)
|
||||||
|
cozeCli := coze.NewCozeAPI(
|
||||||
|
authCli,
|
||||||
|
coze.WithBaseURL(r.conf.Coze.BaseURL),
|
||||||
|
coze.WithHttpClient(customClient),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从参数中获取workflowID
|
||||||
|
type requestParams struct {
|
||||||
|
Request l_request.Request `json:"request"`
|
||||||
|
}
|
||||||
|
var config requestParams
|
||||||
|
err = json.Unmarshal([]byte(task.Config), &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
workflowId, ok := config.Request.Json["workflow_id"].(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("workflow_id不能为空")
|
||||||
|
}
|
||||||
|
// 提取参数
|
||||||
|
var data map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(rec.Match.Parameters), &data)
|
||||||
|
|
||||||
|
req := &coze.RunWorkflowsReq{
|
||||||
|
WorkflowID: workflowId,
|
||||||
|
Parameters: data,
|
||||||
|
// IsAsync: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := config.Request.Json["stream"].(bool)
|
||||||
|
|
||||||
|
entitys.ResLog(rec.Ch, task.Index, "工作流执行中...")
|
||||||
|
|
||||||
|
if stream {
|
||||||
|
streamResp, err := cozeCli.Workflows.Runs.Stream(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCozeWorkflowEvents(ctx, streamResp, cozeCli, workflowId, rec.Ch, task.Index)
|
||||||
|
} else {
|
||||||
|
resp, err := cozeCli.Workflows.Runs.Create(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entitys.ResJson(rec.Ch, task.Index, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCozeWorkflowEvents 处理 coze 工作流事件
|
||||||
|
func handleCozeWorkflowEvents(ctx context.Context, resp coze.Stream[coze.WorkflowEvent], cozeCli coze.CozeAPI, workflowID string, ch chan entitys.Response, index string) {
|
||||||
|
defer resp.Close()
|
||||||
|
for {
|
||||||
|
event, err := resp.Recv()
|
||||||
|
if errorsSpecial.Is(err, io.EOF) {
|
||||||
|
fmt.Println("Stream finished")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error receiving event:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event.Event {
|
||||||
|
case coze.WorkflowEventTypeMessage:
|
||||||
|
entitys.ResStream(ch, index, event.Message.Content)
|
||||||
|
case coze.WorkflowEventTypeError:
|
||||||
|
entitys.ResError(ch, index, fmt.Sprintf("工作流执行错误: %s", event.Error))
|
||||||
|
case coze.WorkflowEventTypeDone:
|
||||||
|
entitys.ResEnd(ch, index, "工作流执行完成")
|
||||||
|
case coze.WorkflowEventTypeInterrupt:
|
||||||
|
resumeReq := &coze.ResumeRunWorkflowsReq{
|
||||||
|
WorkflowID: workflowID,
|
||||||
|
EventID: event.Interrupt.InterruptData.EventID,
|
||||||
|
ResumeData: "your data",
|
||||||
|
InterruptType: event.Interrupt.InterruptData.Type,
|
||||||
|
}
|
||||||
|
newResp, err := cozeCli.Workflows.Runs.Resume(ctx, resumeReq)
|
||||||
|
if err != nil {
|
||||||
|
entitys.ResError(ch, index, fmt.Sprintf("工作流恢复执行错误: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entitys.ResLog(ch, index, "工作流恢复执行中...")
|
||||||
|
handleCozeWorkflowEvents(ctx, newResp, cozeCli, workflowID, ch, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("done, log:%s\n", resp.Response().LogID())
|
||||||
|
}
|
||||||
|
|
||||||
// 权限验证
|
// 权限验证
|
||||||
func (r *Handle) PermissionAuth(client *gateway.Client, pointTask *model.AiTask) (err error) {
|
func (r *Handle) PermissionAuth(client *gateway.Client, pointTask *model.AiTask) (err error) {
|
||||||
// 授权检查权限
|
// 授权检查权限
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
package do
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/biz/handle"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
"ai_scheduler/internal/pkg/utils_vllm"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PromptOption interface {
|
||||||
|
CreatePrompt(ctx context.Context, rec *entitys.Recognize) (mes []api.Message, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithSys struct {
|
||||||
|
Config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WithSys) CreatePrompt(ctx context.Context, rec *entitys.Recognize) (mes []api.Message, err error) {
|
||||||
|
var (
|
||||||
|
prompt = make([]api.Message, 0) // 初始化一个空的api.Message切片
|
||||||
|
)
|
||||||
|
// 获取用户内容,如果出错则直接返回错误
|
||||||
|
content, err := f.getUserContent(ctx, rec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 构建提示消息列表,包含系统提示、助手回复和用户内容
|
||||||
|
mes = append(prompt, api.Message{
|
||||||
|
Role: "system", // 系统角色
|
||||||
|
Content: rec.SystemPrompt, // 系统提示内容
|
||||||
|
}, api.Message{
|
||||||
|
Role: "assistant", // 助手角色
|
||||||
|
Content: "### 聊天记录:" + pkg.JsonStringIgonErr(rec.ChatHis), // 助手回复内容
|
||||||
|
}, api.Message{
|
||||||
|
Role: "user", // 用户角色
|
||||||
|
Content: content.String(), // 用户输入内容
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WithSys) getUserContent(ctx context.Context, rec *entitys.Recognize) (content strings.Builder, err error) {
|
||||||
|
var hasFile bool
|
||||||
|
if len(rec.UserContent.File) > 0 {
|
||||||
|
hasFile = true
|
||||||
|
}
|
||||||
|
content.WriteString(rec.UserContent.Text)
|
||||||
|
if hasFile {
|
||||||
|
content.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rec.UserContent.Tag) > 0 {
|
||||||
|
content.WriteString("\n")
|
||||||
|
content.WriteString("### 工具必须使用:")
|
||||||
|
content.WriteString(rec.UserContent.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rec.ChatHis.Messages) > 0 {
|
||||||
|
content.WriteString("### 引用历史聊天记录:\n")
|
||||||
|
content.WriteString(pkg.JsonStringIgonErr(rec.ChatHis))
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasFile {
|
||||||
|
content.WriteString("\n")
|
||||||
|
content.WriteString("### 文件内容:\n")
|
||||||
|
for _, file := range rec.UserContent.File {
|
||||||
|
handle.HandleRecognizeFile(file)
|
||||||
|
// 文件识别
|
||||||
|
switch file.FileType {
|
||||||
|
case constants.FileTypeImage:
|
||||||
|
entitys.ResLog(rec.Ch, "recognize_img_start", "图片识别中...")
|
||||||
|
var imageContent string
|
||||||
|
imageContent, err = f.recognizeWithImgVllm(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entitys.ResLog(rec.Ch, "recognize_img_end", "图片识别完成,识别内容:"+imageContent)
|
||||||
|
|
||||||
|
// 解析结果回写到file
|
||||||
|
file.FileRec = imageContent
|
||||||
|
default:
|
||||||
|
content.WriteString(file.FileRec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WithSys) recognizeWithImgVllm(ctx context.Context, file *entitys.RecognizeFile) (content string, err error) {
|
||||||
|
if file.FileData == nil || file.FileType != constants.FileTypeImage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, cleanup, err := utils_vllm.NewClient(f.Config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
outMsg, err := client.RecognizeWithImgBytes(ctx,
|
||||||
|
f.Config.DefaultPrompt.ImgRecognize.SystemPrompt,
|
||||||
|
f.Config.DefaultPrompt.ImgRecognize.UserPrompt,
|
||||||
|
file.FileData,
|
||||||
|
file.FileRealMime,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outMsg.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithDingTalkBot struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WithDingTalkBot) CreatePrompt(ctx context.Context, rec *entitys.Recognize) (mes []api.Message, err error) {
|
||||||
|
var (
|
||||||
|
prompt = make([]api.Message, 0) // 初始化一个空的api.Message切片
|
||||||
|
)
|
||||||
|
// 获取用户内容,如果出错则直接返回错误
|
||||||
|
content, err := f.getUserContent(ctx, rec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 构建提示消息列表,包含系统提示、助手回复和用户内容
|
||||||
|
mes = append(prompt, api.Message{
|
||||||
|
Role: "system", // 系统角色
|
||||||
|
Content: rec.SystemPrompt, // 系统提示内容
|
||||||
|
}, api.Message{
|
||||||
|
Role: "assistant", // 助手角色
|
||||||
|
Content: "### 聊天记录:" + pkg.JsonStringIgonErr(rec.ChatHis), // 助手回复内容
|
||||||
|
}, api.Message{
|
||||||
|
Role: "user", // 用户角色
|
||||||
|
Content: content.String(), // 用户输入内容
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *WithDingTalkBot) getUserContent(ctx context.Context, rec *entitys.Recognize) (content strings.Builder, err error) {
|
||||||
|
var hasFile bool
|
||||||
|
if rec.UserContent.File != nil && len(rec.UserContent.File) > 0 {
|
||||||
|
hasFile = true
|
||||||
|
}
|
||||||
|
content.WriteString(rec.UserContent.Text)
|
||||||
|
if hasFile {
|
||||||
|
content.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rec.UserContent.Tag) > 0 {
|
||||||
|
content.WriteString("\n")
|
||||||
|
content.WriteString("### 工具必须使用:")
|
||||||
|
content.WriteString(rec.UserContent.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rec.ChatHis.Messages) > 0 {
|
||||||
|
content.WriteString("### 引用历史聊天记录:\n")
|
||||||
|
content.WriteString(pkg.JsonStringIgonErr(rec.ChatHis))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
redis *redis.Client
|
||||||
|
cfg *config.Config
|
||||||
|
botConfigImpl *impl.BotConfigImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuth(cfg *config.Config, redis *utils.Rdb, botConfigImpl *impl.BotConfigImpl) *Auth {
|
||||||
|
return &Auth{
|
||||||
|
redis: redis.Rdb,
|
||||||
|
cfg: cfg,
|
||||||
|
botConfigImpl: botConfigImpl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) GetAccessToken(ctx context.Context, clientId string, clientSecret string) (authInfo *AuthInfo, err error) {
|
||||||
|
if clientId == "" {
|
||||||
|
return nil, errors.New("clientId is empty")
|
||||||
|
}
|
||||||
|
accessToken := a.redis.Get(ctx, a.getKey(clientId)).Val()
|
||||||
|
var expire time.Duration
|
||||||
|
if accessToken == "" {
|
||||||
|
dingTalkAuthRes, _err := a.getNewAccessToken(ctx, clientId, clientSecret)
|
||||||
|
if _err != nil {
|
||||||
|
return nil, _err
|
||||||
|
}
|
||||||
|
expire = time.Duration(dingTalkAuthRes.ExpireIn-3600) * time.Second
|
||||||
|
err = a.redis.SetEx(ctx, a.getKey(clientId), dingTalkAuthRes.AccessToken, expire).Err()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accessToken = dingTalkAuthRes.AccessToken
|
||||||
|
} else {
|
||||||
|
expire, _ = a.redis.TTL(ctx, a.getKey(clientId)).Result()
|
||||||
|
}
|
||||||
|
return &AuthInfo{
|
||||||
|
ClientId: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
Expire: expire,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) getKey(clientId string) string {
|
||||||
|
return a.cfg.Redis.Key + ":" + constants.DingTalkAuthBaseKeyPrefix + ":" + clientId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) getKeyBot(botCode string) string {
|
||||||
|
return a.cfg.Redis.Key + ":" + constants.DingTalkAuthBaseKeyBotPrefix + ":" + botCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) getNewAccessToken(ctx context.Context, clientId string, clientSecret string) (auth DingTalkAuthIRes, err error) {
|
||||||
|
if clientId == "" || clientSecret == "" {
|
||||||
|
err = errors.New("clientId or clientSecret is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Url: "https://api.dingtalk.com/v1.0/oauth2/accessToken",
|
||||||
|
Json: map[string]interface{}{
|
||||||
|
"appKey": clientId,
|
||||||
|
"appSecret": clientSecret,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(res.Content, &auth)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) GetTokenFromBotOption(ctx context.Context, botOption ...BotOption) (token *AuthInfo, err error) {
|
||||||
|
botInfo := &Bot{}
|
||||||
|
for _, option := range botOption {
|
||||||
|
option(botInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if botInfo.Id == 0 && botInfo.BotConfig == nil && botInfo.BotCode == "" {
|
||||||
|
err = errors.New("botInfo is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if botInfo.BotConfig == nil {
|
||||||
|
err = a.GetBotConfigFromModel(botInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authInfo := a.redis.Get(ctx, a.getKeyBot(botInfo.BotConfig.RobotCode)).Val()
|
||||||
|
if authInfo == "" {
|
||||||
|
var botConfig entitys.DingTalkBot
|
||||||
|
err = json.Unmarshal([]byte(botInfo.BotConfig.BotConfig), &botConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("初始化“%s”失败:%s", botInfo.BotConfig.BotName, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token, err = a.GetAccessToken(ctx, botConfig.ClientId, botConfig.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = a.redis.SetEx(ctx, a.getKeyBot(botInfo.BotConfig.RobotCode), pkg.JsonStringIgonErr(token), token.Expire).Err()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var tokenData AuthInfo
|
||||||
|
err = json.Unmarshal([]byte(authInfo), &tokenData)
|
||||||
|
token = &tokenData
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) GetBotConfigFromModel(botInfo *Bot) (err error) {
|
||||||
|
var (
|
||||||
|
botConfigDo model.AiBotConfig
|
||||||
|
)
|
||||||
|
cond := builder.NewCond()
|
||||||
|
if botInfo.Id > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"bot_id": botInfo.Id})
|
||||||
|
}
|
||||||
|
if botInfo.BotCode != "" {
|
||||||
|
cond = cond.And(builder.Eq{"robot_code": botInfo.BotCode})
|
||||||
|
}
|
||||||
|
err = a.botConfigImpl.GetOneBySearchToStrut(&cond, &botConfigDo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if botConfigDo.BotID == 0 {
|
||||||
|
err = errors.New("未找到机器人服务配置")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
botInfo.BotConfig = &botConfigDo
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dept struct {
|
||||||
|
dingDeptImpl *impl.BotDeptImpl
|
||||||
|
auth *Auth
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDept(dingDeptImpl *impl.BotDeptImpl, auth *Auth) *Dept {
|
||||||
|
return &Dept{
|
||||||
|
dingDeptImpl: dingDeptImpl,
|
||||||
|
auth: auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dept) GetDeptInfoByDeptIds(ctx context.Context, deptIds []int, authInfo *AuthInfo) (depts []*entitys.Dept, err error) {
|
||||||
|
if len(deptIds) == 0 || authInfo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var deptsInfo []model.AiBotDept
|
||||||
|
cond := builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"dingtalk_dept_id": deptIds})
|
||||||
|
err = d.dingDeptImpl.GetRangeToMapStruct(&cond, &deptsInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var existDept = make([]int, len(deptsInfo), 0)
|
||||||
|
for _, dept := range deptsInfo {
|
||||||
|
depts = append(depts, &entitys.Dept{
|
||||||
|
DeptId: int(dept.DeptID),
|
||||||
|
Name: dept.Name,
|
||||||
|
ToolList: dept.ToolList,
|
||||||
|
})
|
||||||
|
existDept = append(existDept, int(dept.DeptID))
|
||||||
|
}
|
||||||
|
diff := pkg.Difference(deptIds, existDept)
|
||||||
|
if len(diff) > 0 {
|
||||||
|
deptDo := make([]model.AiBotDept, 0)
|
||||||
|
for _, deptId := range diff {
|
||||||
|
deptInfo, _err := d.GetDeptInfoFromDingTalk(ctx, deptId, authInfo.AccessToken)
|
||||||
|
if _err != nil {
|
||||||
|
return nil, _err
|
||||||
|
}
|
||||||
|
depts = append(depts, &entitys.Dept{
|
||||||
|
DeptId: deptInfo.DeptId,
|
||||||
|
Name: deptInfo.Name,
|
||||||
|
})
|
||||||
|
deptDo = append(deptDo, model.AiBotDept{
|
||||||
|
DingtalkDeptID: int32(deptInfo.DeptId),
|
||||||
|
Name: deptInfo.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(deptDo) > 0 {
|
||||||
|
_, err = d.dingDeptImpl.Add(deptDo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dept) GetDeptInfoFromDingTalk(ctx context.Context, deptId int, token string) (depts DeptResResult, err error) {
|
||||||
|
if deptId == 0 || len(token) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Url: constants.GetDingTalkRequestUrl(constants.RequestUrlGetDeptGet, map[string]string{
|
||||||
|
"access_token": token,
|
||||||
|
}),
|
||||||
|
Json: map[string]interface{}{
|
||||||
|
"dept_id": deptId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, _err := req.Send()
|
||||||
|
if _err != nil {
|
||||||
|
err = _err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var deptInfo DeptRes
|
||||||
|
|
||||||
|
err = json.Unmarshal(res.Content, &deptInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deptInfo.Errcode != 0 {
|
||||||
|
fmt.Errorf("钉钉请求报错:%s", deptInfo.Errmsg)
|
||||||
|
}
|
||||||
|
return deptInfo.DeptResResult, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import "ai_scheduler/internal/data/model"
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
Id int
|
||||||
|
BotCode string
|
||||||
|
BotConfig *model.AiBotConfig
|
||||||
|
}
|
||||||
|
type BotOption func(*Bot)
|
||||||
|
|
||||||
|
func WithId(id int) BotOption {
|
||||||
|
return func(b *Bot) {
|
||||||
|
b.Id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBotConfig(BotConfig *model.AiBotConfig) BotOption {
|
||||||
|
return func(bot *Bot) {
|
||||||
|
bot.BotConfig = BotConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBotCode(BotCode string) BotOption {
|
||||||
|
return func(bot *Bot) {
|
||||||
|
bot.BotCode = BotCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBot(botSelf *Bot) BotOption {
|
||||||
|
return func(bot *Bot) {
|
||||||
|
bot.BotCode = botSelf.BotCode
|
||||||
|
bot.Id = botSelf.Id
|
||||||
|
bot.BotConfig = botSelf.BotConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ProviderSetDingTalk = wire.NewSet(
|
||||||
|
NewUser,
|
||||||
|
NewAuth,
|
||||||
|
NewDept,
|
||||||
|
NewSendCardClient,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
dingtalkim_1_0 "github.com/alibabacloud-go/dingtalk/im_1_0"
|
||||||
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultInterval = 100 * time.Millisecond
|
||||||
|
const HeardBeatX = 50
|
||||||
|
|
||||||
|
type SendCardClient struct {
|
||||||
|
Auth *Auth
|
||||||
|
CardClient *sync.Map
|
||||||
|
mu sync.RWMutex // 保护 CardClient 的并发访问
|
||||||
|
logger log.AllLogger // 日志记录
|
||||||
|
botOption *Bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendCardClient(auth *Auth, logger log.AllLogger) *SendCardClient {
|
||||||
|
return &SendCardClient{
|
||||||
|
Auth: auth,
|
||||||
|
CardClient: &sync.Map{},
|
||||||
|
logger: logger,
|
||||||
|
botOption: &Bot{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initClient 初始化或复用 DingTalk 客户端
|
||||||
|
func (s *SendCardClient) initClient(robotCode string) (*dingtalkim_1_0.Client, error) {
|
||||||
|
if client, ok := s.CardClient.Load(robotCode); ok {
|
||||||
|
return client.(*dingtalkim_1_0.Client), nil
|
||||||
|
}
|
||||||
|
s.botOption.BotCode = robotCode
|
||||||
|
config := &openapi.Config{
|
||||||
|
Protocol: tea.String("https"),
|
||||||
|
RegionId: tea.String("central"),
|
||||||
|
}
|
||||||
|
client, err := dingtalkim_1_0.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to init DingTalk client")
|
||||||
|
return nil, fmt.Errorf("init client failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.CardClient.Store(robotCode, client)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendCardClient) NewCard(ctx context.Context, cardSend *CardSend) error {
|
||||||
|
// 参数校验
|
||||||
|
if (len(cardSend.ContentSlice) == 0 || cardSend.ContentSlice == nil) && cardSend.ContentChannel == nil {
|
||||||
|
return errors.New("卡片内容不能为空")
|
||||||
|
}
|
||||||
|
if cardSend.UpdateInterval == 0 {
|
||||||
|
cardSend.UpdateInterval = DefaultInterval // 默认更新间隔
|
||||||
|
}
|
||||||
|
if cardSend.Title == "" {
|
||||||
|
cardSend.Title = "钉钉卡片"
|
||||||
|
}
|
||||||
|
//替换标题
|
||||||
|
replace, err := pkg.SafeReplace(string(cardSend.Template), "${title}", cardSend.Title)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cardSend.Template = constants.CardTemp(replace)
|
||||||
|
// 初始化客户端
|
||||||
|
client, err := s.initClient(cardSend.RobotCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化client失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成卡片实例ID
|
||||||
|
cardInstanceId, err := uuid.NewUUID()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建uuid失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建初始请求
|
||||||
|
request, err := s.buildBaseRequest(cardSend, cardInstanceId.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送初始卡片
|
||||||
|
if _, err := s.SendInteractiveCard(ctx, request, cardSend.RobotCode, client); err != nil {
|
||||||
|
return fmt.Errorf("发送初始卡片失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理切片内容(同步)
|
||||||
|
if len(cardSend.ContentSlice) > 0 {
|
||||||
|
if err := s.processContentSlice(ctx, cardSend, cardInstanceId.String(), client); err != nil {
|
||||||
|
return fmt.Errorf("内容同步失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理通道内容(异步)
|
||||||
|
if cardSend.ContentChannel != nil {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
s.processContentChannel(ctx, cardSend, cardInstanceId.String(), client)
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildBaseRequest 构建基础请求
|
||||||
|
func (s *SendCardClient) buildBaseRequest(cardSend *CardSend, cardInstanceId string) (*dingtalkim_1_0.SendRobotInteractiveCardRequest, error) {
|
||||||
|
cardData := fmt.Sprintf(string(cardSend.Template), "") // 初始空内容
|
||||||
|
request := &dingtalkim_1_0.SendRobotInteractiveCardRequest{
|
||||||
|
CardTemplateId: tea.String("StandardCard"),
|
||||||
|
CardBizId: tea.String(cardInstanceId),
|
||||||
|
CardData: tea.String(cardData),
|
||||||
|
RobotCode: tea.String(cardSend.RobotCode),
|
||||||
|
SendOptions: &dingtalkim_1_0.SendRobotInteractiveCardRequestSendOptions{},
|
||||||
|
PullStrategy: tea.Bool(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cardSend.ConversationType {
|
||||||
|
case constants.ConversationTypeGroup:
|
||||||
|
request.SetOpenConversationId(cardSend.ConversationId)
|
||||||
|
case constants.ConversationTypeSingle:
|
||||||
|
receiver, err := json.Marshal(map[string]string{"userId": cardSend.SenderStaffId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("数据整理失败: %w", err)
|
||||||
|
}
|
||||||
|
request.SetSingleChatReceiver(string(receiver))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("未知的聊天场景")
|
||||||
|
}
|
||||||
|
|
||||||
|
return request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processContentChannel 处理通道内容(异步更新)
|
||||||
|
func (s *SendCardClient) processContentChannel(ctx context.Context, cardSend *CardSend, cardInstanceId string, client *dingtalkim_1_0.Client) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
s.logger.Error("panic in processContentChannel")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(cardSend.UpdateInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
heartbeatTicker := time.NewTicker(time.Duration(HeardBeatX) * DefaultInterval)
|
||||||
|
defer heartbeatTicker.Stop()
|
||||||
|
|
||||||
|
var (
|
||||||
|
contentBuilder strings.Builder
|
||||||
|
lastUpdate time.Time
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case content, ok := <-cardSend.ContentChannel:
|
||||||
|
if !ok {
|
||||||
|
// 通道关闭,发送最终内容
|
||||||
|
if contentBuilder.Len() > 0 {
|
||||||
|
if err := s.updateCardContent(ctx, cardSend, cardInstanceId, contentBuilder.String(), client); err != nil {
|
||||||
|
s.logger.Errorf("更新卡片失败1:%s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBuilder.WriteString(content)
|
||||||
|
if contentBuilder.Len() > 0 {
|
||||||
|
if err := s.updateCardContent(ctx, cardSend, cardInstanceId, contentBuilder.String(), client); err != nil {
|
||||||
|
s.logger.Errorf("更新卡片失败2:%s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastUpdate = time.Now()
|
||||||
|
|
||||||
|
case <-heartbeatTicker.C:
|
||||||
|
if time.Now().Unix()-lastUpdate.Unix() >= HeardBeatX {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.logger.Info("context canceled, stop channel processing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// processContentSlice 处理切片内容(同步更新)
|
||||||
|
func (s *SendCardClient) processContentSlice(ctx context.Context, cardSend *CardSend, cardInstanceId string, client *dingtalkim_1_0.Client) error {
|
||||||
|
var contentBuilder strings.Builder
|
||||||
|
for _, content := range cardSend.ContentSlice {
|
||||||
|
|
||||||
|
contentBuilder.WriteString(content)
|
||||||
|
err := s.updateCardRequest(ctx, &UpdateCardRequest{
|
||||||
|
Template: string(cardSend.Template),
|
||||||
|
Content: contentBuilder.String(),
|
||||||
|
Client: client,
|
||||||
|
RobotCode: cardSend.RobotCode,
|
||||||
|
CardInstanceId: cardInstanceId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("更新卡片失败: %w", err)
|
||||||
|
}
|
||||||
|
time.Sleep(cardSend.UpdateInterval) // 控制更新频率
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateCardContent 封装卡片更新逻辑
|
||||||
|
func (s *SendCardClient) updateCardContent(ctx context.Context, cardSend *CardSend, cardInstanceId, content string, client *dingtalkim_1_0.Client) error {
|
||||||
|
err := s.updateCardRequest(ctx, &UpdateCardRequest{
|
||||||
|
Template: string(cardSend.Template),
|
||||||
|
Content: content,
|
||||||
|
Client: client,
|
||||||
|
RobotCode: cardSend.RobotCode,
|
||||||
|
CardInstanceId: cardInstanceId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendCardClient) updateCardRequest(ctx context.Context, updateCardRequest *UpdateCardRequest) error {
|
||||||
|
content, err := pkg.SafeReplace(updateCardRequest.Template, "%s", updateCardRequest.Content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateRequest := &dingtalkim_1_0.UpdateRobotInteractiveCardRequest{
|
||||||
|
CardBizId: tea.String(updateCardRequest.CardInstanceId),
|
||||||
|
CardData: tea.String(content),
|
||||||
|
}
|
||||||
|
_, err = s.UpdateInteractiveCard(ctx, updateRequest, updateCardRequest.RobotCode, updateCardRequest.Client)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInteractiveCard 更新交互卡片(封装错误处理)
|
||||||
|
func (s *SendCardClient) UpdateInteractiveCard(ctx context.Context, request *dingtalkim_1_0.UpdateRobotInteractiveCardRequest, robotCode string, client *dingtalkim_1_0.Client) (*dingtalkim_1_0.UpdateRobotInteractiveCardResponse, error) {
|
||||||
|
authInfo, err := s.Auth.GetTokenFromBotOption(ctx, WithBot(s.botOption))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get token failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := &dingtalkim_1_0.UpdateRobotInteractiveCardHeaders{
|
||||||
|
XAcsDingtalkAccessToken: tea.String(authInfo.AccessToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.UpdateRobotInteractiveCardWithOptions(request, headers, &util.RuntimeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("API call failed: %w,request:%v", err, request.String())
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInteractiveCard 发送交互卡片(封装错误处理)
|
||||||
|
func (s *SendCardClient) SendInteractiveCard(ctx context.Context, request *dingtalkim_1_0.SendRobotInteractiveCardRequest, robotCode string, client *dingtalkim_1_0.Client) (res *dingtalkim_1_0.SendRobotInteractiveCardResponse, err error) {
|
||||||
|
err = s.Auth.GetBotConfigFromModel(s.botOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("初始化bot失败: %w", err)
|
||||||
|
}
|
||||||
|
authInfo, err := s.Auth.GetTokenFromBotOption(ctx, WithBot(s.botOption))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get token failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := &dingtalkim_1_0.SendRobotInteractiveCardHeaders{
|
||||||
|
XAcsDingtalkAccessToken: tea.String(authInfo.AccessToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.SendRobotInteractiveCardWithOptions(request, headers, &util.RuntimeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("API call failed: %w", err)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
dingtalkcard_1_0 "github.com/alibabacloud-go/dingtalk/card_1_0"
|
||||||
|
dingtalkim_1_0 "github.com/alibabacloud-go/dingtalk/im_1_0"
|
||||||
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultInterval = 100 * time.Millisecond
|
||||||
|
const HeardBeatX = 50
|
||||||
|
|
||||||
|
type SendCardClient struct {
|
||||||
|
Auth *Auth
|
||||||
|
CardClient *sync.Map
|
||||||
|
mu sync.RWMutex // 保护 CardClient 的并发访问
|
||||||
|
logger log.AllLogger // 日志记录
|
||||||
|
botOption *Bot
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendCardClient(auth *Auth, logger log.AllLogger) *SendCardClient {
|
||||||
|
return &SendCardClient{
|
||||||
|
Auth: auth,
|
||||||
|
CardClient: &sync.Map{},
|
||||||
|
logger: logger,
|
||||||
|
botOption: &Bot{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initClient 初始化或复用 DingTalk 客户端
|
||||||
|
func (s *SendCardClient) initClient(robotCode string) (*dingtalkcard_1_0.Client, error) {
|
||||||
|
if client, ok := s.CardClient.Load(robotCode); ok {
|
||||||
|
return client.(*dingtalkcard_1_0.Client), nil
|
||||||
|
}
|
||||||
|
s.botOption.BotCode = robotCode
|
||||||
|
config := &openapi.Config{
|
||||||
|
Protocol: tea.String("https"),
|
||||||
|
RegionId: tea.String("central"),
|
||||||
|
}
|
||||||
|
client, err := dingtalkcard_1_0.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to init DingTalk client")
|
||||||
|
return nil, fmt.Errorf("init client failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.CardClient.Store(robotCode, client)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendCardClient) NewCard(ctx context.Context, cardSend *CardSend) error {
|
||||||
|
// 参数校验
|
||||||
|
if (len(cardSend.ContentSlice) == 0 || cardSend.ContentSlice == nil) && cardSend.ContentChannel == nil {
|
||||||
|
return errors.New("卡片内容不能为空")
|
||||||
|
}
|
||||||
|
if cardSend.UpdateInterval == 0 {
|
||||||
|
cardSend.UpdateInterval = DefaultInterval // 默认更新间隔
|
||||||
|
}
|
||||||
|
if cardSend.Title == "" {
|
||||||
|
cardSend.Title = "钉钉卡片"
|
||||||
|
}
|
||||||
|
//替换标题
|
||||||
|
cardSend.Template = constants.CardTemp(strings.Replace(string(cardSend.Template), "${title}", cardSend.Title, 1))
|
||||||
|
// 初始化客户端
|
||||||
|
client, err := s.initClient(cardSend.RobotCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化client失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成卡片实例ID
|
||||||
|
cardInstanceId, err := uuid.NewUUID()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建uuid失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建初始请求
|
||||||
|
request, err := s.buildBaseRequest(cardSend, cardInstanceId.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送初始卡片
|
||||||
|
if _, err := s.SendInteractiveCard(ctx, request, cardSend.RobotCode, client); err != nil {
|
||||||
|
return fmt.Errorf("发送初始卡片失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理切片内容(同步)
|
||||||
|
if len(cardSend.ContentSlice) > 0 {
|
||||||
|
if err := s.processContentSlice(ctx, cardSend, cardInstanceId.String(), client); err != nil {
|
||||||
|
return fmt.Errorf("内容同步失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理通道内容(异步)
|
||||||
|
if cardSend.ContentChannel != nil {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
s.processContentChannel(ctx, cardSend, cardInstanceId.String(), client)
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildBaseRequest 构建基础请求
|
||||||
|
func (s *SendCardClient) buildBaseRequest(cardSend *CardSend, cardInstanceId string) (*dingtalkcard_1_0.StreamingUpdateRequest, error) {
|
||||||
|
cardData := fmt.Sprintf(string(cardSend.Template), "") // 初始空内容
|
||||||
|
request := &dingtalkcard_1_0.StreamingUpdateRequest{
|
||||||
|
OutTrackId: tea.String("your-out-track-id"),
|
||||||
|
Guid: tea.String("0F714542-0AFC-2B0E-CF14-E2D39F5BFFE8"),
|
||||||
|
Key: tea.String("your-ai-param"),
|
||||||
|
Content: tea.String("test"),
|
||||||
|
IsFull: tea.Bool(false),
|
||||||
|
IsFinalize: tea.Bool(false),
|
||||||
|
IsError: tea.Bool(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cardSend.ConversationType {
|
||||||
|
case constants.ConversationTypeGroup:
|
||||||
|
request.SetOpenConversationId(cardSend.ConversationId)
|
||||||
|
case constants.ConversationTypeSingle:
|
||||||
|
receiver, err := json.Marshal(map[string]string{"userId": cardSend.SenderStaffId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("数据整理失败: %w", err)
|
||||||
|
}
|
||||||
|
request.SetSingleChatReceiver(string(receiver))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("未知的聊天场景")
|
||||||
|
}
|
||||||
|
|
||||||
|
return request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processContentChannel 处理通道内容(异步更新)
|
||||||
|
func (s *SendCardClient) processContentChannel(ctx context.Context, cardSend *CardSend, cardInstanceId string, client *dingtalkim_1_0.Client) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
s.logger.Error("panic in processContentChannel")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(cardSend.UpdateInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
heartbeatTicker := time.NewTicker(time.Duration(HeardBeatX) * DefaultInterval)
|
||||||
|
defer heartbeatTicker.Stop()
|
||||||
|
|
||||||
|
var (
|
||||||
|
contentBuilder strings.Builder
|
||||||
|
lastUpdate time.Time
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case content, ok := <-cardSend.ContentChannel:
|
||||||
|
if !ok {
|
||||||
|
// 通道关闭,发送最终内容
|
||||||
|
if contentBuilder.Len() > 0 {
|
||||||
|
if err := s.updateCardContent(ctx, cardSend, cardInstanceId, contentBuilder.String(), client); err != nil {
|
||||||
|
s.logger.Errorf("更新卡片失败1:%s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentBuilder.WriteString(content)
|
||||||
|
if contentBuilder.Len() > 0 {
|
||||||
|
if err := s.updateCardContent(ctx, cardSend, cardInstanceId, contentBuilder.String(), client); err != nil {
|
||||||
|
s.logger.Errorf("更新卡片失败2:%s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastUpdate = time.Now()
|
||||||
|
|
||||||
|
case <-heartbeatTicker.C:
|
||||||
|
if time.Now().Unix()-lastUpdate.Unix() >= HeardBeatX {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.logger.Info("context canceled, stop channel processing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// processContentSlice 处理切片内容(同步更新)
|
||||||
|
func (s *SendCardClient) processContentSlice(ctx context.Context, cardSend *CardSend, cardInstanceId string, client *dingtalkim_1_0.Client) error {
|
||||||
|
var contentBuilder strings.Builder
|
||||||
|
for _, content := range cardSend.ContentSlice {
|
||||||
|
contentBuilder.WriteString(content)
|
||||||
|
err := s.updateCardRequest(ctx, &UpdateCardRequest{
|
||||||
|
Template: string(cardSend.Template),
|
||||||
|
Content: contentBuilder.String(),
|
||||||
|
Client: client,
|
||||||
|
RobotCode: cardSend.RobotCode,
|
||||||
|
CardInstanceId: cardInstanceId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("更新卡片失败: %w", err)
|
||||||
|
}
|
||||||
|
time.Sleep(cardSend.UpdateInterval) // 控制更新频率
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateCardContent 封装卡片更新逻辑
|
||||||
|
func (s *SendCardClient) updateCardContent(ctx context.Context, cardSend *CardSend, cardInstanceId, content string, client *dingtalkim_1_0.Client) error {
|
||||||
|
err := s.updateCardRequest(ctx, &UpdateCardRequest{
|
||||||
|
Template: string(cardSend.Template),
|
||||||
|
Content: content,
|
||||||
|
Client: client,
|
||||||
|
RobotCode: cardSend.RobotCode,
|
||||||
|
CardInstanceId: cardInstanceId,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendCardClient) updateCardRequest(ctx context.Context, updateCardRequest *UpdateCardRequest) error {
|
||||||
|
|
||||||
|
updateRequest := &dingtalkim_1_0.UpdateRobotInteractiveCardRequest{
|
||||||
|
CardBizId: tea.String(updateCardRequest.CardInstanceId),
|
||||||
|
CardData: tea.String(fmt.Sprintf(updateCardRequest.Template, updateCardRequest.Content)),
|
||||||
|
}
|
||||||
|
_, err := s.UpdateInteractiveCard(ctx, updateRequest, updateCardRequest.RobotCode, updateCardRequest.Client)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInteractiveCard 更新交互卡片(封装错误处理)
|
||||||
|
func (s *SendCardClient) UpdateInteractiveCard(ctx context.Context, request *dingtalkim_1_0.UpdateRobotInteractiveCardRequest, robotCode string, client *dingtalkim_1_0.Client) (*dingtalkim_1_0.UpdateRobotInteractiveCardResponse, error) {
|
||||||
|
authInfo, err := s.Auth.GetTokenFromBotOption(ctx, WithBot(s.botOption))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get token failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := &dingtalkim_1_0.UpdateRobotInteractiveCardHeaders{
|
||||||
|
XAcsDingtalkAccessToken: tea.String(authInfo.AccessToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.UpdateRobotInteractiveCardWithOptions(request, headers, &util.RuntimeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("API call failed: %w,request:%v", err, request.String())
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInteractiveCard 发送交互卡片(封装错误处理)
|
||||||
|
func (s *SendCardClient) SendInteractiveCard(ctx context.Context, request *dingtalkim_1_0.SendRobotInteractiveCardRequest, robotCode string, client *dingtalkim_1_0.Client) (res *dingtalkim_1_0.SendRobotInteractiveCardResponse, err error) {
|
||||||
|
err = s.Auth.GetBotConfigFromModel(s.botOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("初始化bot失败: %w", err)
|
||||||
|
}
|
||||||
|
authInfo, err := s.Auth.GetTokenFromBotOption(ctx, WithBot(s.botOption))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get token failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := &dingtalkim_1_0.SendRobotInteractiveCardHeaders{
|
||||||
|
XAcsDingtalkAccessToken: tea.String(authInfo.AccessToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.SendRobotInteractiveCardWithOptions(request, headers, &util.RuntimeOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("API call failed: %w", err)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dingtalkim_1_0 "github.com/alibabacloud-go/dingtalk/im_1_0"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DingTalkAuthIRes struct {
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
ExpireIn int64 `json:"expireIn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoRes struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Result UserInfoResResult `json:"result"`
|
||||||
|
RequestId string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResResult struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Boss bool `json:"boss"`
|
||||||
|
CreateTime time.Time `json:"create_time"`
|
||||||
|
DeptIdList []int `json:"dept_id_list"`
|
||||||
|
DeptOrderList []struct {
|
||||||
|
DeptId int `json:"dept_id"`
|
||||||
|
Order int64 `json:"order"`
|
||||||
|
} `json:"dept_order_list"`
|
||||||
|
ExclusiveAccount bool `json:"exclusive_account"`
|
||||||
|
HideMobile bool `json:"hide_mobile"`
|
||||||
|
HiredDate int64 `json:"hired_date"`
|
||||||
|
JobNumber string `json:"job_number"`
|
||||||
|
LeaderInDept []struct {
|
||||||
|
DeptId int `json:"dept_id"`
|
||||||
|
Leader bool `json:"leader"`
|
||||||
|
} `json:"leader_in_dept"`
|
||||||
|
ManagerUserid string `json:"manager_userid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RealAuthed bool `json:"real_authed"`
|
||||||
|
RoleList []struct {
|
||||||
|
GroupName string `json:"group_name"`
|
||||||
|
Id int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"role_list"`
|
||||||
|
Senior bool `json:"senior"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Unionid string `json:"unionid"`
|
||||||
|
Userid string `json:"userid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeptRes struct {
|
||||||
|
Errcode int `json:"errcode"`
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
DeptResResult DeptResResult `json:"result"`
|
||||||
|
RequestId string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeptResResult struct {
|
||||||
|
DeptPermits []int `json:"dept_permits"`
|
||||||
|
OuterPermitUsers []string `json:"outer_permit_users"`
|
||||||
|
DeptManagerUseridList []string `json:"dept_manager_userid_list"`
|
||||||
|
OrgDeptOwner string `json:"org_dept_owner"`
|
||||||
|
OuterDept bool `json:"outer_dept"`
|
||||||
|
DeptGroupChatId string `json:"dept_group_chat_id"`
|
||||||
|
GroupContainSubDept bool `json:"group_contain_sub_dept"`
|
||||||
|
AutoAddUser bool `json:"auto_add_user"`
|
||||||
|
HideDept bool `json:"hide_dept"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OuterPermitDepts []int `json:"outer_permit_depts"`
|
||||||
|
UserPermits []interface{} `json:"user_permits"`
|
||||||
|
DeptId int `json:"dept_id"`
|
||||||
|
CreateDeptGroup bool `json:"create_dept_group"`
|
||||||
|
Order int `json:"order"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
UnionDeptExt struct {
|
||||||
|
CorpId string `json:"corp_id"`
|
||||||
|
DeptId int `json:"dept_id"`
|
||||||
|
} `json:"union_dept_ext"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthInfo struct {
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
Expire time.Duration `json:"expireIn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CardSend struct {
|
||||||
|
RobotCode string
|
||||||
|
ConversationType constants.ConversationType
|
||||||
|
ConversationId string
|
||||||
|
Template constants.CardTemp
|
||||||
|
SenderStaffId string
|
||||||
|
Title string
|
||||||
|
ContentSlice []string
|
||||||
|
ContentChannel chan string
|
||||||
|
UpdateInterval time.Duration // 控制通道更新的频率
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCardRequest struct {
|
||||||
|
Template string
|
||||||
|
Content string
|
||||||
|
Client *dingtalkim_1_0.Client
|
||||||
|
RobotCode string
|
||||||
|
CardInstanceId string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
dingUserImpl *impl.BotUserImpl
|
||||||
|
botConfigImpl *impl.BotConfigImpl
|
||||||
|
auth *Auth
|
||||||
|
dept *Dept
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUser(
|
||||||
|
dingUserImpl *impl.BotUserImpl,
|
||||||
|
auth *Auth,
|
||||||
|
dept *Dept,
|
||||||
|
) *User {
|
||||||
|
return &User{
|
||||||
|
dingUserImpl: dingUserImpl,
|
||||||
|
auth: auth,
|
||||||
|
dept: dept,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetUserInfoFromBot(ctx context.Context, staffId string, botOption ...BotOption) (userInfo *entitys.DingTalkUserInfo, err error) {
|
||||||
|
if len(staffId) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, err := u.dingUserImpl.GetByStaffId(staffId)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//待优化
|
||||||
|
authInfo, err := u.auth.GetTokenFromBotOption(ctx, botOption...)
|
||||||
|
if err != nil || authInfo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//如果没有找到,则新增
|
||||||
|
if user == nil {
|
||||||
|
|
||||||
|
DingUserInfo, _err := u.getUserInfoFromDingTalk(ctx, authInfo.AccessToken, staffId)
|
||||||
|
if _err != nil {
|
||||||
|
return nil, _err
|
||||||
|
}
|
||||||
|
user = &model.AiBotUser{
|
||||||
|
StaffID: DingUserInfo.Userid,
|
||||||
|
Name: DingUserInfo.Name,
|
||||||
|
Title: DingUserInfo.Title,
|
||||||
|
//Extension: DingUserInfo.Extension,
|
||||||
|
DeptIDList: strings.Join(pkg.SliceIntToString(DingUserInfo.DeptIdList), ","),
|
||||||
|
IsBoss: int32(pkg.Ter(DingUserInfo.Boss, constants.IsBossTrue, constants.IsBossFalse)),
|
||||||
|
IsSenior: int32(pkg.Ter(DingUserInfo.Senior, constants.IsSeniorTrue, constants.IsSeniorFalse)),
|
||||||
|
HiredDate: time.UnixMilli(DingUserInfo.HiredDate),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = u.dingUserImpl.Add(user)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userInfo = &entitys.DingTalkUserInfo{
|
||||||
|
UserId: int(user.UserID),
|
||||||
|
StaffId: user.StaffID,
|
||||||
|
Name: user.Name,
|
||||||
|
IsBoss: constants.IsBoss(user.IsBoss),
|
||||||
|
IsSenior: constants.IsSenior(user.IsSenior),
|
||||||
|
HiredDate: user.HiredDate,
|
||||||
|
Extension: user.Extension,
|
||||||
|
}
|
||||||
|
if len(user.DeptIDList) > 0 {
|
||||||
|
deptIdList := pkg.SliceStringToInt(strings.Split(user.DeptIDList, ","))
|
||||||
|
depts, _err := u.dept.GetDeptInfoByDeptIds(ctx, deptIdList, authInfo)
|
||||||
|
if _err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, dept := range depts {
|
||||||
|
userInfo.Dept = append(userInfo.Dept, dept)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) getUserInfoFromDingTalk(ctx context.Context, token string, staffId string) (user UserInfoResResult, err error) {
|
||||||
|
if token == "" && staffId == "" {
|
||||||
|
err = errors.New("获取钉钉用户信息的必要参数不足")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Url: constants.GetDingTalkRequestUrl(constants.RequestUrlGetUserGet, map[string]string{
|
||||||
|
"access_token": token,
|
||||||
|
}),
|
||||||
|
Data: map[string]string{
|
||||||
|
"userid": staffId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var userInfoRes UserInfoRes
|
||||||
|
err = json.Unmarshal(res.Content, &userInfoRes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfoRes.Errcode != 0 {
|
||||||
|
fmt.Errorf("钉钉请求报错:%s", userInfoRes.Errmsg)
|
||||||
|
}
|
||||||
|
return userInfoRes.Result, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
package handle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/entitys"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gabriel-vasile/mimetype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleRecognizeFile 这里的目的是无论将什么类型的file都转为二进制格式
|
||||||
|
// 最终输出:1.将 files.FileData 填充为文件的二进制数据 2.将 files.FileType 填充为文件的类型(当前为 constants.Caller,兼容写入其字符串值)
|
||||||
|
// 判断文件大小(统一限制为10MB);判断文件类型;判断文件是否合法(类型在白名单映射中);无法识别/非法/超限→填充unknown并兼容返回
|
||||||
|
// 若 FileData 不存在 且 FileUrl 不存在, 则直接退出
|
||||||
|
// 若 FileData 存在 FileType 存在, 则直接退出
|
||||||
|
// 若 FileData 存在 FileType 不存在, 则根据 FileData 推断文件类型并填充 FileType
|
||||||
|
// 若 FileUrl 存在, 则下载文件并填充 FileData 和 FileType
|
||||||
|
func HandleRecognizeFile(files *entitys.RecognizeFile) {
|
||||||
|
if files == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxSize = 10 * 1024 * 1024 // 10MB 上限
|
||||||
|
|
||||||
|
// 工具:根据 MIME 或扩展名映射到 FileType
|
||||||
|
mapToFileType := func(s string) constants.FileType {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return constants.FileTypeUnknown
|
||||||
|
}
|
||||||
|
s = strings.ToLower(strings.TrimSpace(s))
|
||||||
|
for ft, items := range constants.FileTypeMappings {
|
||||||
|
for _, item := range items {
|
||||||
|
if !strings.HasPrefix(item, ".") { // MIME
|
||||||
|
if s == item {
|
||||||
|
return ft
|
||||||
|
}
|
||||||
|
} else { // 扩展名
|
||||||
|
if s == item {
|
||||||
|
return ft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constants.FileTypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支1:无数据、无URL→直接返回
|
||||||
|
if len(files.FileData) == 0 && len(files.FileUrl) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支2:已有数据且已有类型→直接返回
|
||||||
|
if len(files.FileData) > 0 && len(strings.TrimSpace(files.FileType.String())) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支3:仅有数据、无类型→内容检测并填充
|
||||||
|
if len(files.FileData) > 0 && len(strings.TrimSpace(files.FileType.String())) == 0 {
|
||||||
|
if len(files.FileData) > maxSize {
|
||||||
|
files.FileType = constants.FileTypeUnknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader(files.FileData)
|
||||||
|
detected, fileRealMime := detectFileType(reader, "")
|
||||||
|
files.FileType = detected
|
||||||
|
files.FileRealMime = fileRealMime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分支4:存在URL→下载并填充数据与类型
|
||||||
|
if len(files.FileUrl) > 0 {
|
||||||
|
fileBytes, contentType, err := downloadFile(files.FileUrl)
|
||||||
|
if err != nil || len(fileBytes) == 0 {
|
||||||
|
files.FileType = constants.FileTypeUnknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fileBytes) > maxSize {
|
||||||
|
// 超限:不写入数据,类型置 unknown
|
||||||
|
files.FileType = constants.FileTypeUnknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先使用响应头的 Content-Type 映射
|
||||||
|
detected := mapToFileType(contentType)
|
||||||
|
fileRealMime := contentType
|
||||||
|
|
||||||
|
if detected == constants.FileTypeUnknown {
|
||||||
|
// 回退:内容检测 + URL 文件名扩展名辅助
|
||||||
|
var fname string
|
||||||
|
if u, perr := url.Parse(files.FileUrl); perr == nil {
|
||||||
|
fname = filepath.Base(u.Path)
|
||||||
|
}
|
||||||
|
reader := bytes.NewReader(fileBytes)
|
||||||
|
detected, fileRealMime = detectFileType(reader, fname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
files.FileData = fileBytes
|
||||||
|
files.FileType = detected
|
||||||
|
files.FileRealMime = fileRealMime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载文件并返回二进制数据、MIME 类型
|
||||||
|
func downloadFile(fileUrl string) (fileBytes []byte, contentType string, err error) {
|
||||||
|
if len(fileUrl) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Url: fileUrl,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||||
|
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ex bool
|
||||||
|
if contentType, ex = res.Headers["Content-Type"]; !ex {
|
||||||
|
err = errors.New("Content-Type不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("server returned non-200 status: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
fileBytes = res.Content
|
||||||
|
|
||||||
|
return fileBytes, contentType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectFileType 判断文件类型
|
||||||
|
func detectFileType(file io.ReadSeeker, filename string) (constants.FileType, string) {
|
||||||
|
// 1. 读取文件头检测 MIME
|
||||||
|
buffer := make([]byte, 512)
|
||||||
|
n, _ := file.Read(buffer)
|
||||||
|
file.Seek(0, io.SeekStart) // 重置读取位置
|
||||||
|
|
||||||
|
detectedMIME := mimetype.Detect(buffer[:n]).String()
|
||||||
|
for fileType, items := range constants.FileTypeMappings {
|
||||||
|
for _, item := range items {
|
||||||
|
if !strings.HasPrefix(item, ".") && item == detectedMIME {
|
||||||
|
return fileType, detectedMIME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 备用:通过扩展名检测
|
||||||
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
for fileType, items := range constants.FileTypeMappings {
|
||||||
|
for _, item := range items {
|
||||||
|
if strings.HasPrefix(item, ".") && item == ext {
|
||||||
|
return fileType, ext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return constants.FileTypeUnknown, ""
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package handle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
"ai_scheduler/internal/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handle struct {
|
|
||||||
toolManager *tools.Manager
|
|
||||||
conf *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHandle(
|
|
||||||
toolManager *tools.Manager,
|
|
||||||
conf *config.Config,
|
|
||||||
|
|
||||||
) *Handle {
|
|
||||||
return &Handle{
|
|
||||||
toolManager: toolManager,
|
|
||||||
conf: conf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package llm_service
|
package llm_service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/data/constants"
|
|
||||||
"ai_scheduler/internal/data/model"
|
"ai_scheduler/internal/data/model"
|
||||||
"ai_scheduler/internal/entitys"
|
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -20,48 +18,3 @@ func buildSystemPrompt(prompt string) string {
|
||||||
|
|
||||||
return prompt
|
return prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAssistant(his []model.AiChatHi) (chatHis entitys.ChatHis) {
|
|
||||||
for _, item := range his {
|
|
||||||
if len(chatHis.SessionId) == 0 {
|
|
||||||
chatHis.SessionId = item.SessionID
|
|
||||||
}
|
|
||||||
chatHis.Messages = append(chatHis.Messages, []entitys.HisMessage{
|
|
||||||
{
|
|
||||||
Role: constants.RoleUser,
|
|
||||||
Content: item.Ques,
|
|
||||||
Timestamp: item.CreateAt.Format(time.DateTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: constants.RoleAssistant,
|
|
||||||
Content: item.Ans,
|
|
||||||
Timestamp: item.CreateAt.Format(time.DateTime),
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
chatHis.Context = entitys.HisContext{
|
|
||||||
UserLanguage: "zh-CN",
|
|
||||||
SystemMode: "technical_support",
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildChatHisMessage(his []model.AiChatHi) (chatHis []entitys.HisMessage) {
|
|
||||||
for _, item := range his {
|
|
||||||
|
|
||||||
chatHis = append(chatHis, []entitys.HisMessage{
|
|
||||||
{
|
|
||||||
Role: constants.RoleUser,
|
|
||||||
Content: item.Ques,
|
|
||||||
Timestamp: item.CreateAt.Format(time.DateTime),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Role: constants.RoleAssistant,
|
|
||||||
Content: item.Ans,
|
|
||||||
Timestamp: item.CreateAt.Format(time.DateTime),
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,76 @@
|
||||||
package llm_service
|
package llm_service
|
||||||
|
|
||||||
import (
|
//type LangChainService struct {
|
||||||
"ai_scheduler/internal/data/model"
|
// client *utils_langchain.UtilLangChain
|
||||||
"ai_scheduler/internal/entitys"
|
//}
|
||||||
"ai_scheduler/internal/pkg"
|
//
|
||||||
"ai_scheduler/internal/pkg/utils_langchain"
|
//func NewLangChainGenerate(
|
||||||
"context"
|
// client *utils_langchain.UtilLangChain,
|
||||||
"encoding/json"
|
//) *LangChainService {
|
||||||
|
//
|
||||||
"github.com/tmc/langchaingo/llms"
|
// return &LangChainService{
|
||||||
)
|
// client: client,
|
||||||
|
// }
|
||||||
type LangChainService struct {
|
//}
|
||||||
client *utils_langchain.UtilLangChain
|
//
|
||||||
}
|
//func (r *LangChainService) IntentRecognize(ctx context.Context, sysInfo model.AiSy, history []model.AiChatHi, userInput string, tasks []model.AiTask) (msg string, err error) {
|
||||||
|
// prompt := r.getPrompt(sysInfo, history, userInput, tasks)
|
||||||
func NewLangChainGenerate(
|
// AgentClient := r.client.Get()
|
||||||
client *utils_langchain.UtilLangChain,
|
// defer r.client.Put(AgentClient)
|
||||||
) *LangChainService {
|
// match, err := AgentClient.Llm.GenerateContent(
|
||||||
|
// ctx, // 使用可取消的上下文
|
||||||
return &LangChainService{
|
// prompt,
|
||||||
client: client,
|
// llms.WithJSONMode(),
|
||||||
}
|
// )
|
||||||
}
|
// msg = match.Choices[0].Content
|
||||||
|
// return
|
||||||
func (r *LangChainService) IntentRecognize(ctx context.Context, sysInfo model.AiSy, history []model.AiChatHi, userInput string, tasks []model.AiTask) (msg string, err error) {
|
//}
|
||||||
prompt := r.getPrompt(sysInfo, history, userInput, tasks)
|
//
|
||||||
AgentClient := r.client.Get()
|
//func (r *LangChainService) getPrompt(sysInfo model.AiSy, history []model.AiChatHi, reqInput string, tasks []model.AiTask) []llms.MessageContent {
|
||||||
defer r.client.Put(AgentClient)
|
// var (
|
||||||
match, err := AgentClient.Llm.GenerateContent(
|
// prompt = make([]llms.MessageContent, 0)
|
||||||
ctx, // 使用可取消的上下文
|
// )
|
||||||
prompt,
|
// prompt = append(prompt, llms.MessageContent{
|
||||||
llms.WithJSONMode(),
|
// Role: llms.ChatMessageTypeSystem,
|
||||||
)
|
// Parts: []llms.ContentPart{
|
||||||
msg = match.Choices[0].Content
|
// llms.TextPart(buildSystemPrompt(sysInfo.SysPrompt)),
|
||||||
return
|
// },
|
||||||
}
|
// }, llms.MessageContent{
|
||||||
|
// Role: llms.ChatMessageTypeTool,
|
||||||
func (r *LangChainService) getPrompt(sysInfo model.AiSy, history []model.AiChatHi, reqInput string, tasks []model.AiTask) []llms.MessageContent {
|
// Parts: []llms.ContentPart{
|
||||||
var (
|
// llms.TextPart(pkg.JsonStringIgonErr(buildAssistant(history))),
|
||||||
prompt = make([]llms.MessageContent, 0)
|
// },
|
||||||
)
|
// }, llms.MessageContent{
|
||||||
prompt = append(prompt, llms.MessageContent{
|
// Role: llms.ChatMessageTypeTool,
|
||||||
Role: llms.ChatMessageTypeSystem,
|
// Parts: []llms.ContentPart{
|
||||||
Parts: []llms.ContentPart{
|
// llms.TextPart(pkg.JsonStringIgonErr(r.registerTools(tasks))),
|
||||||
llms.TextPart(buildSystemPrompt(sysInfo.SysPrompt)),
|
// },
|
||||||
},
|
// }, llms.MessageContent{
|
||||||
}, llms.MessageContent{
|
// Role: llms.ChatMessageTypeHuman,
|
||||||
Role: llms.ChatMessageTypeTool,
|
// Parts: []llms.ContentPart{
|
||||||
Parts: []llms.ContentPart{
|
// llms.TextPart(reqInput),
|
||||||
llms.TextPart(pkg.JsonStringIgonErr(buildAssistant(history))),
|
// },
|
||||||
},
|
// })
|
||||||
}, llms.MessageContent{
|
// return prompt
|
||||||
Role: llms.ChatMessageTypeTool,
|
//}
|
||||||
Parts: []llms.ContentPart{
|
//
|
||||||
llms.TextPart(pkg.JsonStringIgonErr(r.registerTools(tasks))),
|
//func (r *LangChainService) registerTools(tasks []model.AiTask) []llms.Tool {
|
||||||
},
|
// taskPrompt := make([]llms.Tool, 0)
|
||||||
}, llms.MessageContent{
|
// for _, task := range tasks {
|
||||||
Role: llms.ChatMessageTypeHuman,
|
// var taskConfig entitys.TaskConfig
|
||||||
Parts: []llms.ContentPart{
|
// err := json.Unmarshal([]byte(task.Config), &taskConfig)
|
||||||
llms.TextPart(reqInput),
|
// if err != nil {
|
||||||
},
|
// continue
|
||||||
})
|
// }
|
||||||
return prompt
|
// taskPrompt = append(taskPrompt, llms.Tool{
|
||||||
}
|
// Type: "function",
|
||||||
|
// Function: &llms.FunctionDefinition{
|
||||||
func (r *LangChainService) registerTools(tasks []model.AiTask) []llms.Tool {
|
// Name: task.Index,
|
||||||
taskPrompt := make([]llms.Tool, 0)
|
// Description: task.Desc,
|
||||||
for _, task := range tasks {
|
// Parameters: taskConfig.Param,
|
||||||
var taskConfig entitys.TaskConfig
|
// },
|
||||||
err := json.Unmarshal([]byte(task.Config), &taskConfig)
|
// })
|
||||||
if err != nil {
|
//
|
||||||
continue
|
// }
|
||||||
}
|
// return taskPrompt
|
||||||
taskPrompt = append(taskPrompt, llms.Tool{
|
//}
|
||||||
Type: "function",
|
|
||||||
Function: &llms.FunctionDefinition{
|
|
||||||
Name: task.Index,
|
|
||||||
Description: task.Desc,
|
|
||||||
Parameters: taskConfig.Param,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
return taskPrompt
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,46 +3,42 @@ package llm_service
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
"ai_scheduler/internal/config"
|
||||||
"ai_scheduler/internal/data/impl"
|
"ai_scheduler/internal/data/impl"
|
||||||
"ai_scheduler/internal/data/model"
|
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
"ai_scheduler/internal/pkg"
|
"ai_scheduler/internal/pkg"
|
||||||
"ai_scheduler/internal/pkg/utils_ollama"
|
"ai_scheduler/internal/pkg/utils_ollama"
|
||||||
|
"ai_scheduler/internal/pkg/utils_vllm"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ollama/ollama/api"
|
"github.com/ollama/ollama/api"
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OllamaService struct {
|
type OllamaService struct {
|
||||||
client *utils_ollama.Client
|
client *utils_ollama.Client
|
||||||
|
vllmClient *utils_vllm.Client
|
||||||
config *config.Config
|
config *config.Config
|
||||||
chatHis *impl.ChatImpl
|
chatHis *impl.ChatHisImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOllamaGenerate(
|
func NewOllamaGenerate(
|
||||||
client *utils_ollama.Client,
|
client *utils_ollama.Client,
|
||||||
|
vllmClient *utils_vllm.Client,
|
||||||
config *config.Config,
|
config *config.Config,
|
||||||
chatHis *impl.ChatImpl,
|
chatHis *impl.ChatHisImpl,
|
||||||
) *OllamaService {
|
) *OllamaService {
|
||||||
return &OllamaService{
|
return &OllamaService{
|
||||||
client: client,
|
client: client,
|
||||||
|
vllmClient: vllmClient,
|
||||||
config: config,
|
config: config,
|
||||||
chatHis: chatHis,
|
chatHis: chatHis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OllamaService) IntentRecognize(ctx context.Context, requireData *entitys.RequireData) (msg string, err error) {
|
func (r *OllamaService) IntentRecognize(ctx context.Context, req *entitys.ToolSelect) (msg string, err error) {
|
||||||
prompt, err := r.getPrompt(ctx, requireData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
toolDefinitions := r.registerToolsOllama(requireData.Tasks)
|
|
||||||
|
|
||||||
match, err := r.client.ToolSelect(ctx, prompt, toolDefinitions)
|
toolDefinitions := r.registerToolsOllama(req.Tools)
|
||||||
|
match, err := r.client.ToolSelect(ctx, req.Prompt, toolDefinitions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -66,105 +62,63 @@ func (r *OllamaService) IntentRecognize(ctx context.Context, requireData *entity
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *OllamaService) getPrompt(ctx context.Context, requireData *entitys.RequireData) ([]api.Message, error) {
|
//func (r *OllamaService) RecognizeWithImg(ctx context.Context, imgByte []api.ImageData, ch chan entitys.Response) (desc api.GenerateResponse, err error) {
|
||||||
|
// if imgByte == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// entitys.ResLog(requireData.Ch, "recognize_img_start", "图片识别中...")
|
||||||
|
//
|
||||||
|
// desc, err = r.client.Generation(ctx, &api.GenerateRequest{
|
||||||
|
// Model: r.config.Ollama.VlModel,
|
||||||
|
// Stream: new(bool),
|
||||||
|
// System: r.config.DefaultPrompt.ImgRecognize.SystemPrompt,
|
||||||
|
// Prompt: r.config.DefaultPrompt.ImgRecognize.UserPrompt,
|
||||||
|
// Images: requireData.ImgByte,
|
||||||
|
// KeepAlive: &api.Duration{Duration: 3600 * time.Second},
|
||||||
|
// //Think: &api.ThinkValue{Value: false},
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// entitys.ResLog(requireData.Ch, "recognize_img_end", "图片识别完成,识别内容:"+desc.Response)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
var (
|
//func (r *OllamaService) RecognizeWithImgVllm(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) {
|
||||||
prompt = make([]api.Message, 0)
|
// if requireData.ImgByte == nil {
|
||||||
)
|
// return
|
||||||
content, err := r.getUserContent(ctx, requireData)
|
// }
|
||||||
if err != nil {
|
// entitys.ResLog(requireData.Ch, "recognize_img_start", "图片识别中...")
|
||||||
return nil, err
|
//
|
||||||
}
|
// outMsg, err := r.vllmClient.RecognizeWithImg(ctx,
|
||||||
prompt = append(prompt, api.Message{
|
// r.config.DefaultPrompt.ImgRecognize.SystemPrompt,
|
||||||
Role: "system",
|
// r.config.DefaultPrompt.ImgRecognize.UserPrompt,
|
||||||
Content: buildSystemPrompt(requireData.Sys.SysPrompt),
|
// requireData.ImgUrls,
|
||||||
}, api.Message{
|
// )
|
||||||
Role: "assistant",
|
// if err != nil {
|
||||||
Content: "### 聊天记录:" + pkg.JsonStringIgonErr(buildAssistant(requireData.Histories)),
|
// return api.GenerateResponse{}, err
|
||||||
}, api.Message{
|
// }
|
||||||
Role: "user",
|
//
|
||||||
Content: content,
|
// desc = api.GenerateResponse{
|
||||||
})
|
// Response: outMsg.Content,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// entitys.ResLog(requireData.Ch, "recognize_img_end", "图片识别完成,识别内容:"+desc.Response)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
return prompt, nil
|
func (r *OllamaService) registerToolsOllama(tasks []entitys.RegistrationTask) []api.Tool {
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OllamaService) getUserContent(ctx context.Context, requireData *entitys.RequireData) (string, error) {
|
|
||||||
var content strings.Builder
|
|
||||||
content.WriteString(requireData.Req.Text)
|
|
||||||
if len(requireData.ImgByte) > 0 {
|
|
||||||
content.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(requireData.Req.Tags) > 0 {
|
|
||||||
content.WriteString("\n")
|
|
||||||
content.WriteString("### 工具必须使用:")
|
|
||||||
content.WriteString(requireData.Req.Tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(requireData.ImgByte) > 0 {
|
|
||||||
desc, err := r.RecognizeWithImg(ctx, requireData)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
content.WriteString("### 上传图片解析内容:\n")
|
|
||||||
content.WriteString(requireData.Req.Tags)
|
|
||||||
content.WriteString(desc.Response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if requireData.Req.MarkHis > 0 {
|
|
||||||
var his model.AiChatHi
|
|
||||||
cond := builder.NewCond()
|
|
||||||
cond = cond.And(builder.Eq{"his_id": requireData.Req.MarkHis})
|
|
||||||
err := r.chatHis.GetOneBySearchToStrut(&cond, &his)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
content.WriteString("### 引用历史聊天记录:\n")
|
|
||||||
content.WriteString(pkg.JsonStringIgonErr(BuildChatHisMessage([]model.AiChatHi{his})))
|
|
||||||
}
|
|
||||||
return content.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OllamaService) RecognizeWithImg(ctx context.Context, requireData *entitys.RequireData) (desc api.GenerateResponse, err error) {
|
|
||||||
if requireData.ImgByte == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entitys.ResLog(requireData.Ch, "recognize_img_start", "图片识别中...")
|
|
||||||
|
|
||||||
desc, err = r.client.Generation(ctx, &api.GenerateRequest{
|
|
||||||
Model: r.config.Ollama.VlModel,
|
|
||||||
Stream: new(bool),
|
|
||||||
System: r.config.DefaultPrompt.ImgRecognize.SystemPrompt,
|
|
||||||
Prompt: r.config.DefaultPrompt.ImgRecognize.UserPrompt,
|
|
||||||
Images: requireData.ImgByte,
|
|
||||||
KeepAlive: &api.Duration{Duration: 3600 * time.Second},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entitys.ResLog(requireData.Ch, "recognize_img_end", "图片识别完成,识别内容:"+desc.Response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OllamaService) registerToolsOllama(tasks []model.AiTask) []api.Tool {
|
|
||||||
taskPrompt := make([]api.Tool, 0)
|
taskPrompt := make([]api.Tool, 0)
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
var taskConfig entitys.TaskConfigDetail
|
|
||||||
err := json.Unmarshal([]byte(task.Config), &taskConfig)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
taskPrompt = append(taskPrompt, api.Tool{
|
taskPrompt = append(taskPrompt, api.Tool{
|
||||||
Type: "function",
|
Type: "function",
|
||||||
Function: api.ToolFunction{
|
Function: api.ToolFunction{
|
||||||
Name: task.Index,
|
Name: task.Name,
|
||||||
Description: task.Desc,
|
Description: task.Desc,
|
||||||
Parameters: api.ToolFunctionParameters{
|
Parameters: api.ToolFunctionParameters{
|
||||||
Type: taskConfig.Param.Type,
|
Type: task.TaskConfigDetail.Param.Type,
|
||||||
Required: taskConfig.Param.Required,
|
Required: task.TaskConfigDetail.Param.Required,
|
||||||
Properties: taskConfig.Param.Properties,
|
Properties: task.TaskConfigDetail.Param.Properties,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/biz/do"
|
"ai_scheduler/internal/biz/do"
|
||||||
"ai_scheduler/internal/biz/handle"
|
|
||||||
"ai_scheduler/internal/biz/llm_service"
|
"ai_scheduler/internal/biz/llm_service"
|
||||||
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
|
|
@ -12,10 +11,11 @@ var ProviderSetBiz = wire.NewSet(
|
||||||
NewAiRouterBiz,
|
NewAiRouterBiz,
|
||||||
NewSessionBiz,
|
NewSessionBiz,
|
||||||
NewChatHistoryBiz,
|
NewChatHistoryBiz,
|
||||||
llm_service.NewLangChainGenerate,
|
//llm_service.NewLangChainGenerate,
|
||||||
llm_service.NewOllamaGenerate,
|
llm_service.NewOllamaGenerate,
|
||||||
handle.NewHandle,
|
//handle.NewHandle,
|
||||||
do.NewDo,
|
do.NewDo,
|
||||||
do.NewHandle,
|
do.NewHandle,
|
||||||
NewTaskBiz,
|
NewTaskBiz,
|
||||||
|
NewDingTalkBotBiz,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,17 @@ package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/biz/do"
|
"ai_scheduler/internal/biz/do"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
errors "ai_scheduler/internal/data/error"
|
||||||
"ai_scheduler/internal/gateway"
|
"ai_scheduler/internal/gateway"
|
||||||
|
|
||||||
|
"ai_scheduler/internal/pkg/rec_extra"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"ai_scheduler/internal/entitys"
|
"ai_scheduler/internal/entitys"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2/log"
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
|
@ -13,16 +22,19 @@ import (
|
||||||
type AiRouterBiz struct {
|
type AiRouterBiz struct {
|
||||||
do *do.Do
|
do *do.Do
|
||||||
handle *do.Handle
|
handle *do.Handle
|
||||||
|
config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAiRouterBiz 创建路由服务
|
// NewAiRouterBiz 创建路由服务
|
||||||
func NewAiRouterBiz(
|
func NewAiRouterBiz(
|
||||||
do *do.Do,
|
do *do.Do,
|
||||||
handle *do.Handle,
|
handle *do.Handle,
|
||||||
|
config *config.Config,
|
||||||
) *AiRouterBiz {
|
) *AiRouterBiz {
|
||||||
return &AiRouterBiz{
|
return &AiRouterBiz{
|
||||||
do: do,
|
do: do,
|
||||||
handle: handle,
|
handle: handle,
|
||||||
|
config: config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,11 +51,9 @@ func (r *AiRouterBiz) RouteWithSocket(client *gateway.Client, req *entitys.ChatS
|
||||||
requireData := &entitys.RequireData{
|
requireData := &entitys.RequireData{
|
||||||
Req: req,
|
Req: req,
|
||||||
}
|
}
|
||||||
// 获取WebSocket连接
|
|
||||||
conn := client.GetConn()
|
|
||||||
|
|
||||||
//初始化通道/上下文
|
//初始化通道/上下文
|
||||||
ctx, clearFunc := r.do.MakeCh(conn, requireData)
|
ctx, clearFunc := r.do.MakeCh(client, requireData)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
entitys.ResError(requireData.Ch, "", err.Error())
|
entitys.ResError(requireData.Ch, "", err.Error())
|
||||||
|
|
@ -56,17 +66,133 @@ func (r *AiRouterBiz) RouteWithSocket(client *gateway.Client, req *entitys.ChatS
|
||||||
log.Errorf("数据验证和收集失败: %s", err.Error())
|
log.Errorf("数据验证和收集失败: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//组装意图识别
|
||||||
|
rec, sys, err := r.SetRec(ctx, requireData)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("组装意图识别失败: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
//意图识别
|
//意图识别
|
||||||
if err = r.handle.Recognize(ctx, requireData); err != nil {
|
|
||||||
|
err = r.handle.Recognize(ctx, &rec, sys)
|
||||||
|
if err != nil {
|
||||||
log.Errorf("意图识别失败: %s", err.Error())
|
log.Errorf("意图识别失败: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//任务处理
|
||||||
|
rec_extra.SetTaskRecExt(requireData, &rec)
|
||||||
//向下传递
|
//向下传递
|
||||||
if err = r.handle.HandleMatch(ctx, client, requireData); err != nil {
|
if err = r.handle.HandleMatch(ctx, client, &rec, requireData); err != nil {
|
||||||
log.Errorf("任务处理失败: %s", err.Error())
|
log.Errorf("任务处理失败: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *AiRouterBiz) SetRec(ctx context.Context, requireData *entitys.RequireData) (match entitys.Recognize, sys do.PromptOption, err error) {
|
||||||
|
// 参数空值检查
|
||||||
|
if requireData == nil || requireData.Req == nil {
|
||||||
|
return match, sys, errors.NewBusinessErr(500, "请求参数为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应不同的appKey, 配置不同的系统提示词
|
||||||
|
switch requireData.Sys.AppKey {
|
||||||
|
default:
|
||||||
|
sys = &do.WithSys{Config: r.config}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 系统提示词
|
||||||
|
match.SystemPrompt = requireData.Sys.SysPrompt
|
||||||
|
|
||||||
|
// 2. 用户输入和文件处理
|
||||||
|
match.UserContent, err = r.buildUserContent(requireData)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("构建用户内容失败: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 聊天记录 - 只有在有历史记录时才构建
|
||||||
|
if len(requireData.Histories) > 0 {
|
||||||
|
match.ChatHis = r.buildChatHistory(requireData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 任务列表 - 预分配切片容量
|
||||||
|
if len(requireData.Tasks) > 0 {
|
||||||
|
match.Tasks = make([]entitys.RegistrationTask, 0, len(requireData.Tasks))
|
||||||
|
for _, task := range requireData.Tasks {
|
||||||
|
taskConfig := entitys.TaskConfigDetail{}
|
||||||
|
if err = json.Unmarshal([]byte(task.Config), &taskConfig); err != nil {
|
||||||
|
log.Errorf("解析任务配置失败: %s, 任务ID: %s", err.Error(), task.Index)
|
||||||
|
continue // 解析失败时跳过该任务,而不是直接返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
match.Tasks = append(match.Tasks, entitys.RegistrationTask{
|
||||||
|
Name: task.Index,
|
||||||
|
Desc: task.Desc,
|
||||||
|
TaskConfigDetail: taskConfig, // 直接使用解析后的配置,避免重复构建
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match.Ch = requireData.Ch
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildUserContent 构建用户内容
|
||||||
|
func (r *AiRouterBiz) buildUserContent(requireData *entitys.RequireData) (*entitys.RecognizeUserContent, error) {
|
||||||
|
// 预分配文件切片容量(最多2个文件:File和Img)
|
||||||
|
files := make([]*entitys.RecognizeFile, 0, 2)
|
||||||
|
|
||||||
|
// 处理文件和图片
|
||||||
|
fileUrls := []string{requireData.Req.File, requireData.Req.Img}
|
||||||
|
for _, item := range fileUrls {
|
||||||
|
// 处理逗号分隔的多个URL
|
||||||
|
urlList := strings.Split(item, ",")
|
||||||
|
for _, url := range urlList {
|
||||||
|
if url != "" {
|
||||||
|
files = append(files, &entitys.RecognizeFile{FileUrl: url})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建并返回用户内容
|
||||||
|
return &entitys.RecognizeUserContent{
|
||||||
|
Text: requireData.Req.Text,
|
||||||
|
File: files,
|
||||||
|
ActionCardUrl: "", // TODO: 后续实现操作卡片功能
|
||||||
|
Tag: requireData.Req.Tags,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildChatHistory 构建聊天历史
|
||||||
|
func (r *AiRouterBiz) buildChatHistory(requireData *entitys.RequireData) entitys.ChatHis {
|
||||||
|
// 预分配消息切片容量(每个历史记录生成2条消息)
|
||||||
|
messages := make([]entitys.HisMessage, 0, len(requireData.Histories)*2)
|
||||||
|
|
||||||
|
// 构建聊天记录
|
||||||
|
for _, h := range requireData.Histories {
|
||||||
|
// 用户消息
|
||||||
|
messages = append(messages, entitys.HisMessage{
|
||||||
|
Role: constants.RoleUser, // 用户角色
|
||||||
|
Content: h.Ans, // 用户输入内容
|
||||||
|
Timestamp: h.CreateAt.Format(time.DateTime),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 助手消息
|
||||||
|
messages = append(messages, entitys.HisMessage{
|
||||||
|
Role: constants.RoleAssistant, // 助手角色
|
||||||
|
Content: h.Ques, // 助手回复内容
|
||||||
|
Timestamp: h.CreateAt.Format(time.DateTime),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建聊天历史上下文
|
||||||
|
return entitys.ChatHis{
|
||||||
|
SessionId: requireData.Session,
|
||||||
|
Messages: messages,
|
||||||
|
Context: entitys.HisContext{
|
||||||
|
UserLanguage: constants.LangZhCN, // 默认中文
|
||||||
|
SystemMode: constants.SystemModeTechnicalSupport, // 默认技术支持模式
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,16 @@ import (
|
||||||
type SessionBiz struct {
|
type SessionBiz struct {
|
||||||
sessionRepo *impl.SessionImpl
|
sessionRepo *impl.SessionImpl
|
||||||
sysRepo *impl.SysImpl
|
sysRepo *impl.SysImpl
|
||||||
chatRepo *impl.ChatImpl
|
chatHisRepo *impl.ChatHisImpl
|
||||||
|
|
||||||
conf *config.Config
|
conf *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionBiz(conf *config.Config, sessionImpl *impl.SessionImpl, sysImpl *impl.SysImpl, chatImpl *impl.ChatImpl) *SessionBiz {
|
func NewSessionBiz(conf *config.Config, sessionImpl *impl.SessionImpl, sysImpl *impl.SysImpl, chatImpl *impl.ChatHisImpl) *SessionBiz {
|
||||||
return &SessionBiz{
|
return &SessionBiz{
|
||||||
sessionRepo: sessionImpl,
|
sessionRepo: sessionImpl,
|
||||||
sysRepo: sysImpl,
|
sysRepo: sysImpl,
|
||||||
chatRepo: chatImpl,
|
chatHisRepo: chatImpl,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,10 +91,10 @@ func (s *SessionBiz) SessionInit(ctx context.Context, req *entitys.SessionInitRe
|
||||||
result.Prologue = sysConfig.Prologue
|
result.Prologue = sysConfig.Prologue
|
||||||
// 存在,返回会话历史
|
// 存在,返回会话历史
|
||||||
var chatList []model.AiChatHi
|
var chatList []model.AiChatHi
|
||||||
chatList, err = s.chatRepo.FindAll(
|
chatList, err = s.chatHisRepo.FindAll(
|
||||||
s.chatRepo.WithSessionId(session.SessionID), // 条件:会话ID
|
s.chatHisRepo.WithSessionId(session.SessionID), // 条件:会话ID
|
||||||
s.chatRepo.OrderByDesc("create_at"), // 排序:按创建时间降序
|
s.chatHisRepo.OrderByDesc("create_at"), // 排序:按创建时间降序
|
||||||
s.chatRepo.WithLimit(constants.ChatHistoryLimit), // 限制返回条数
|
s.chatHisRepo.WithLimit(constants.ChatHistoryLimit), // 限制返回条数
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -70,13 +70,13 @@ func (t *TaskBiz) GetUserPermission(req *entitys.TaskRequest, auth string) (code
|
||||||
// 发送请求
|
// 发送请求
|
||||||
res, err := request.Send()
|
res, err := request.Send()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.SysErr("请求用户权限失败")
|
err = errors.SysErrf("请求用户权限失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查响应状态码
|
// 检查响应状态码
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
err = errors.SysErr("获取用户权限失败")
|
err = errors.SysErrf("获取用户权限失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package tools_regis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ProviderToolsRegis = wire.NewSet(
|
||||||
|
NewToolsRegis,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package tools_regis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/constants"
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ToolRegis struct {
|
||||||
|
//待优化
|
||||||
|
BootTools []model.AiBotTool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToolsRegis(botToolsImpl *impl.BotToolsImpl) *ToolRegis {
|
||||||
|
botTools := &ToolRegis{}
|
||||||
|
err := botTools.RegisTools(botToolsImpl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return botTools
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToolRegis) RegisTools(botToolsImpl *impl.BotToolsImpl) error {
|
||||||
|
cond := builder.NewCond()
|
||||||
|
cond = cond.And(builder.Eq{"status": constants.Enable})
|
||||||
|
err := botToolsImpl.GetRangeToMapStruct(&cond, &t.BootTools)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -11,14 +11,19 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `mapstructure:"server"`
|
Server ServerConfig `mapstructure:"server"`
|
||||||
Ollama OllamaConfig `mapstructure:"ollama"`
|
Ollama OllamaConfig `mapstructure:"ollama"`
|
||||||
|
Vllm VllmConfig `mapstructure:"vllm"`
|
||||||
|
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"`
|
||||||
DefaultPrompt SysPrompt `mapstructure:"default_prompt"`
|
DefaultPrompt SysPrompt `mapstructure:"default_prompt"`
|
||||||
PermissionConfig PermissionConfig `mapstructure:"permissionConfig"`
|
PermissionConfig PermissionConfig `mapstructure:"permissionConfig"`
|
||||||
// LLM *LLM `mapstructure:"llm"`
|
LLM LLM `mapstructure:"llm"`
|
||||||
|
// DingTalkBots map[string]*DingTalkBot `mapstructure:"ding_talk_bots"`
|
||||||
|
Dingtalk DingtalkConfig `mapstructure:"dingtalk"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SysPrompt struct {
|
type SysPrompt struct {
|
||||||
|
|
@ -31,7 +36,44 @@ type DefaultPrompt struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LLM struct {
|
type LLM struct {
|
||||||
|
Providers map[string]LLMProviderConfig `mapstructure:"providers"`
|
||||||
|
Capabilities map[string]LLMCapabilityConfig `mapstructure:"capabilities"`
|
||||||
|
}
|
||||||
|
type LLMProviderConfig struct {
|
||||||
|
Endpoint string `mapstructure:"endpoint"`
|
||||||
|
Timeout string `mapstructure:"timeout"`
|
||||||
|
Models []LLMModle `mapstructure:"models"`
|
||||||
|
}
|
||||||
|
type LLMModle struct {
|
||||||
|
ID string `mapstructure:"id"`
|
||||||
|
Name string `mapstructure:"name"`
|
||||||
|
Streaming bool `mapstructure:"streaming"`
|
||||||
|
Modalities []string `mapstructure:"modalities"`
|
||||||
|
MaxTokens int `mapstructure:"max_tokens"`
|
||||||
|
}
|
||||||
|
type LLMParameters struct {
|
||||||
|
Temperature float64 `mapstructure:"temperature"`
|
||||||
|
MaxTokens int `mapstructure:"max_tokens"`
|
||||||
|
Stream bool `mapstructure:"stream"`
|
||||||
|
}
|
||||||
|
type LLMCapabilityConfig struct {
|
||||||
|
Provider string `mapstructure:"provider"`
|
||||||
Model string `mapstructure:"model"`
|
Model string `mapstructure:"model"`
|
||||||
|
Parameters LLMParameters `mapstructure:"parameters"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DingtalkConfig 钉钉配置
|
||||||
|
type DingtalkConfig struct {
|
||||||
|
ApiKey string `mapstructure:"api_key"`
|
||||||
|
ApiSecret string `mapstructure:"api_secret"`
|
||||||
|
TableDemand AITableConfig `mapstructure:"table_demand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableDemandConfig 需求表配置
|
||||||
|
type AITableConfig struct {
|
||||||
|
Url string `mapstructure:"url"`
|
||||||
|
BaseId string `mapstructure:"base_id"`
|
||||||
|
SheetIdOrName string `mapstructure:"sheet_id_or_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SysConfig 系统配置
|
// SysConfig 系统配置
|
||||||
|
|
@ -40,6 +82,7 @@ type SysConfig struct {
|
||||||
ChannelPoolLen int `mapstructure:"channel_pool_len"`
|
ChannelPoolLen int `mapstructure:"channel_pool_len"`
|
||||||
ChannelPoolSize int `mapstructure:"channel_pool_size"`
|
ChannelPoolSize int `mapstructure:"channel_pool_size"`
|
||||||
LlmPoolLen int `mapstructure:"llm_pool_len"`
|
LlmPoolLen int `mapstructure:"llm_pool_len"`
|
||||||
|
HeartbeatInterval int `mapstructure:"heartbeat_interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig 服务器配置
|
// ServerConfig 服务器配置
|
||||||
|
|
@ -53,10 +96,25 @@ type OllamaConfig struct {
|
||||||
BaseURL string `mapstructure:"base_url"`
|
BaseURL string `mapstructure:"base_url"`
|
||||||
Model string `mapstructure:"model"`
|
Model string `mapstructure:"model"`
|
||||||
GenerateModel string `mapstructure:"generate_model"`
|
GenerateModel string `mapstructure:"generate_model"`
|
||||||
|
MappingModel string `mapstructure:"mapping_model"`
|
||||||
VlModel string `mapstructure:"vl_model"`
|
VlModel string `mapstructure:"vl_model"`
|
||||||
Timeout time.Duration `mapstructure:"timeout"`
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VllmConfig struct {
|
||||||
|
BaseURL string `mapstructure:"base_url"`
|
||||||
|
VlModel string `mapstructure:"vl_model"`
|
||||||
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
|
Level string `mapstructure:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CozeConfig Coze配置
|
||||||
|
type CozeConfig struct {
|
||||||
|
BaseURL string `mapstructure:"base_url"`
|
||||||
|
ApiKey string `mapstructure:"api_key"`
|
||||||
|
ApiSecret string `mapstructure:"api_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
type Redis struct {
|
type Redis struct {
|
||||||
Host string `mapstructure:"host"`
|
Host string `mapstructure:"host"`
|
||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
|
|
@ -96,6 +154,10 @@ type ToolsConfig struct {
|
||||||
ZltxOrderAfterSaleReseller ToolConfig `mapstructure:"zltxOrderAfterSaleReseller"`
|
ZltxOrderAfterSaleReseller ToolConfig `mapstructure:"zltxOrderAfterSaleReseller"`
|
||||||
// 下游批充订单售后
|
// 下游批充订单售后
|
||||||
ZltxOrderAfterSaleResellerBatch ToolConfig `mapstructure:"zltxOrderAfterSaleResellerBatch"`
|
ZltxOrderAfterSaleResellerBatch ToolConfig `mapstructure:"zltxOrderAfterSaleResellerBatch"`
|
||||||
|
// Coze 快递查询工具
|
||||||
|
CozeExpress ToolConfig `mapstructure:"cozeExpress"`
|
||||||
|
// Coze 公司查询工具
|
||||||
|
CozeCompany ToolConfig `mapstructure:"cozeCompany"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToolConfig 单个工具配置
|
// ToolConfig 单个工具配置
|
||||||
|
|
@ -108,6 +170,26 @@ 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"`
|
||||||
|
// 货易通商品添加
|
||||||
|
HytGoodsAdd ToolConfig `mapstructure:"hytGoodsAdd"`
|
||||||
|
// 货易通商品图片添加
|
||||||
|
HytGoodsMediaAdd ToolConfig `mapstructure:"hytGoodsMediaAdd"`
|
||||||
|
// 货易通商品分类添加
|
||||||
|
HytGoodsCategoryAdd ToolConfig `mapstructure:"hytGoodsCategoryAdd"`
|
||||||
|
// 货易通商品分类查询
|
||||||
|
HytGoodsCategorySearch ToolConfig `mapstructure:"hytGoodsCategorySearch"`
|
||||||
|
// 货易通商品品牌查询
|
||||||
|
HytGoodsBrandSearch ToolConfig `mapstructure:"hytGoodsBrandSearch"`
|
||||||
|
}
|
||||||
|
|
||||||
// LoggingConfig 日志配置
|
// LoggingConfig 日志配置
|
||||||
type LoggingConfig struct {
|
type LoggingConfig struct {
|
||||||
Level string `mapstructure:"level"`
|
Level string `mapstructure:"level"`
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,43 @@ package constants
|
||||||
type BotTools string
|
type BotTools string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BotToolsBugOptimizationSubmit = "bug_optimization_submit" // 系统的bug/优化建议
|
BotToolsBugOptimizationSubmit BotTools = "bug_optimization_submit" // 系统的bug/优化建议
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatStyle int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChatStyleNormal ChatStyle = 1 //正常
|
||||||
|
ChatStyleSerious ChatStyle = 2 //严肃
|
||||||
|
ChatStyleGentle ChatStyle = 3 //温柔
|
||||||
|
ChatStyleArrogance ChatStyle = 4 //傲慢
|
||||||
|
ChatStyleCute ChatStyle = 5 //可爱
|
||||||
|
ChatStyleAngry ChatStyle = 6 //愤怒
|
||||||
|
)
|
||||||
|
|
||||||
|
var ChatStyleMap = map[ChatStyle]string{
|
||||||
|
ChatStyleNormal: "正常",
|
||||||
|
ChatStyleSerious: "严肃",
|
||||||
|
ChatStyleGentle: "温柔",
|
||||||
|
ChatStyleArrogance: "傲慢",
|
||||||
|
ChatStyleCute: "可爱",
|
||||||
|
ChatStyleAngry: "愤怒",
|
||||||
|
}
|
||||||
|
|
||||||
|
type BotType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BotTypeDingTalk BotType = 1 // 系统的bug/优化建议
|
||||||
|
)
|
||||||
|
|
||||||
|
const DingTalkAuthBaseKeyPrefix = "dingTalk_auth"
|
||||||
|
|
||||||
|
const DingTalkAuthBaseKeyBotPrefix = "dingTalk_auth_bot"
|
||||||
|
|
||||||
|
// PermissionType 工具使用权限
|
||||||
|
type PermissionType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PermissionTypeNone = 1
|
||||||
|
PermissionTypeDept = 2
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ const (
|
||||||
|
|
||||||
// 分页默认条数
|
// 分页默认条数
|
||||||
ChatHistoryLimit = 10
|
ChatHistoryLimit = 10
|
||||||
|
|
||||||
|
// 语言
|
||||||
|
LangZhCN = "zh-CN" // 中文
|
||||||
|
|
||||||
|
// 系统模式
|
||||||
|
SystemModeDefault = "default" // 默认模式
|
||||||
|
// 系统模式 "technical_support", // 技术支持模式
|
||||||
|
SystemModeTechnicalSupport = "technical_support" // 技术支持模式
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c Caller) String() string {
|
func (c Caller) String() string {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// Token
|
||||||
|
const (
|
||||||
|
CapabilityProductIngestToken = "A7f9KQ3mP2X8LZC4R5e"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prompt
|
||||||
|
const (
|
||||||
|
SystemPrompt = `
|
||||||
|
你是一个专业的商品属性提取助手,你的唯一任务是提取属性并以指定格式输出。请严格遵守:
|
||||||
|
<<< 格式规则 >>>
|
||||||
|
1. 输出必须是且仅是一个紧凑的、无任何多余空白字符(包括换行、缩进)的纯JSON字符串。
|
||||||
|
2. 整个JSON必须在一行内,例如:{"商品标题":"示例","价格":100}。
|
||||||
|
3. 严格禁止输出任何Markdown代码块标识、额外解释、思考过程或提示词本身。
|
||||||
|
4. 任何对上述规则的偏离都会导致系统解析失败。
|
||||||
|
<<< 规则结束 >>>
|
||||||
|
|
||||||
|
接下来,请处理用户输入并直接输出符合上述规则的结果。`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 商品属性模板-中文
|
||||||
|
const (
|
||||||
|
// 货易通供应商商品属性模板-中文
|
||||||
|
HYTSupplierProductPropertyTemplateZH = `{
|
||||||
|
"货品编号": "string", // 商品编号
|
||||||
|
"条码": "string", // 货品编号
|
||||||
|
"分类名称": "string", // 商品分类
|
||||||
|
"货品名称": "string", // 商品名称
|
||||||
|
"商品货号": "string", // 货品编号
|
||||||
|
"品牌": "string", // 商品品牌
|
||||||
|
"单位": "string", // 商品单位,若无则使用'个'
|
||||||
|
"规格参数": "string", // 商品规格参数
|
||||||
|
"货品说明": "string", // 商品说明
|
||||||
|
"保质期": "string", // 商品保质期,无则空
|
||||||
|
"保质期单位": "string", // 商品保质期单位,无则空
|
||||||
|
"链接": "string", // 空
|
||||||
|
"货品图片": ["string"], // 商品多图,取前2个即可
|
||||||
|
"电商销售价格": "string", // 商品电商销售价格 decimal(10,2)
|
||||||
|
"销售价": "string", // 商品销售价格 decimal(10,2)
|
||||||
|
"备注": "string", // 无则空
|
||||||
|
"长": "string", // 商品长度,decimal(10,2)+单位
|
||||||
|
"宽": "string", // 商品宽度,decimal(10,2)+单位
|
||||||
|
"高": "string", // 商品高度,decimal(10,2)+单位
|
||||||
|
"重量": "string", // 商品重量,decimal(10,2)+单位(kg)
|
||||||
|
"SPU名称": "string", // 商品SPU名称
|
||||||
|
"SPU编码": "string" // 货品编号
|
||||||
|
"供应商报价": "string", // 空
|
||||||
|
"税率": "string", // 商品税率 x%,无则空
|
||||||
|
"利润": "string", // 空
|
||||||
|
"默认供应商": "string", // 空
|
||||||
|
"默认存放仓库": "string", // 空
|
||||||
|
}`
|
||||||
|
// 货易通商品属性模板-中文 Ps:手机端主图、详情图文、平台资质图 (暂时无需)
|
||||||
|
HYTGoodsAddPropertyTemplateZH = `{
|
||||||
|
"商品标题": "string", // 商品名称
|
||||||
|
"商品编码": "string", // 商品编号+rand(1000-999)
|
||||||
|
"SPU名称": "string", // 商品SPU名称
|
||||||
|
"SPU编码": "string", // 'ai_'+商品编号
|
||||||
|
"商品货号": "string", // 商品编号
|
||||||
|
"商品条形码": "string", // 商品编号
|
||||||
|
"市场价": "string", // 优惠前价格 decimal(10,2)
|
||||||
|
"建议销售价": "string", // 市场价
|
||||||
|
"电商销售价格": "string", // 优惠后价格 decimal(10,2)
|
||||||
|
"单位": "string", // 价格单位,默认'元'
|
||||||
|
"折扣": "string", // 商品折扣(%),默认'0%'
|
||||||
|
"税率": "string", // 商品税率(%),默认'13%'
|
||||||
|
"运费模版": "string", // 商品运费模版,默认空
|
||||||
|
"保质期": "string", // 商品保质期,无则空
|
||||||
|
"保质期单位": "string", // 商品保质期单位,无则空
|
||||||
|
"品牌": "string", // 商品品牌,若无则空
|
||||||
|
"是否热销主推": "string", // 默认'否'
|
||||||
|
"外部平台链接": "string", // 空即可
|
||||||
|
"商品卖点": "string", // 商品卖点
|
||||||
|
"商品规格参数": "string", // 商品规格参数
|
||||||
|
"商品说明": "string", // 商品说明
|
||||||
|
"备注": "string", // 无则空
|
||||||
|
"分类名称": "string", // 商品分类
|
||||||
|
"电脑端主图": ["string"], // 商品电脑端主图,取第一张
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 缓存key
|
||||||
|
const (
|
||||||
|
CapabilityProductIngestCacheKey = "ai_scheduler:capability:product_ingest:%s"
|
||||||
|
)
|
||||||
|
|
@ -15,6 +15,8 @@ const (
|
||||||
TaskTypeKnowle TaskType = 2
|
TaskTypeKnowle TaskType = 2
|
||||||
TaskTypeFunc TaskType = 3
|
TaskTypeFunc TaskType = 3
|
||||||
TaskTypeBot TaskType = 4
|
TaskTypeBot TaskType = 4
|
||||||
|
TaskTypeEinoWorkflow TaskType = 5 // eino 工作流
|
||||||
|
TaskTypeCozeWorkflow TaskType = 6 // coze 工作流
|
||||||
)
|
)
|
||||||
|
|
||||||
type UseFul int32
|
type UseFul int32
|
||||||
|
|
@ -30,3 +32,5 @@ var UseFulMap = map[UseFul]string{
|
||||||
UseFulNotUnclear: "回答不明确",
|
UseFulNotUnclear: "回答不明确",
|
||||||
UseFulNotError: "理解错误",
|
UseFulNotError: "理解错误",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BaseBool int32
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
const DingTalkBseUrl = "https://oapi.dingtalk.com"
|
||||||
|
|
||||||
|
type RequestUrl string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestUrlGetUserGet RequestUrl = "/topapi/v2/user/get"
|
||||||
|
RequestUrlGetDeptGet RequestUrl = "/topapi/v2/department/get"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDingTalkRequestUrl(path RequestUrl, query map[string]string) string {
|
||||||
|
u, _ := url.Parse(DingTalkBseUrl + string(path))
|
||||||
|
q := u.Query()
|
||||||
|
for key, val := range query {
|
||||||
|
q.Add(key, val)
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBoss 是否是老板
|
||||||
|
type IsBoss int
|
||||||
|
|
||||||
|
const (
|
||||||
|
IsBossTrue IsBoss = 1
|
||||||
|
IsBossFalse IsBoss = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsSenior 是否是老板
|
||||||
|
type IsSenior int
|
||||||
|
|
||||||
|
const (
|
||||||
|
IsSeniorTrue IsSenior = 1
|
||||||
|
IsSeniorFalse IsSenior = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConversationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConversationTypeSingle = "1" // 单聊
|
||||||
|
ConversationTypeGroup = "2" //群聊
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotMsgType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BotMsgTypeText BotMsgType = "text"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CardTemp string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CardTempDefault CardTemp = `{
|
||||||
|
"config": {
|
||||||
|
"autoLayout": true,
|
||||||
|
"enableForward": true
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": {
|
||||||
|
"type": "text",
|
||||||
|
"text": "${title}",
|
||||||
|
},
|
||||||
|
"logo": "@lALPDfJ6V_FPDmvNAfTNAfQ"
|
||||||
|
},
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"type": "divider",
|
||||||
|
"id": "divider_1765952728523"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "markdown",
|
||||||
|
"text": "%s",
|
||||||
|
"id": "markdown_1765970168635"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
type FileType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileTypeUnknown FileType = "unknown"
|
||||||
|
FileTypeImage FileType = "image"
|
||||||
|
//FileTypeVideo FileType = "video"
|
||||||
|
FileTypeExcel FileType = "excel"
|
||||||
|
FileTypeWord FileType = "word"
|
||||||
|
FileTypeTxt FileType = "txt"
|
||||||
|
FileTypePDF FileType = "pdf"
|
||||||
|
FileTypePPT FileType = "ppt"
|
||||||
|
FileTypeCSV FileType = "csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FileTypeMappings = map[FileType][]string{
|
||||||
|
FileTypeImage: {
|
||||||
|
"image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml",
|
||||||
|
".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg",
|
||||||
|
},
|
||||||
|
FileTypeExcel: {
|
||||||
|
"application/vnd.ms-excel",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
".xls", ".xlsx",
|
||||||
|
},
|
||||||
|
FileTypeWord: {
|
||||||
|
"application/msword",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
".doc", ".docx",
|
||||||
|
},
|
||||||
|
FileTypePDF: {
|
||||||
|
"application/pdf",
|
||||||
|
".pdf",
|
||||||
|
},
|
||||||
|
FileTypeTxt: {
|
||||||
|
"text/plain",
|
||||||
|
".txt",
|
||||||
|
},
|
||||||
|
FileTypePPT: {
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
".pptx",
|
||||||
|
},
|
||||||
|
FileTypeCSV: {
|
||||||
|
"text/csv",
|
||||||
|
".csv",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ft FileType) String() string {
|
||||||
|
return string(ft)
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import "fmt"
|
||||||
var (
|
var (
|
||||||
Success = &BusinessErr{code: 200, message: "成功"}
|
Success = &BusinessErr{code: 200, message: "成功"}
|
||||||
ParamError = &BusinessErr{code: 401, message: "参数错误"}
|
ParamError = &BusinessErr{code: 401, message: "参数错误"}
|
||||||
|
ForbiddenError = &BusinessErr{code: 403, message: "权限不足"}
|
||||||
NotFoundError = &BusinessErr{code: 404, message: "请求地址未找到"}
|
NotFoundError = &BusinessErr{code: 404, message: "请求地址未找到"}
|
||||||
SystemError = &BusinessErr{code: 405, message: "系统错误"}
|
SystemError = &BusinessErr{code: 405, message: "系统错误"}
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ var (
|
||||||
SysNotFound = &BusinessErr{code: 410, message: "未找到系统信息"}
|
SysNotFound = &BusinessErr{code: 410, message: "未找到系统信息"}
|
||||||
SysCodeNotFound = &BusinessErr{code: 411, message: "未找到系统编码"}
|
SysCodeNotFound = &BusinessErr{code: 411, message: "未找到系统编码"}
|
||||||
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
InvalidParam = &BusinessErr{code: InvalidParamCode, message: "无效参数"}
|
||||||
|
WorkflowError = &BusinessErr{code: 501, message: "工作流过程错误"}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -43,14 +45,30 @@ func NewBusinessErr(code int, message string) *BusinessErr {
|
||||||
return &BusinessErr{code: code, message: message}
|
return &BusinessErr{code: code, message: message}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SysErr(message string, arg ...any) *BusinessErr {
|
func SysErrf(message string, arg ...any) *BusinessErr {
|
||||||
return &BusinessErr{code: SystemError.code, message: fmt.Sprintf(message, arg)}
|
return &BusinessErr{code: SystemError.code, message: fmt.Sprintf(message, arg)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParamErr(message string, arg ...any) *BusinessErr {
|
func SysErr(message string) *BusinessErr {
|
||||||
|
return &BusinessErr{code: SystemError.code, message: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParamErrf(message string, arg ...any) *BusinessErr {
|
||||||
return &BusinessErr{code: ParamError.code, message: fmt.Sprintf(message, arg)}
|
return &BusinessErr{code: ParamError.code, message: fmt.Sprintf(message, arg)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParamErr(message string) *BusinessErr {
|
||||||
|
return &BusinessErr{code: ParamError.code, message: message}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *BusinessErr) Wrap(err error) *BusinessErr {
|
func (e *BusinessErr) Wrap(err error) *BusinessErr {
|
||||||
return NewBusinessErr(e.code, err.Error())
|
return NewBusinessErr(e.code, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WorkflowErr(message string) *BusinessErr {
|
||||||
|
return NewBusinessErr(WorkflowError.code, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForbiddenErr(message string) *BusinessErr {
|
||||||
|
return NewBusinessErr(ForbiddenError.code, message)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ BaseModel 是一个泛型结构体,用于封装GORM数据库通用操作。
|
||||||
// 定义受支持的PO类型集合(可根据需要扩展), 只有包含表结构才能使用BaseModel,避免使用出现问题
|
// 定义受支持的PO类型集合(可根据需要扩展), 只有包含表结构才能使用BaseModel,避免使用出现问题
|
||||||
type PO interface {
|
type PO interface {
|
||||||
model.AiChatHi |
|
model.AiChatHi |
|
||||||
model.AiSy | model.AiSession | model.AiTask | model.AiBot
|
model.AiSy | model.AiSession | model.AiTask | model.AiBotConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseModel[P PO] struct {
|
type BaseModel[P PO] struct {
|
||||||
|
|
@ -54,6 +54,8 @@ type BaseRepository[P PO] interface {
|
||||||
WithStatus(status int) CondFunc // 查询status
|
WithStatus(status int) CondFunc // 查询status
|
||||||
GetDb() *gorm.DB // 获取数据库连接
|
GetDb() *gorm.DB // 获取数据库连接
|
||||||
WithLimit(limit int) CondFunc // 限制返回条数
|
WithLimit(limit int) CondFunc // 限制返回条数
|
||||||
|
In(field string, values interface{}) CondFunc // 查询字段是否在列表中
|
||||||
|
Select(fields ...string) CondFunc // 选择字段
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaginationResult 分页查询结果
|
// PaginationResult 分页查询结果
|
||||||
|
|
@ -215,3 +217,17 @@ func (this *BaseModel[P]) WithLimit(limit int) CondFunc {
|
||||||
return db.Limit(limit)
|
return db.Limit(limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询字段是否在列表中
|
||||||
|
func (this *BaseModel[P]) In(field string, values interface{}) CondFunc {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where(fmt.Sprintf("%s IN ?", field), values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select 字段
|
||||||
|
func (this *BaseModel[P]) Select(fields ...string) CondFunc {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select(fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotChatHisImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotChatHisImpl(db *utils.Db) *BotChatHisImpl {
|
||||||
|
return &BotChatHisImpl{*dataTemp.NewDataTemp(db, new(model.AiBotChatHi))}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotConfigImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotConfigImpl(db *utils.Db) *BotConfigImpl {
|
||||||
|
return &BotConfigImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBotConfig)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotDeptImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotDeptImpl(db *utils.Db) *BotDeptImpl {
|
||||||
|
return &BotDeptImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBotDept)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotGroupImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotGroupImpl(db *utils.Db) *BotGroupImpl {
|
||||||
|
return &BotGroupImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBotGroup)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k BotGroupImpl) GetByConversationIdAndRobotCode(staffId string, robotCode string) (*model.AiBotGroup, error) {
|
||||||
|
var data model.AiBotGroup
|
||||||
|
err := k.Db.Model(k.Model).Where("conversation_id = ? and robot_code = ?", staffId, robotCode).Find(&data).Error
|
||||||
|
if data.GroupID == 0 {
|
||||||
|
err = sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return &data, err
|
||||||
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package impl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"ai_scheduler/internal/data/model"
|
|
||||||
"ai_scheduler/tmpl/dataTemp"
|
|
||||||
"ai_scheduler/utils"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BotImpl struct {
|
|
||||||
dataTemp.DataTemp
|
|
||||||
BaseRepository[model.AiBot]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBotImpl(db *utils.Db) *BotImpl {
|
|
||||||
return &BotImpl{
|
|
||||||
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBot)),
|
|
||||||
BaseRepository: NewBaseModel[model.AiBot](db.Client),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSysId 系统id
|
|
||||||
func (s *BotImpl) WithSysId(sysId interface{}) CondFunc {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Where("sys_id = ?", sysId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotToolsImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotToolsImpl(db *utils.Db) *BotToolsImpl {
|
||||||
|
return &BotToolsImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBotTool)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/model"
|
||||||
|
"ai_scheduler/tmpl/dataTemp"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BotUserImpl struct {
|
||||||
|
dataTemp.DataTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBotUserImpl(db *utils.Db) *BotUserImpl {
|
||||||
|
return &BotUserImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiBotUser)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k BotUserImpl) GetByStaffId(staffId string) (*model.AiBotUser, error) {
|
||||||
|
var data model.AiBotUser
|
||||||
|
err := k.Db.Model(k.Model).Where("staff_id = ?", staffId).Find(&data).Error
|
||||||
|
if data.UserID == 0 {
|
||||||
|
err = sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return &data, err
|
||||||
|
}
|
||||||
|
|
@ -11,14 +11,14 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatImpl struct {
|
type ChatHisImpl struct {
|
||||||
dataTemp.DataTemp
|
dataTemp.DataTemp
|
||||||
BaseRepository[model.AiChatHi]
|
BaseRepository[model.AiChatHi]
|
||||||
chatChannel chan model.AiChatHi
|
chatChannel chan model.AiChatHi
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatImpl(db *utils.Db) *ChatImpl {
|
func NewChatHisImpl(db *utils.Db) *ChatHisImpl {
|
||||||
return &ChatImpl{
|
return &ChatHisImpl{
|
||||||
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiChatHi)),
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiChatHi)),
|
||||||
BaseRepository: NewBaseModel[model.AiChatHi](db.Client),
|
BaseRepository: NewBaseModel[model.AiChatHi](db.Client),
|
||||||
chatChannel: make(chan model.AiChatHi, 100),
|
chatChannel: make(chan model.AiChatHi, 100),
|
||||||
|
|
@ -26,19 +26,19 @@ func NewChatImpl(db *utils.Db) *ChatImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSessionId 条件:会话ID
|
// WithSessionId 条件:会话ID
|
||||||
func (impl *ChatImpl) WithSessionId(sessionId interface{}) CondFunc {
|
func (impl *ChatHisImpl) WithSessionId(sessionId interface{}) CondFunc {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("session_id = ?", sessionId)
|
return db.Where("session_id = ?", sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步添加会话历史
|
// 异步添加会话历史
|
||||||
func (impl *ChatImpl) AsyncCreate(ctx context.Context, chat model.AiChatHi) {
|
func (impl *ChatHisImpl) AsyncCreate(ctx context.Context, chat model.AiChatHi) {
|
||||||
impl.chatChannel <- chat
|
impl.chatChannel <- chat
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步处理会话历史
|
// 异步处理会话历史
|
||||||
func (impl *ChatImpl) AsyncProcess(ctx context.Context) {
|
func (impl *ChatHisImpl) AsyncProcess(ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case chat := <-impl.chatChannel:
|
case chat := <-impl.chatChannel:
|
||||||
|
|
@ -55,3 +55,10 @@ func (impl *ChatImpl) AsyncProcess(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// his_id 条件:历史ID
|
||||||
|
func (impl *ChatHisImpl) WithHisId(hisId interface{}) CondFunc {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("his_id = ?", hisId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,15 @@ import (
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ProviderImpl = wire.NewSet(NewSessionImpl, NewSysImpl, NewTaskImpl, NewChatImpl)
|
var ProviderImpl = wire.NewSet(
|
||||||
|
NewSessionImpl,
|
||||||
|
NewSysImpl,
|
||||||
|
NewTaskImpl,
|
||||||
|
NewChatHisImpl,
|
||||||
|
NewBotConfigImpl,
|
||||||
|
NewBotDeptImpl,
|
||||||
|
NewBotUserImpl,
|
||||||
|
NewBotChatHisImpl,
|
||||||
|
NewBotToolsImpl,
|
||||||
|
NewBotGroupImpl,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,12 @@ import (
|
||||||
|
|
||||||
type TaskImpl struct {
|
type TaskImpl struct {
|
||||||
dataTemp.DataTemp
|
dataTemp.DataTemp
|
||||||
|
BaseRepository[model.AiTask]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskImpl(db *utils.Db) *TaskImpl {
|
func NewTaskImpl(db *utils.Db) *TaskImpl {
|
||||||
return &TaskImpl{*dataTemp.NewDataTemp(db, new(model.AiTask))}
|
return &TaskImpl{
|
||||||
|
DataTemp: *dataTemp.NewDataTemp(db, new(model.AiTask)),
|
||||||
|
BaseRepository: NewBaseModel[model.AiTask](db.Client),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
|
||||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
|
||||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
|
||||||
|
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const TableNameAiBot = "ai_bot"
|
|
||||||
|
|
||||||
// AiBot mapped from table <ai_bot>
|
|
||||||
type AiBot struct {
|
|
||||||
BotID int32 `gorm:"column:bot_id;primaryKey;autoIncrement:true" json:"bot_id"`
|
|
||||||
SysID int32 `gorm:"column:sys_id;not null" json:"sys_id"`
|
|
||||||
BotType int32 `gorm:"column:bot_type" json:"bot_type"`
|
|
||||||
BotName string `gorm:"column:bot_name;not null" json:"bot_name"`
|
|
||||||
BotConfig string `gorm:"column:bot_config;not null" json:"bot_config"`
|
|
||||||
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
|
||||||
Status int32 `gorm:"column:status;not null" json:"status"`
|
|
||||||
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName AiBot's table name
|
|
||||||
func (*AiBot) TableName() string {
|
|
||||||
return TableNameAiBot
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotChatHi = "ai_bot_chat_his"
|
||||||
|
|
||||||
|
// AiBotChatHi mapped from table <ai_bot_chat_his>
|
||||||
|
type AiBotChatHi struct {
|
||||||
|
HisID int64 `gorm:"column:his_id;primaryKey;autoIncrement:true" json:"his_id"`
|
||||||
|
HisType string `gorm:"column:his_type;not null;default:1;comment:1为个人,2为群聊" json:"his_type"` // 1为个人,2为群聊
|
||||||
|
ID int32 `gorm:"column:id;not null;comment:对应的id" json:"id"` // 对应的id
|
||||||
|
Role string `gorm:"column:role;not null;comment:system系统输出,assistant助手输出,user用户输入" json:"role"` // system系统输出,assistant助手输出,user用户输入
|
||||||
|
Content string `gorm:"column:content;not null" json:"content"`
|
||||||
|
Files string `gorm:"column:files;not null" json:"files"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotChatHi's table name
|
||||||
|
func (*AiBotChatHi) TableName() string {
|
||||||
|
return TableNameAiBotChatHi
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotConfig = "ai_bot_config"
|
||||||
|
|
||||||
|
// AiBotConfig mapped from table <ai_bot_config>
|
||||||
|
type AiBotConfig struct {
|
||||||
|
BotID int32 `gorm:"column:bot_id;primaryKey;autoIncrement:true" json:"bot_id"`
|
||||||
|
BotType int32 `gorm:"column:bot_type;not null;default:1;comment:类型,1为钉钉机器人" json:"bot_type"` // 类型,1为钉钉机器人
|
||||||
|
SysPrompt string `gorm:"column:sys_prompt" json:"sys_prompt"`
|
||||||
|
BotName string `gorm:"column:bot_name;not null;comment:名字" json:"bot_name"` // 名字
|
||||||
|
BotConfig string `gorm:"column:bot_config;not null;comment:配置" json:"bot_config"` // 配置
|
||||||
|
RobotCode string `gorm:"column:robot_code;not null;comment:索引" json:"robot_code"` // 索引
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||||
|
Status int32 `gorm:"column:status;not null" json:"status"`
|
||||||
|
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotConfig's table name
|
||||||
|
func (*AiBotConfig) TableName() string {
|
||||||
|
return TableNameAiBotConfig
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotDept = "ai_bot_dept"
|
||||||
|
|
||||||
|
// AiBotDept mapped from table <ai_bot_dept>
|
||||||
|
type AiBotDept struct {
|
||||||
|
DeptID int32 `gorm:"column:dept_id;primaryKey;autoIncrement:true" json:"dept_id"`
|
||||||
|
DingtalkDeptID int32 `gorm:"column:dingtalk_dept_id;not null;comment:标记部门的唯一id,钉钉:钉钉侧提供的dept_id" json:"dingtalk_dept_id"` // 标记部门的唯一id,钉钉:钉钉侧提供的dept_id
|
||||||
|
Name string `gorm:"column:name;not null;comment:用户名称" json:"name"` // 用户名称
|
||||||
|
ToolList string `gorm:"column:tool_list;not null;comment:该部门支持的权限" json:"tool_list"` // 该部门支持的权限
|
||||||
|
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
|
||||||
|
DeleteAt *time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotDept's table name
|
||||||
|
func (*AiBotDept) TableName() string {
|
||||||
|
return TableNameAiBotDept
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotGroup = "ai_bot_group"
|
||||||
|
|
||||||
|
// AiBotGroup mapped from table <ai_bot_group>
|
||||||
|
type AiBotGroup struct {
|
||||||
|
GroupID int32 `gorm:"column:group_id;primaryKey;autoIncrement:true" json:"group_id"`
|
||||||
|
ConversationID string `gorm:"column:conversation_id;not null;comment:会话ID" json:"conversation_id"` // 会话ID
|
||||||
|
RobotCode string `gorm:"column:robot_code;not null;comment:绑定机器人code" json:"robot_code"` // 绑定机器人code
|
||||||
|
Title string `gorm:"column:title;not null;comment:群名称" json:"title"` // 群名称
|
||||||
|
ToolList string `gorm:"column:tool_list;not null;comment:开通工具列表" json:"tool_list"` // 开通工具列表
|
||||||
|
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
|
||||||
|
DeleteAt *time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotGroup's table name
|
||||||
|
func (*AiBotGroup) TableName() string {
|
||||||
|
return TableNameAiBotGroup
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotTool = "ai_bot_tools"
|
||||||
|
|
||||||
|
// AiBotTool mapped from table <ai_bot_tools>
|
||||||
|
type AiBotTool struct {
|
||||||
|
ToolID int32 `gorm:"column:tool_id;primaryKey;autoIncrement:true" json:"tool_id"`
|
||||||
|
PermissionType int32 `gorm:"column:permission_type;not null;comment:类型,1为公共工具,不需要进行权限管理,反之则为2" json:"permission_type"` // 类型,1为公共工具,不需要进行权限管理,反之则为2
|
||||||
|
Config string `gorm:"column:config;not null;comment:类型下所需路由以及参数" json:"config"` // 类型下所需路由以及参数
|
||||||
|
Type int32 `gorm:"column:type;not null;default:3" json:"type"`
|
||||||
|
Name string `gorm:"column:name;not null;default:1;comment:工具名称" json:"name"` // 工具名称
|
||||||
|
Index string `gorm:"column:index;not null;comment:索引" json:"index"` // 索引
|
||||||
|
Desc string `gorm:"column:desc;not null;comment:工具描述" json:"desc"` // 工具描述
|
||||||
|
TempPrompt string `gorm:"column:temp_prompt;not null;comment:提示词模板" json:"temp_prompt"` // 提示词模板
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||||
|
Status int32 `gorm:"column:status;not null" json:"status"`
|
||||||
|
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotTool's table name
|
||||||
|
func (*AiBotTool) TableName() string {
|
||||||
|
return TableNameAiBotTool
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameAiBotUser = "ai_bot_user"
|
||||||
|
|
||||||
|
// AiBotUser mapped from table <ai_bot_user>
|
||||||
|
type AiBotUser struct {
|
||||||
|
UserID int32 `gorm:"column:user_id;primaryKey" json:"user_id"`
|
||||||
|
StaffID string `gorm:"column:staff_id;not null;comment:标记用户用的唯一id,钉钉:钉钉侧提供的user_id" json:"staff_id"` // 标记用户用的唯一id,钉钉:钉钉侧提供的user_id
|
||||||
|
Name string `gorm:"column:name;not null;comment:用户名称" json:"name"` // 用户名称
|
||||||
|
Title string `gorm:"column:title;not null;comment:职位" json:"title"` // 职位
|
||||||
|
Extension string `gorm:"column:extension;not null;default:1;comment:信息面板" json:"extension"` // 信息面板
|
||||||
|
RoleList string `gorm:"column:role_list;not null;comment:角色列表。" json:"role_list"` // 角色列表。
|
||||||
|
DeptIDList string `gorm:"column:dept_id_list;not null;comment:所在部门id列表" json:"dept_id_list"` // 所在部门id列表
|
||||||
|
IsBoss int32 `gorm:"column:is_boss;not null;comment:是否是老板" json:"is_boss"` // 是否是老板
|
||||||
|
IsSenior int32 `gorm:"column:is_senior;not null;comment:是否是高管" json:"is_senior"` // 是否是高管
|
||||||
|
HiredDate time.Time `gorm:"column:hired_date;not null;default:CURRENT_TIMESTAMP;comment:入职时间" json:"hired_date"` // 入职时间
|
||||||
|
Status int32 `gorm:"column:status;not null" json:"status"`
|
||||||
|
DeleteAt *time.Time `gorm:"column:delete_at" json:"delete_at"`
|
||||||
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName AiBotUser's table name
|
||||||
|
func (*AiBotUser) TableName() string {
|
||||||
|
return TableNameAiBotUser
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ type AiChatHi struct {
|
||||||
Useful int32 `gorm:"column:useful;not null;comment:0不评价,1有用,其他为无用" json:"useful"` // 0不评价,1有用,其他为无用
|
Useful int32 `gorm:"column:useful;not null;comment:0不评价,1有用,其他为无用" json:"useful"` // 0不评价,1有用,其他为无用
|
||||||
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||||
|
TaskID int32 `gorm:"column:task_id;not null" json:"task_id"` // 任务ID
|
||||||
|
Content string `gorm:"column:content" json:"content"` // 前端回传数据
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName AiChatHi's table name
|
// TableName AiChatHi's table name
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OptionsFromLLMParameters(p config.LLMParameters) llm.Options {
|
||||||
|
return llm.Options{Temperature: float32(p.Temperature), MaxTokens: p.MaxTokens, Stream: p.Stream}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
type KV map[string]any
|
||||||
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageInput struct {
|
||||||
|
URLs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildVisionMessages(systemPrompt string, userText string, images ImageInput) ([]*schema.Message, error) {
|
||||||
|
if len(images.URLs) == 0 {
|
||||||
|
return nil, errors.New("vision requires at least one image url")
|
||||||
|
}
|
||||||
|
parts := make([]schema.MessageInputPart, 0, 1+len(images.URLs))
|
||||||
|
if strings.TrimSpace(userText) != "" {
|
||||||
|
parts = append(parts, schema.MessageInputPart{Type: schema.ChatMessagePartTypeText, Text: userText})
|
||||||
|
}
|
||||||
|
for _, u := range images.URLs {
|
||||||
|
if u == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = append(parts, schema.MessageInputPart{Type: schema.ChatMessagePartTypeImageURL, Image: &schema.MessageInputImage{MessagePartCommon: schema.MessagePartCommon{URL: &u}, Detail: schema.ImageURLDetailHigh}})
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return nil, errors.New("vision inputs invalid: no text or valid image urls")
|
||||||
|
}
|
||||||
|
msgs := []*schema.Message{schema.SystemMessage(systemPrompt)}
|
||||||
|
msgs = append(msgs, &schema.Message{Role: schema.User, UserInputMultiContent: parts})
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package callback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"ai_scheduler/internal/pkg"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
Register(ctx context.Context, taskID string, sessionID string) error
|
||||||
|
Wait(ctx context.Context, taskID string, timeout time.Duration) (string, error)
|
||||||
|
Notify(ctx context.Context, taskID string, result string) error
|
||||||
|
GetSession(ctx context.Context, taskID string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisManager struct {
|
||||||
|
rdb *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisManager(rdb *pkg.Rdb) *RedisManager {
|
||||||
|
return &RedisManager{
|
||||||
|
rdb: rdb.Rdb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyPrefixSession = "callback:session:"
|
||||||
|
keyPrefixSignal = "callback:signal:"
|
||||||
|
defaultTTL = 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *RedisManager) Register(ctx context.Context, taskID string, sessionID string) error {
|
||||||
|
key := keyPrefixSession + taskID
|
||||||
|
return m.rdb.Set(ctx, key, sessionID, defaultTTL).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RedisManager) Wait(ctx context.Context, taskID string, timeout time.Duration) (string, error) {
|
||||||
|
key := keyPrefixSignal + taskID
|
||||||
|
// BLPop 阻塞等待
|
||||||
|
result, err := m.rdb.BLPop(ctx, timeout, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
if err == redis.Nil {
|
||||||
|
return "", fmt.Errorf("timeout waiting for callback")
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// result[0] is key, result[1] is value
|
||||||
|
if len(result) < 2 {
|
||||||
|
return "", fmt.Errorf("invalid redis result")
|
||||||
|
}
|
||||||
|
return result[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RedisManager) Notify(ctx context.Context, taskID string, result string) error {
|
||||||
|
key := keyPrefixSignal + taskID
|
||||||
|
// Push 信号,同时设置过期时间防止堆积
|
||||||
|
pipe := m.rdb.Pipeline()
|
||||||
|
pipe.RPush(ctx, key, result)
|
||||||
|
pipe.Expire(ctx, key, 1*time.Hour) // 信号列表也需要过期
|
||||||
|
_, err := pipe.Exec(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *RedisManager) GetSession(ctx context.Context, taskID string) (string, error) {
|
||||||
|
key := keyPrefixSession + taskID
|
||||||
|
return m.rdb.Get(ctx, key).Result()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package callback
|
||||||
|
|
||||||
|
import "github.com/google/wire"
|
||||||
|
|
||||||
|
var ProviderSet = wire.NewSet(NewRedisManager, wire.Bind(new(Manager), new(*RedisManager)))
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/domain/component/callback"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Components struct {
|
||||||
|
Callback callback.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewComponents(callbackManager callback.Manager) *Components {
|
||||||
|
return &Components{
|
||||||
|
Callback: callbackManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/domain/component/callback"
|
||||||
|
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ProviderSetComponent = wire.NewSet(NewComponents)
|
||||||
|
|
||||||
|
var ProviderSet = wire.NewSet(
|
||||||
|
callback.NewRedisManager, wire.Bind(new(callback.Manager), new(*callback.RedisManager)),
|
||||||
|
NewComponents,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package llm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Chat(ctx context.Context, input []*schema.Message, opts Options) (*schema.Message, error)
|
||||||
|
ChatStream(ctx context.Context, input []*schema.Message, opts Options) (*schema.StreamReader[*schema.Message], error)
|
||||||
|
Vision(ctx context.Context, input []*schema.Message, opts Options) (*schema.Message, error)
|
||||||
|
Intent(ctx context.Context, input []*schema.Message, opts Options) (*schema.Message, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package capability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Route(cfg *config.Config, ability Ability) (ProviderChoice, llm.Options, error) {
|
||||||
|
cap, ok := cfg.LLM.Capabilities[string(ability)]
|
||||||
|
if !ok {
|
||||||
|
return ProviderChoice{}, llm.Options{}, llm.ErrInvalidCapability
|
||||||
|
}
|
||||||
|
prov, ok := cfg.LLM.Providers[cap.Provider]
|
||||||
|
if !ok {
|
||||||
|
return ProviderChoice{}, llm.Options{}, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
var modelConf config.LLMModle
|
||||||
|
found := false
|
||||||
|
for _, m := range prov.Models {
|
||||||
|
if m.Name == cap.Model || m.ID == cap.Model {
|
||||||
|
modelConf = m
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return ProviderChoice{}, llm.Options{}, llm.ErrModelNotFound
|
||||||
|
}
|
||||||
|
to := llm.Options{}
|
||||||
|
to.Model = modelConf.Name
|
||||||
|
to.Stream = cap.Parameters.Stream || modelConf.Streaming
|
||||||
|
if to.Stream && !modelConf.Streaming {
|
||||||
|
to.Stream = false
|
||||||
|
}
|
||||||
|
to.MaxTokens = modelConf.MaxTokens
|
||||||
|
if cap.Parameters.MaxTokens > 0 && cap.Parameters.MaxTokens <= modelConf.MaxTokens {
|
||||||
|
to.MaxTokens = cap.Parameters.MaxTokens
|
||||||
|
}
|
||||||
|
to.Temperature = float32(cap.Parameters.Temperature)
|
||||||
|
to.Modalities = append([]string{}, modelConf.Modalities...)
|
||||||
|
d, _ := time.ParseDuration(strings.TrimSpace(prov.Timeout))
|
||||||
|
to.Timeout = d
|
||||||
|
to.Endpoint = prov.Endpoint
|
||||||
|
choice := ProviderChoice{Provider: cap.Provider, Model: to.Model}
|
||||||
|
return choice, to, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package capability
|
||||||
|
|
||||||
|
type Ability string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Intent Ability = "intent"
|
||||||
|
Vision Ability = "vision"
|
||||||
|
Chat Ability = "chat"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProviderChoice struct {
|
||||||
|
Provider string
|
||||||
|
Model string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package capability
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Validate(ability Ability, opts llm.Options) error {
|
||||||
|
if ability == Vision {
|
||||||
|
has := false
|
||||||
|
for _, m := range opts.Modalities {
|
||||||
|
if m == "image" {
|
||||||
|
has = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return llm.ErrModalityMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package llm
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrInvalidCapability = errors.New("能力未配置或无效")
|
||||||
|
var ErrProviderNotFound = errors.New("提供者未找到或未注册")
|
||||||
|
var ErrModelNotFound = errors.New("模型未找到或未配置")
|
||||||
|
var ErrModalityMismatch = errors.New("模态不匹配:视觉能力需要包含 image")
|
||||||
|
var ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package llm
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Temperature float32
|
||||||
|
MaxTokens int
|
||||||
|
Stream bool
|
||||||
|
Timeout time.Duration
|
||||||
|
Modalities []string
|
||||||
|
SystemPrompt string
|
||||||
|
Model string
|
||||||
|
TopP float32
|
||||||
|
Stop []string
|
||||||
|
Endpoint string
|
||||||
|
Thinking bool
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"ai_scheduler/internal/domain/llm/capability"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider/ollama"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider/vllm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
provider.Register("ollama", func() provider.Adapter { return ollama.New() })
|
||||||
|
provider.Register("vllm", func() provider.Adapter { return vllm.New() })
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildChat(ctx context.Context, cfg *config.Config) (compose.Runnable[[]*schema.Message, *schema.Message], error) {
|
||||||
|
choice, opts, err := capability.Route(cfg, capability.Chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = capability.Validate(capability.Chat, opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
ad := f()
|
||||||
|
c := compose.NewChain[[]*schema.Message, *schema.Message]()
|
||||||
|
c.AppendLambda(compose.InvokableLambda(func(ctx context.Context, in []*schema.Message) (*schema.Message, error) {
|
||||||
|
return ad.Generate(ctx, in, opts)
|
||||||
|
}))
|
||||||
|
return c.Compile(ctx)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"ai_scheduler/internal/domain/llm/capability"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildIntent(ctx context.Context, cfg *config.Config) (compose.Runnable[[]*schema.Message, *schema.Message], error) {
|
||||||
|
choice, opts, err := capability.Route(cfg, capability.Intent)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if err = capability.Validate(capability.Intent, opts); err != nil { return nil, err }
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil { return nil, llm.ErrProviderNotFound }
|
||||||
|
ad := f()
|
||||||
|
c := compose.NewChain[[]*schema.Message, *schema.Message]()
|
||||||
|
c.AppendLambda(compose.InvokableLambda(func(ctx context.Context, in []*schema.Message) (*schema.Message, error) {
|
||||||
|
return ad.Generate(ctx, in, opts)
|
||||||
|
}))
|
||||||
|
return c.Compile(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package pipeline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/common"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"ai_scheduler/internal/domain/llm/capability"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildVision(ctx context.Context, cfg *config.Config) (compose.Runnable[[]*schema.Message, *schema.Message], error) {
|
||||||
|
choice, opts, err := capability.Route(cfg, capability.Vision)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = capability.Validate(capability.Vision, opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
ad := f()
|
||||||
|
c := compose.NewChain[[]*schema.Message, *schema.Message]()
|
||||||
|
c.AppendLambda(compose.InvokableLambda(func(ctx context.Context, in []*schema.Message) (*schema.Message, error) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
msgs, err := common.BuildVisionMessages("你是一名视觉助手,请根据图片与描述进行理解与回答。", "", common.ImageInput{URLs: []string{}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ad.Generate(ctx, msgs, opts)
|
||||||
|
}
|
||||||
|
if len(in[0].MultiContent) == 0 {
|
||||||
|
urls := []string{}
|
||||||
|
for _, tok := range splitBySpace(in[0].Content) {
|
||||||
|
if hasHTTPPrefix(tok) {
|
||||||
|
urls = append(urls, tok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msgs, err := common.BuildVisionMessages("你是一名视觉助手,请根据图片与描述进行理解与回答。", "", common.ImageInput{URLs: urls})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ad.Generate(ctx, msgs, opts)
|
||||||
|
}
|
||||||
|
return ad.Generate(ctx, in, opts)
|
||||||
|
}))
|
||||||
|
return c.Compile(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitBySpace(s string) []string {
|
||||||
|
res := []string{}
|
||||||
|
start := -1
|
||||||
|
for i, r := range s {
|
||||||
|
if r == ' ' || r == '\n' || r == '\t' || r == '\r' {
|
||||||
|
if start >= 0 {
|
||||||
|
res = append(res, s[start:i])
|
||||||
|
start = -1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if start < 0 {
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start >= 0 {
|
||||||
|
res = append(res, s[start:])
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasHTTPPrefix(s string) bool {
|
||||||
|
return len(s) >= 7 && (s[:7] == "http://" || (len(s) >= 8 && s[:8] == "https://"))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package prompt
|
||||||
|
|
||||||
|
func SystemForChat() string {
|
||||||
|
return "你是一名有用的助手,请用清晰、简洁的中文回答。"
|
||||||
|
}
|
||||||
|
func SystemForVision() string {
|
||||||
|
return "你是一名视觉助手,请根据图片与描述进行中文理解与回答。"
|
||||||
|
}
|
||||||
|
func SystemForIntent() string {
|
||||||
|
return "你负责意图识别,请用中文给出明确的意图类别与理由。"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package ollama
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
eino_ollama "github.com/cloudwego/eino-ext/components/model/ollama"
|
||||||
|
eino_model "github.com/cloudwego/eino/components/model"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct{}
|
||||||
|
|
||||||
|
func New() *Adapter { return &Adapter{} }
|
||||||
|
|
||||||
|
func (a *Adapter) Generate(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error) {
|
||||||
|
cm, err := eino_ollama.NewChatModel(ctx, &eino_ollama.ChatModelConfig{
|
||||||
|
BaseURL: opts.Endpoint,
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
Model: opts.Model,
|
||||||
|
Options: &eino_ollama.Options{Temperature: opts.Temperature, NumPredict: opts.MaxTokens},
|
||||||
|
Thinking: &eino_ollama.ThinkValue{Value: opts.Thinking},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var mopts []eino_model.Option
|
||||||
|
if opts.Temperature != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTemperature(opts.Temperature))
|
||||||
|
}
|
||||||
|
if opts.MaxTokens > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithMaxTokens(opts.MaxTokens))
|
||||||
|
}
|
||||||
|
if opts.Model != "" {
|
||||||
|
mopts = append(mopts, eino_model.WithModel(opts.Model))
|
||||||
|
}
|
||||||
|
if opts.TopP != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTopP(opts.TopP))
|
||||||
|
}
|
||||||
|
if len(opts.Stop) > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithStop(opts.Stop))
|
||||||
|
}
|
||||||
|
return cm.Generate(ctx, input, mopts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Stream(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.StreamReader[*schema.Message], error) {
|
||||||
|
cm, err := eino_ollama.NewChatModel(ctx, &eino_ollama.ChatModelConfig{
|
||||||
|
BaseURL: opts.Endpoint,
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
Model: opts.Model,
|
||||||
|
Options: &eino_ollama.Options{Temperature: opts.Temperature, NumPredict: opts.MaxTokens},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var mopts []eino_model.Option
|
||||||
|
if opts.Temperature != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTemperature(opts.Temperature))
|
||||||
|
}
|
||||||
|
if opts.MaxTokens > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithMaxTokens(opts.MaxTokens))
|
||||||
|
}
|
||||||
|
if opts.Model != "" {
|
||||||
|
mopts = append(mopts, eino_model.WithModel(opts.Model))
|
||||||
|
}
|
||||||
|
if opts.TopP != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTopP(opts.TopP))
|
||||||
|
}
|
||||||
|
if len(opts.Stop) > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithStop(opts.Stop))
|
||||||
|
}
|
||||||
|
return cm.Stream(ctx, input, mopts...)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter interface {
|
||||||
|
Generate(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error)
|
||||||
|
Stream(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.StreamReader[*schema.Message], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Factory func() Adapter
|
||||||
|
|
||||||
|
var registry = map[string]Factory{}
|
||||||
|
|
||||||
|
func Register(name string, f Factory) {
|
||||||
|
registry[name] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(name string) Factory {
|
||||||
|
return registry[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package vllm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
eino_openai "github.com/cloudwego/eino-ext/components/model/openai"
|
||||||
|
eino_model "github.com/cloudwego/eino/components/model"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct{}
|
||||||
|
|
||||||
|
func New() *Adapter { return &Adapter{} }
|
||||||
|
|
||||||
|
func (a *Adapter) Generate(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error) {
|
||||||
|
cm, err := eino_openai.NewChatModel(ctx, &eino_openai.ChatModelConfig{
|
||||||
|
BaseURL: opts.Endpoint,
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
Model: opts.Model,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var mopts []eino_model.Option
|
||||||
|
if opts.Temperature != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTemperature(opts.Temperature))
|
||||||
|
}
|
||||||
|
if opts.MaxTokens > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithMaxTokens(opts.MaxTokens))
|
||||||
|
}
|
||||||
|
if opts.Model != "" {
|
||||||
|
mopts = append(mopts, eino_model.WithModel(opts.Model))
|
||||||
|
}
|
||||||
|
if opts.TopP != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTopP(opts.TopP))
|
||||||
|
}
|
||||||
|
if len(opts.Stop) > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithStop(opts.Stop))
|
||||||
|
}
|
||||||
|
return cm.Generate(ctx, input, mopts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Stream(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.StreamReader[*schema.Message], error) {
|
||||||
|
cm, err := eino_openai.NewChatModel(ctx, &eino_openai.ChatModelConfig{
|
||||||
|
BaseURL: opts.Endpoint,
|
||||||
|
Timeout: opts.Timeout,
|
||||||
|
Model: opts.Model,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var mopts []eino_model.Option
|
||||||
|
if opts.Temperature != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTemperature(opts.Temperature))
|
||||||
|
}
|
||||||
|
if opts.MaxTokens > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithMaxTokens(opts.MaxTokens))
|
||||||
|
}
|
||||||
|
if opts.Model != "" {
|
||||||
|
mopts = append(mopts, eino_model.WithModel(opts.Model))
|
||||||
|
}
|
||||||
|
if opts.TopP != 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithTopP(opts.TopP))
|
||||||
|
}
|
||||||
|
if len(opts.Stop) > 0 {
|
||||||
|
mopts = append(mopts, eino_model.WithStop(opts.Stop))
|
||||||
|
}
|
||||||
|
return cm.Stream(ctx, input, mopts...)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm/pipeline"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatService struct{ run compose.Runnable[[]*schema.Message, *schema.Message] }
|
||||||
|
|
||||||
|
func NewChatService(ctx context.Context, cfg *config.Config) (*ChatService, error) {
|
||||||
|
r, err := pipeline.BuildChat(ctx, cfg)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return &ChatService{run: r}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChatService) Invoke(ctx context.Context, msgs []*schema.Message) (*schema.Message, error) {
|
||||||
|
return s.run.Invoke(ctx, msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm/pipeline"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntentService struct{ run compose.Runnable[[]*schema.Message, *schema.Message] }
|
||||||
|
|
||||||
|
func NewIntentService(ctx context.Context, cfg *config.Config) (*IntentService, error) {
|
||||||
|
r, err := pipeline.BuildIntent(ctx, cfg)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return &IntentService{run: r}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntentService) Invoke(ctx context.Context, msgs []*schema.Message) (*schema.Message, error) {
|
||||||
|
return s.run.Invoke(ctx, msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm"
|
||||||
|
"ai_scheduler/internal/domain/llm/capability"
|
||||||
|
"ai_scheduler/internal/domain/llm/provider"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LLMService struct{ cfg *config.Config }
|
||||||
|
|
||||||
|
func NewLLMService(cfg *config.Config) *LLMService { return &LLMService{cfg: cfg} }
|
||||||
|
|
||||||
|
func (s *LLMService) Chat(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error) {
|
||||||
|
choice, routeOpts, err := capability.Route(s.cfg, capability.Chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mergeOptions(&routeOpts, opts)
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
return f().Generate(ctx, input, routeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LLMService) ChatStream(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.StreamReader[*schema.Message], error) {
|
||||||
|
choice, routeOpts, err := capability.Route(s.cfg, capability.Chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
routeOpts.Stream = true
|
||||||
|
mergeOptions(&routeOpts, opts)
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
return f().Stream(ctx, input, routeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LLMService) Vision(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error) {
|
||||||
|
choice, routeOpts, err := capability.Route(s.cfg, capability.Vision)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mergeOptions(&routeOpts, opts)
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
return f().Generate(ctx, input, routeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LLMService) Intent(ctx context.Context, input []*schema.Message, opts llm.Options) (*schema.Message, error) {
|
||||||
|
choice, routeOpts, err := capability.Route(s.cfg, capability.Intent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mergeOptions(&routeOpts, opts)
|
||||||
|
f := provider.Get(choice.Provider)
|
||||||
|
if f == nil {
|
||||||
|
return nil, llm.ErrProviderNotFound
|
||||||
|
}
|
||||||
|
return f().Generate(ctx, input, routeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeOptions(base *llm.Options, override llm.Options) {
|
||||||
|
if override.Model != "" {
|
||||||
|
base.Model = override.Model
|
||||||
|
}
|
||||||
|
if override.MaxTokens > 0 {
|
||||||
|
base.MaxTokens = override.MaxTokens
|
||||||
|
}
|
||||||
|
if override.Temperature != 0 {
|
||||||
|
base.Temperature = override.Temperature
|
||||||
|
}
|
||||||
|
if override.Timeout > 0 {
|
||||||
|
base.Timeout = override.Timeout
|
||||||
|
}
|
||||||
|
if len(override.Modalities) > 0 {
|
||||||
|
base.Modalities = override.Modalities
|
||||||
|
}
|
||||||
|
if override.SystemPrompt != "" {
|
||||||
|
base.SystemPrompt = override.SystemPrompt
|
||||||
|
}
|
||||||
|
if override.TopP != 0 {
|
||||||
|
base.TopP = override.TopP
|
||||||
|
}
|
||||||
|
if len(override.Stop) > 0 {
|
||||||
|
base.Stop = override.Stop
|
||||||
|
}
|
||||||
|
base.Stream = base.Stream || override.Stream
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/cloudwego/eino/compose"
|
||||||
|
"github.com/cloudwego/eino/schema"
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/domain/llm/pipeline"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisionService struct{ run compose.Runnable[[]*schema.Message, *schema.Message] }
|
||||||
|
|
||||||
|
func NewVisionService(ctx context.Context, cfg *config.Config) (*VisionService, error) {
|
||||||
|
r, err := pipeline.BuildVision(ctx, cfg)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return &VisionService{run: r}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VisionService) Invoke(ctx context.Context, msgs []*schema.Message) (*schema.Message, error) {
|
||||||
|
return s.run.Invoke(ctx, msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionAdapter 适配 impl.SessionImpl 到 SessionRepo 接口
|
||||||
|
type SessionAdapter struct {
|
||||||
|
impl *impl.SessionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSessionAdapter(impl *impl.SessionImpl) *SessionAdapter {
|
||||||
|
return &SessionAdapter{impl: impl}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionAdapter) GetUserName(ctx context.Context, sessionID string) (string, error) {
|
||||||
|
// 复用 SessionImpl 的查询能力
|
||||||
|
// 这里假设 sessionID 是唯一的,直接用 FindOne
|
||||||
|
session, has, err := s.impl.FindOne(s.impl.WithSessionId(sessionID))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return "", errors.New("session not found")
|
||||||
|
}
|
||||||
|
return session.UserName, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import "github.com/google/wire"
|
||||||
|
|
||||||
|
var ProviderSet = wire.NewSet(NewRepos)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/data/impl"
|
||||||
|
"ai_scheduler/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repos 聚合所有 Repository
|
||||||
|
type Repos struct {
|
||||||
|
Session SessionRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepos(sessionImpl *impl.SessionImpl, rdb *utils.Rdb) *Repos {
|
||||||
|
return &Repos{
|
||||||
|
Session: NewSessionAdapter(sessionImpl),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionRepo 定义会话相关的查询接口
|
||||||
|
// 这里只暴露 workflow 真正需要的方法,避免直接依赖 impl 层
|
||||||
|
type SessionRepo interface {
|
||||||
|
GetUserName(ctx context.Context, sessionID string) (string, error)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package goods_add
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
cfg config.ToolConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.ToolConfig) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(ctx context.Context, req *GoodsAddRequest) (*GoodsAddResponse, error) {
|
||||||
|
apiReq, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
r := l_request.Request{
|
||||||
|
Method: "Post",
|
||||||
|
Url: c.cfg.BaseURL,
|
||||||
|
Json: apiReq,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := r.Send()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type resType struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
Id int `json:"id"` // 商品 ID
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var resData resType
|
||||||
|
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析响应失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resData.Code != 200 {
|
||||||
|
return nil, fmt.Errorf("业务错误,%s", resData.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
toolResp := &GoodsAddResponse{
|
||||||
|
PreviewUrl: c.cfg.AddURL,
|
||||||
|
SpuCode: req.SpuCode,
|
||||||
|
Id: resData.Data.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return toolResp, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package goods_add
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_Call
|
||||||
|
func Test_Call(t *testing.T) {
|
||||||
|
req := &GoodsAddRequest{
|
||||||
|
Unit: "元",
|
||||||
|
IsComposeGoods: 2,
|
||||||
|
GoodsAttributes: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品规格参数</span></p>",
|
||||||
|
Introduction: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品卖点</span></p>",
|
||||||
|
GoodsIllustration: "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(255, 255, 255); font-size: 14px;\">商品说明</span></p>",
|
||||||
|
IsHot: 2,
|
||||||
|
Title: "fu测试001",
|
||||||
|
GoodsNum: "futest001sku",
|
||||||
|
SpuCode: "futest001spu",
|
||||||
|
SpuName: "fu测试001",
|
||||||
|
Price: 100,
|
||||||
|
SalesPrice: 80,
|
||||||
|
Discount: 15,
|
||||||
|
TaxRate: 13,
|
||||||
|
FreightId: 3,
|
||||||
|
Remark: "备注说明",
|
||||||
|
SellByDate: 180,
|
||||||
|
ExternalPrice: 120,
|
||||||
|
GoodsBarCode: "futest001code2",
|
||||||
|
GoodsCode: "futest001code1",
|
||||||
|
SellByDateUnit: "天",
|
||||||
|
BrandId: 3,
|
||||||
|
ExternalUrl: "https://www.baidu.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.ToolConfig{
|
||||||
|
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/add",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := New(cfg)
|
||||||
|
toolResp, err := client.Call(context.Background(), req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Call() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("toolResp: %v\n", toolResp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package goods_add
|
||||||
|
|
||||||
|
type GoodsAddRequest struct {
|
||||||
|
Title string `json:"title"` // 商品标题
|
||||||
|
GoodsCode string `json:"goods_code"` // 商品编码
|
||||||
|
SpuName string `json:"spu_name"` // SPU 名称
|
||||||
|
SpuCode string `json:"spu_code"` // SPU 编码
|
||||||
|
GoodsNum string `json:"goods_num"` // 商品货号
|
||||||
|
GoodsBarCode string `json:"goods_bar_code"` // 商品条形码
|
||||||
|
Price float64 `json:"price"` // 市场价
|
||||||
|
SalesPrice float64 `json:"sales_price"` // 建议销售价
|
||||||
|
ExternalPrice float64 `json:"external_price"` // 电商销售价格
|
||||||
|
Unit string `json:"unit"` // 价格单位
|
||||||
|
Discount int `json:"discount"` // 折扣
|
||||||
|
TaxRate int `json:"tax_rate"` // 税率
|
||||||
|
FreightId int `json:"freight_id"` // 运费模板 ID
|
||||||
|
SellByDate int `json:"sell_by_date"` // 保质期
|
||||||
|
SellByDateUnit string `json:"sell_by_date_unit"` // 保质期单位
|
||||||
|
BrandId int `json:"brand_id"` // 品牌 ID
|
||||||
|
IsHot int `json:"is_hot"` // 是否热销主推 1.是 2.否(默认)
|
||||||
|
ExternalUrl string `json:"external_url"` // 外部平台链接
|
||||||
|
Introduction string `json:"introduction"` // 商品卖点
|
||||||
|
GoodsAttributes string `json:"goods_attributes"` // 商品规格参数
|
||||||
|
GoodsIllustration string `json:"goods_illustration"` // 商品说明
|
||||||
|
Remark string `json:"remark"` // 备注说明
|
||||||
|
IsComposeGoods int `json:"is_compose_goods"` // 是否组合商品 1.是 2.否(默认)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoodsAddResponse struct {
|
||||||
|
PreviewUrl string `json:"preview_url"` // 预览URL
|
||||||
|
SpuCode string `json:"spu_code"` // SPU编码
|
||||||
|
Id int `json:"id"` // 商品ID
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package goods_brand_search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := GoodsBrandSearchRequest{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 1,
|
||||||
|
Search: SearchCondition{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
apiReq, _ := util.StructToMap(reqBody)
|
||||||
|
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: "Post",
|
||||||
|
Url: c.cfg.BaseURL,
|
||||||
|
Json: apiReq,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resData GoodsBrandSearchResponse
|
||||||
|
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||||
|
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resData.Code != 200 {
|
||||||
|
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resData.Data.List) == 0 {
|
||||||
|
return 0, fmt.Errorf("品牌不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个匹配的品牌ID
|
||||||
|
return resData.Data.List[0].ID, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package goods_brand_search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_Call
|
||||||
|
func Test_Call(t *testing.T) {
|
||||||
|
// 使用示例中的查询条件
|
||||||
|
name := "vivo"
|
||||||
|
|
||||||
|
cfg := config.ToolConfig{
|
||||||
|
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/goods/brand/list",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := New(cfg)
|
||||||
|
toolResp, err := client.Call(context.Background(), name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Call() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("toolResp (BrandID): %v\n", toolResp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package goods_brand_search
|
||||||
|
|
||||||
|
type GoodsBrandSearchRequest struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Search SearchCondition `json:"search"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchCondition struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoodsBrandSearchResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
List []BrandInfo `json:"list"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BrandInfo struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Logo string `json:"logo"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package goods_category_add
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
cfg config.ToolConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.ToolConfig) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(ctx context.Context, req *GoodsCategoryAddRequest) (bool, error) {
|
||||||
|
apiReq, _ := util.StructToMap(req)
|
||||||
|
|
||||||
|
r := l_request.Request{
|
||||||
|
Method: "Post",
|
||||||
|
Url: c.cfg.BaseURL,
|
||||||
|
Json: apiReq,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := r.Send()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resData GoodsCategoryAddResponse
|
||||||
|
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||||
|
return false, fmt.Errorf("解析响应失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resData.Code != 200 {
|
||||||
|
return false, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resData.Data.IsSuccess, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package goods_category_add
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_Call
|
||||||
|
func Test_Call(t *testing.T) {
|
||||||
|
req := &GoodsCategoryAddRequest{
|
||||||
|
GoodsId: 8496,
|
||||||
|
CategoryIds: []int{1667},
|
||||||
|
IsCover: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.ToolConfig{
|
||||||
|
BaseURL: "https://gateway.dev.cdlsxd.cn/goods-admin/api/v1/good/category/relation/add",
|
||||||
|
}
|
||||||
|
|
||||||
|
client := New(cfg)
|
||||||
|
toolResp, err := client.Call(context.Background(), req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Call() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("toolResp: %v\n", toolResp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package goods_category_add
|
||||||
|
|
||||||
|
type GoodsCategoryAddRequest struct {
|
||||||
|
GoodsId int `json:"goods_id"`
|
||||||
|
CategoryIds []int `json:"category_ids"`
|
||||||
|
IsCover bool `json:"is_cover"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoodsCategoryAddResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
IsSuccess bool `json:"is_success"` // 是否成功
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package goods_category_search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai_scheduler/internal/config"
|
||||||
|
"ai_scheduler/internal/pkg/l_request"
|
||||||
|
"ai_scheduler/internal/pkg/util"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := GoodsCategorySearchRequest{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 1,
|
||||||
|
Search: SearchCondition{
|
||||||
|
Name: name,
|
||||||
|
Level: 3, // 仅需三级分类
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
apiReq, _ := util.StructToMap(reqBody)
|
||||||
|
|
||||||
|
req := l_request.Request{
|
||||||
|
Method: "Post",
|
||||||
|
Url: c.cfg.BaseURL,
|
||||||
|
Json: apiReq,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("请求失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resData GoodsCategorySearchResponse
|
||||||
|
if err := json.Unmarshal([]byte(res.Text), &resData); err != nil {
|
||||||
|
return 0, fmt.Errorf("解析响应失败,err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resData.Code != 200 {
|
||||||
|
return 0, fmt.Errorf("业务错误,code: %d, msg: %s", resData.Code, resData.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resData.Data.List) == 0 {
|
||||||
|
return 0, fmt.Errorf("商品分类不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个匹配的分类ID
|
||||||
|
return resData.Data.List[0].ID, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package goods_category_search
|
||||||
|
|
||||||
|
type GoodsCategorySearchRequest struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Search SearchCondition `json:"search"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchCondition struct {
|
||||||
|
Name string `json:"full_name"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GoodsCategorySearchResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
List []CategoryInfo `json:"list"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategoryInfo struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue