feat: 昔涟工具扩展 — OpenAI Function Calling 集成 (网络搜索/网页抓取/IoT设备查询)

This commit is contained in:
2026-05-16 23:12:39 +08:00
parent 7f2961e63e
commit 1f5c2508d6
9 changed files with 1081 additions and 26 deletions
+99 -4
View File
@@ -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