feat: 主聊天流程接入工具调用 — Synthesizer 支持 ChatWithTools
Synthesizer 现在向 LLM 传递工具定义并通过 ChatWithTools 执行 工具调用循环(最多5轮),执行结果通过 ToolRegistry 记录到调用 日志。Orchestrator 通过 SetToolRegistry() 注入。用户聊天现在可以 触发 web_search 等工具,调用记录在 DevTools 监控页面可见。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -321,6 +321,7 @@ func main() {
|
||||
memRetriever,
|
||||
memExtractor,
|
||||
)
|
||||
orch.SetToolRegistry(toolRegistry)
|
||||
log.Println("对话编排器 v2.0 已就绪")
|
||||
_ = orch
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/bus"
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/scheduler"
|
||||
|
||||
plgManager "github.com/yourname/cyrene-ai/pkg/plugins/manager"
|
||||
)
|
||||
|
||||
// Orchestrator 对话编排器 v2.0
|
||||
@@ -35,6 +37,7 @@ type Orchestrator struct {
|
||||
enrichmentStore *SessionEnrichmentStore
|
||||
msgScheduler *scheduler.MessageScheduler
|
||||
emotionTracker *persona.EmotionTracker
|
||||
toolRegistry *plgManager.ToolRegistry
|
||||
}
|
||||
|
||||
// SetResponseCache sets the response cache (optional, for Phase 0.2).
|
||||
@@ -62,6 +65,12 @@ func (o *Orchestrator) SetEmotionTracker(t *persona.EmotionTracker) {
|
||||
o.emotionTracker = t
|
||||
}
|
||||
|
||||
// SetToolRegistry sets the tool registry for tool-calling support in the main chat flow.
|
||||
func (o *Orchestrator) SetToolRegistry(tr *plgManager.ToolRegistry) {
|
||||
o.toolRegistry = tr
|
||||
o.synthesizer.toolRegistry = tr
|
||||
}
|
||||
|
||||
// getBus returns the bus or a nop fallback.
|
||||
func (o *Orchestrator) getBus() bus.Bus {
|
||||
if o.eventBus == nil {
|
||||
@@ -87,7 +96,7 @@ func NewOrchestrator(
|
||||
llmAdapter: chatAdapter,
|
||||
subManager: subManager,
|
||||
intentAnalyzer: NewIntentAnalyzer(intentAdapter),
|
||||
synthesizer: NewSynthesizer(chatAdapter),
|
||||
synthesizer: NewSynthesizer(chatAdapter, nil),
|
||||
memoryRetriever: memoryRetriever,
|
||||
memoryExtractor: memoryExtractor,
|
||||
}
|
||||
|
||||
@@ -2,24 +2,29 @@ package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"strings"
|
||||
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/llm"
|
||||
"github.com/yourname/cyrene-ai/ai-core/internal/model"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
plgManager "github.com/yourname/cyrene-ai/pkg/plugins/manager"
|
||||
plgSDK "github.com/yourname/cyrene-ai/pkg/plugins/sdk"
|
||||
)
|
||||
|
||||
// Synthesizer 主会话综合器
|
||||
// 汇总子会话结果,生成最终回复
|
||||
type Synthesizer struct {
|
||||
llmAdapter *llm.Adapter
|
||||
llmAdapter *llm.Adapter
|
||||
toolRegistry *plgManager.ToolRegistry
|
||||
}
|
||||
|
||||
// NewSynthesizer 创建综合器
|
||||
func NewSynthesizer(llmAdapter *llm.Adapter) *Synthesizer {
|
||||
func NewSynthesizer(llmAdapter *llm.Adapter, toolRegistry *plgManager.ToolRegistry) *Synthesizer {
|
||||
return &Synthesizer{
|
||||
llmAdapter: llmAdapter,
|
||||
llmAdapter: llmAdapter,
|
||||
toolRegistry: toolRegistry,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +51,73 @@ func (s *Synthesizer) Synthesize(ctx context.Context, params SynthesizeParams) (
|
||||
|
||||
logger.Printf("[synthesizer] 开始综合 (上下文 %d 条消息)", len(messages))
|
||||
|
||||
// 流式调用 LLM
|
||||
return s.llmAdapter.ChatStream(ctx, messages)
|
||||
openAITools := s.buildOpenAITools()
|
||||
if len(openAITools) == 0 {
|
||||
return s.llmAdapter.ChatStream(ctx, messages)
|
||||
}
|
||||
|
||||
resp, err := s.llmAdapter.ChatWithTools(ctx, messages, openAITools)
|
||||
if err != nil {
|
||||
logger.Printf("[synthesizer] ChatWithTools 失败: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxRounds := 5
|
||||
for round := 0; len(resp.ToolCalls) > 0 && round < maxRounds; round++ {
|
||||
logger.Printf("[synthesizer] LLM 请求 %d 个工具调用 (round=%d)", len(resp.ToolCalls), round)
|
||||
|
||||
messages = append(messages, model.LLMMessage{
|
||||
Role: model.RoleAssistant,
|
||||
Content: resp.Content,
|
||||
ToolCalls: resp.ToolCalls,
|
||||
ReasoningContent: resp.ReasoningContent,
|
||||
})
|
||||
|
||||
for _, tc := range resp.ToolCalls {
|
||||
var args map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(tc.Arguments), &args); err != nil {
|
||||
logger.Printf("[synthesizer] 工具 %s 参数解析失败: %v", tc.Name, err)
|
||||
args = make(map[string]interface{})
|
||||
}
|
||||
|
||||
result, execErr := s.toolRegistry.Execute(ctx, tc.Name, args)
|
||||
if execErr != nil {
|
||||
logger.Printf("[synthesizer] 工具 %s 执行失败: %v", tc.Name, execErr)
|
||||
}
|
||||
if result == nil {
|
||||
result = &plgSDK.ToolResult{ToolName: tc.Name, Success: false, Error: execErr.Error()}
|
||||
}
|
||||
|
||||
resultJSON, _ := json.Marshal(result)
|
||||
messages = append(messages, model.LLMMessage{
|
||||
Role: model.RoleTool,
|
||||
Content: string(resultJSON),
|
||||
ToolCallID: tc.ID,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err = s.llmAdapter.ChatWithTools(ctx, messages, openAITools)
|
||||
if err != nil {
|
||||
logger.Printf("[synthesizer] ChatWithTools 失败 (round=%d): %v", round+1, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
finalContent := resp.Content
|
||||
ch := make(chan llm.StreamChunk, 200)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
runes := []rune(finalContent)
|
||||
for i := 0; i < len(runes); i += 3 {
|
||||
end := i + 3
|
||||
if end > len(runes) {
|
||||
end = len(runes)
|
||||
}
|
||||
ch <- llm.StreamChunk{Content: string(runes[i:end])}
|
||||
}
|
||||
ch <- llm.StreamChunk{Done: true}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// buildSynthesizeMessages 构建综合用的 LLM 消息列表
|
||||
@@ -119,6 +189,29 @@ func (s *Synthesizer) buildSynthesizeMessages(params SynthesizeParams) []model.L
|
||||
return messages
|
||||
}
|
||||
|
||||
// buildOpenAITools 将工具注册中心的定义转换为 LLM 工具格式
|
||||
func (s *Synthesizer) buildOpenAITools() []llm.OpenAITool {
|
||||
if s.toolRegistry == nil || !s.toolRegistry.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
defs := s.toolRegistry.Definitions()
|
||||
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
|
||||
}
|
||||
|
||||
// AggregateResults 汇总子会话结果
|
||||
func AggregateResults(results []model.SubSessionResult) *AggregatedContext {
|
||||
agg := &AggregatedContext{
|
||||
|
||||
Reference in New Issue
Block a user