feat: 昔涟工具扩展 — OpenAI Function Calling 集成 (网络搜索/网页抓取/IoT设备查询)
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -96,6 +97,17 @@ func main() {
|
||||
log.Println("IoT 客户端未配置 (IOT_DEBUG_SERVICE_URL 为空)")
|
||||
}
|
||||
|
||||
// 初始化工具注册中心
|
||||
toolRegistry := tools.NewRegistry()
|
||||
if getEnvBool("ENABLE_TOOLS", true) {
|
||||
toolRegistry.Register(tools.NewWebFetchTool())
|
||||
toolRegistry.Register(tools.NewWebSearchTool())
|
||||
if iotClient != nil {
|
||||
toolRegistry.Register(tools.NewIoTQueryTool(iotClient))
|
||||
}
|
||||
log.Printf("工具注册中心已就绪: %d 个工具 (%v)", len(toolRegistry.ListTools()), toolRegistry.ListTools())
|
||||
}
|
||||
|
||||
// 初始化后台思考器
|
||||
thinkerCfg := background.DefaultThinkerConfig()
|
||||
thinker := background.NewThinker(thinkerCfg, personaLoader, memRetriever, llmAdapter, iotClient)
|
||||
@@ -110,7 +122,7 @@ func main() {
|
||||
|
||||
// 注册对话API端点
|
||||
mux.HandleFunc("/api/v1/chat", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleChat(w, r, orch, ctxBuilder, llmAdapter, personaLoader, memRetriever, memExtractor, iotClient, thinker)
|
||||
handleChat(w, r, orch, ctxBuilder, llmAdapter, personaLoader, memRetriever, memExtractor, iotClient, thinker, toolRegistry)
|
||||
})
|
||||
|
||||
// 注册记忆API端点
|
||||
@@ -195,7 +207,45 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// handleChat 处理对话请求(SSE 流式响应)
|
||||
func getEnvBool(key string, fallback bool) bool {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return fallback
|
||||
}
|
||||
switch strings.ToLower(v) {
|
||||
case "true", "1", "yes", "on":
|
||||
return true
|
||||
case "false", "0", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
// buildOpenAITools 将工具注册中心的定义转换为 LLM 层的 OpenAITool 格式
|
||||
func buildOpenAITools(registry *tools.Registry) []llm.OpenAITool {
|
||||
if registry == nil || !registry.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
defs := registry.GetDefinitions()
|
||||
if len(defs) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]llm.OpenAITool, 0, len(defs))
|
||||
for _, d := range defs {
|
||||
result = append(result, llm.OpenAITool{
|
||||
Type: "function",
|
||||
Function: llm.OpenAIToolFunc{
|
||||
Name: d.Name,
|
||||
Description: d.Description,
|
||||
Parameters: d.Parameters,
|
||||
},
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// handleChat 处理对话请求(SSE 流式响应 + 工具调用)
|
||||
func handleChat(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
@@ -207,6 +257,7 @@ func handleChat(
|
||||
memExtractor *memory.Extractor,
|
||||
iotClient *tools.IoTClient,
|
||||
thinker *background.Thinker,
|
||||
toolRegistry *tools.Registry,
|
||||
) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -323,7 +374,52 @@ func handleChat(
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 调用LLM流式接口
|
||||
// 5. 准备工具定义
|
||||
openAITools := buildOpenAITools(toolRegistry)
|
||||
|
||||
// 5.1 如果启用了工具,先进行同步调用检测是否需要工具调用
|
||||
if len(openAITools) > 0 {
|
||||
log.Printf("[chat] 启用工具调用: %d 个工具可用", len(openAITools))
|
||||
|
||||
syncResp, syncErr := llmAdapter.ChatWithTools(ctx, llmMessages, openAITools)
|
||||
if syncErr != nil {
|
||||
log.Printf("[chat] 工具检测调用失败: %v,降级为普通对话", syncErr)
|
||||
} else if len(syncResp.ToolCalls) > 0 {
|
||||
log.Printf("[chat] 模型请求 %d 个工具调用", len(syncResp.ToolCalls))
|
||||
|
||||
// 将助手消息(含工具调用)加入上下文
|
||||
assistantMsg := model.LLMMessage{
|
||||
Role: model.RoleAssistant,
|
||||
Content: syncResp.Content,
|
||||
ToolCalls: syncResp.ToolCalls,
|
||||
}
|
||||
llmMessages = append(llmMessages, assistantMsg)
|
||||
|
||||
// 执行每个工具调用并将结果加入上下文
|
||||
for _, tc := range syncResp.ToolCalls {
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil {
|
||||
log.Printf("[chat] 工具 %s 参数解析失败: %v", tc.Name, err)
|
||||
args = make(map[string]interface{})
|
||||
}
|
||||
|
||||
result, execErr := toolRegistry.Execute(ctx, tc.Name, args)
|
||||
if execErr != nil {
|
||||
log.Printf("[chat] 工具 %s 执行失败: %v", tc.Name, execErr)
|
||||
}
|
||||
|
||||
resultJSON, _ := json.Marshal(result)
|
||||
llmMessages = append(llmMessages, model.LLMMessage{
|
||||
Role: model.RoleTool,
|
||||
Content: string(resultJSON),
|
||||
ToolCallID: tc.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
// 无论是否有工具调用,继续流式输出最终回复
|
||||
}
|
||||
|
||||
// 5.2 调用LLM流式接口(可能已附加工具结果)
|
||||
chunkCh, err := llmAdapter.ChatStream(ctx, llmMessages)
|
||||
if err != nil {
|
||||
// 流式初始化失败,返回 SSE 格式错误
|
||||
@@ -336,7 +432,6 @@ func handleChat(
|
||||
}
|
||||
|
||||
messageID := fmt.Sprintf("msg-%d", time.Now().UnixNano())
|
||||
|
||||
// 6. 逐 token 推送 SSE
|
||||
var fullContent string
|
||||
var segments []llm.Segment
|
||||
|
||||
Reference in New Issue
Block a user