重构:把剩余 12 个顶层包全部迁到 internal/
继续之前的重构:把根目录残留的非数据/资源类子目录(agents、agenttool、ai、 autoreply、completion、conversation、llm、message、mqtpp、stream、 toolmanager、toolrouter)一并搬到 internal/ 下,并把全工程引用它们的 import 路径从 "meshtastic_mqtt_server/<x>" 重写为 "meshtastic_mqtt_server/internal/<x>"。 变更 - git mv 12 个顶层目录进 internal/,无函数体改动。 - 全工程 sed 把 import path 加上 internal/ 前缀,包括 main.go、 internal/bot/bot_service.go、internal/store/bot_store.go 中对 mqtpp 的引用,以及 ai 子系统内部 agents/agenttool/autoreply/conversation/ llm/toolmanager/toolrouter 之间的相互引用。 验证 - go build ./... 通过;go test ./... 全部包通过。 - AI 自动回复链路(main → ai.NewService → autoreply.Service → botSender) 保持不变,仅 import 路径调整。 - 根目录最终只剩 main.go / main_test.go / internal/ 加 go.mod / go.sum 与数据资源目录(dist、doc、firmware、py、win、meshmap_frontend)。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
package toolrouter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"meshtastic_mqtt_server/internal/completion"
|
||||
"meshtastic_mqtt_server/internal/llm"
|
||||
"meshtastic_mqtt_server/internal/message"
|
||||
"meshtastic_mqtt_server/internal/stream"
|
||||
"meshtastic_mqtt_server/internal/toolmanager"
|
||||
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||
)
|
||||
|
||||
const maxAgentToolIterations = 6
|
||||
|
||||
// RunAgentToolLoop runs the agent tool calling loop
|
||||
// systemPrompt is the primary system prompt from LLM config
|
||||
func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, systemPrompt string, chatMessages []message.ChatMessage, manager *toolmanager.Manager, emit stream.EmitFunc) ([]*model.ChatCompletionMessage, error) {
|
||||
finalMessages, err := buildArkMessages(chatMessages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routerProfile := profile
|
||||
if state != nil {
|
||||
routerProfile = state.RouterProfile(profile)
|
||||
}
|
||||
tools := availableAgentTools(state, routerProfile, manager, emit)
|
||||
if len(tools) == 0 {
|
||||
// No tools available, add system prompt and return
|
||||
if strings.TrimSpace(systemPrompt) != "" {
|
||||
systemMessage := &model.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &systemPrompt,
|
||||
},
|
||||
}
|
||||
finalMessages = append([]*model.ChatCompletionMessage{systemMessage}, finalMessages...)
|
||||
}
|
||||
return finalMessages, nil
|
||||
}
|
||||
|
||||
decisionMessages := append([]*model.ChatCompletionMessage(nil), finalMessages...)
|
||||
|
||||
toolByName := make(map[string]AgentTool, len(tools))
|
||||
definitions := make([]*model.Tool, 0, len(tools))
|
||||
availableNames := make([]string, 0, len(tools))
|
||||
toolDescriptions := make([]string, 0, len(tools))
|
||||
for _, tool := range tools {
|
||||
toolByName[tool.name] = tool
|
||||
definitions = append(definitions, tool.definition)
|
||||
availableNames = append(availableNames, tool.name)
|
||||
if tool.definition != nil && tool.definition.Function != nil {
|
||||
toolDescriptions = append(toolDescriptions, fmt.Sprintf("%s: %s", tool.name, tool.definition.Function.Description))
|
||||
}
|
||||
}
|
||||
if emit != nil {
|
||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "prepare", Status: "success", Message: "已准备可用工具", Data: map[string]any{"tools": availableNames, "tool_descriptions": toolDescriptions}})
|
||||
}
|
||||
if state == nil || state.cfg == nil {
|
||||
// No tool router config, but we have tools - use primary system prompt
|
||||
if strings.TrimSpace(systemPrompt) != "" {
|
||||
systemMessage := &model.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &systemPrompt,
|
||||
},
|
||||
}
|
||||
finalMessages = append([]*model.ChatCompletionMessage{systemMessage}, finalMessages...)
|
||||
decisionMessages = append([]*model.ChatCompletionMessage{systemMessage}, decisionMessages...)
|
||||
}
|
||||
return finalMessages, nil
|
||||
}
|
||||
// Use tool router system prompt if available, otherwise fall back to primary system prompt
|
||||
prompt := strings.TrimSpace(state.cfg.SystemPrompt)
|
||||
if prompt == "" {
|
||||
prompt = strings.TrimSpace(systemPrompt)
|
||||
}
|
||||
if prompt != "" {
|
||||
systemMessage := &model.ChatCompletionMessage{
|
||||
Role: "system",
|
||||
Content: &model.ChatCompletionMessageContent{
|
||||
StringValue: &prompt,
|
||||
},
|
||||
}
|
||||
finalMessages = append([]*model.ChatCompletionMessage{systemMessage}, finalMessages...)
|
||||
decisionMessages = append([]*model.ChatCompletionMessage{systemMessage}, decisionMessages...)
|
||||
}
|
||||
for i := 0; i < maxAgentToolIterations; i++ {
|
||||
if emit != nil {
|
||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "request", Status: "running", Message: fmt.Sprintf("正在进行第 %d 轮工具判断", i+1), Data: map[string]any{"iteration": i + 1, "max_iterations": maxAgentToolIterations, "tools": availableNames}})
|
||||
}
|
||||
resp, err := completion.CompleteChat(ctx, routerProfile, model.CreateChatCompletionRequest{
|
||||
Model: routerProfile.Config.Model,
|
||||
Messages: decisionMessages,
|
||||
MaxTokens: &state.cfg.MaxTokens,
|
||||
Tools: definitions,
|
||||
ToolChoice: model.ToolChoiceStringTypeAuto,
|
||||
ParallelToolCalls: BoolPtr(false),
|
||||
}, time.Duration(state.cfg.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return finalMessages, err
|
||||
}
|
||||
if tracker := stream.TrackerFromContext(ctx); tracker != nil {
|
||||
tracker.AddTool(resp.Usage.PromptTokens, resp.Usage.CompletionTokens)
|
||||
}
|
||||
if len(resp.Choices) == 0 {
|
||||
return finalMessages, nil
|
||||
}
|
||||
choice := resp.Choices[0]
|
||||
if emit != nil {
|
||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "decision", Status: "success", Message: "工具判断响应已返回", Data: map[string]any{"iteration": i + 1}})
|
||||
}
|
||||
calls := choice.Message.ToolCalls
|
||||
if len(calls) == 0 && choice.Message.FunctionCall != nil {
|
||||
calls = []*model.ToolCall{{ID: "legacy_function_call", Type: model.ToolTypeFunction, Function: *choice.Message.FunctionCall}}
|
||||
}
|
||||
if len(calls) == 0 {
|
||||
if emit != nil {
|
||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "request", Status: "success", Message: "模型未请求工具,进入回答生成"})
|
||||
}
|
||||
return finalMessages, nil
|
||||
}
|
||||
callNames := make([]string, 0, len(calls))
|
||||
for _, call := range calls {
|
||||
if call != nil {
|
||||
callNames = append(callNames, call.Function.Name)
|
||||
}
|
||||
}
|
||||
if emit != nil {
|
||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "tool_calls", Status: "running", Message: fmt.Sprintf("模型请求调用 %d 个工具", len(calls)), Data: map[string]any{"tools": callNames, "iteration": i + 1}})
|
||||
}
|
||||
assistantMessage := &model.ChatCompletionMessage{Role: "assistant", ToolCalls: calls, Content: choice.Message.Content}
|
||||
finalMessages = append(finalMessages, assistantMessage)
|
||||
decisionMessages = append(decisionMessages, assistantMessage)
|
||||
for _, call := range calls {
|
||||
result := ExecuteAgentToolCall(ctx, call, toolByName, emit)
|
||||
resultContent := &model.ChatCompletionMessageContent{StringValue: &result}
|
||||
toolMessage := &model.ChatCompletionMessage{Role: "tool", ToolCallID: call.ID, Content: resultContent}
|
||||
finalMessages = append(finalMessages, toolMessage)
|
||||
decisionMessages = append(decisionMessages, toolMessage)
|
||||
}
|
||||
}
|
||||
limitText := "工具调用轮数已达到上限。请基于已有工具结果回答,并说明可能未完成全部工具调用。"
|
||||
limitMessage := &model.ChatCompletionMessage{Role: "system", Content: &model.ChatCompletionMessageContent{StringValue: &limitText}}
|
||||
finalMessages = append(finalMessages, limitMessage)
|
||||
return finalMessages, nil
|
||||
}
|
||||
|
||||
func buildArkMessages(chatMessages []message.ChatMessage) ([]*model.ChatCompletionMessage, error) {
|
||||
messages := make([]*model.ChatCompletionMessage, 0, len(chatMessages))
|
||||
for _, msg := range chatMessages {
|
||||
role := msg.Role
|
||||
if role == "" {
|
||||
role = "user"
|
||||
}
|
||||
content := &model.ChatCompletionMessageContent{StringValue: &msg.Content}
|
||||
messages = append(messages, &model.ChatCompletionMessage{
|
||||
Role: role,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// BoolPtr returns a pointer to the given bool
|
||||
func BoolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// IntPtr returns a pointer to the given int
|
||||
func IntPtr(i int) *int {
|
||||
return &i
|
||||
}
|
||||
Reference in New Issue
Block a user