feat(ai-core): wire function calling into the LLM pipeline

- llm: ChatStreamWithTools sends OpenAI-format tools; streamed tool_call
  deltas are reassembled by index into complete ToolCalls on the Done token
- model: LLMMessage gains ToolCallID/ToolCalls; StreamToken gains ToolCalls
- orchestrator: new ToolExecutor interface and streamWithTools loop —
  execute requested tools, feed results back, up to 3 rounds, final round
  forces a plain text answer; normal chats still stream token by token.
  Also adds Pipeline.FinishRound (assistant reply recording + post-round
  hook), wired to transports in the next commit.
- main: inject the plugin registry via toolExecAdapter

Includes an httptest-based test covering delta reassembly, tool
execution, and result feedback.
This commit is contained in:
Aska committed 2026-08-20 19:40:58 +08:00
1 parent 9a3c77dbd3
commit 7c02d31194
5 files changed
+384 -11

No files matched your search

+17
View File
@@ -193,6 +193,7 @@ func main() {
memExtractor := memory.NewExtractor(memSvc, &memory.LLMAdapterBridge{Adapter: memAdapter})
orch.SetMemoryExtractor(memExtractor)
}
orch.SetToolExecutor(&toolExecAdapter{reg: reg})
logger.Infof("[orchestrator] pipeline ready")
// === 11.5 Platform Bridge ===
@@ -330,6 +331,22 @@ func (a *perfAdapter) History() []map[string]any {
return result
}
// toolExecAdapter adapts the plugin registry to orchestrator.ToolExecutor.
type toolExecAdapter struct{ reg *plugin.Registry }
func (t *toolExecAdapter) Tools() []llm.ToolSpec {
defs := t.reg.GetAllToolDefinitions()
specs := make([]llm.ToolSpec, len(defs))
for i, d := range defs {
specs[i] = llm.ToolSpec{Name: d.Name, Description: d.Description, Parameters: d.Parameters}
}
return specs
}
func (t *toolExecAdapter) Execute(ctx context.Context, name string, params map[string]any) (string, error) {
return t.reg.ExecuteTool(ctx, name, params)
}
// bridgeConfigAdapter adapts bridge.ConfigStore to api.BridgeConfigManager.
type bridgeConfigAdapter struct{ *bridge.ConfigStore }
func (a *bridgeConfigAdapter) GetAdapters() []map[string]any {