写成屎山了
This commit is contained in:
+8
-7
@@ -18,17 +18,18 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemPromptStore is the interface for getting the system prompt
|
// ToolConfigStore is the interface for getting tool configuration
|
||||||
type SystemPromptStore interface {
|
type ToolConfigStore interface {
|
||||||
GetLLMPrimaryConfigSystemPrompt() (string, error)
|
GetLLMPrimaryConfigSystemPrompt() (string, error)
|
||||||
|
GetLLMPrimaryConfigEnableTool() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config holds the AI service configuration
|
// Config holds the AI service configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LLMProviders []llm.ProviderConfig
|
LLMProviders []llm.ProviderConfig
|
||||||
DataDir string
|
DataDir string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
SystemPromptStore SystemPromptStore
|
ToolConfigStore ToolConfigStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service manages all AI-related components
|
// Service manages all AI-related components
|
||||||
@@ -97,7 +98,7 @@ func NewService(cfg Config, db *gorm.DB, botSender autoreply.BotSender) (*Servic
|
|||||||
convStore,
|
convStore,
|
||||||
msgQueue,
|
msgQueue,
|
||||||
botSender,
|
botSender,
|
||||||
cfg.SystemPromptStore,
|
cfg.ToolConfigStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
|
|||||||
+68
-26
@@ -14,6 +14,8 @@ import (
|
|||||||
"meshtastic_mqtt_server/message"
|
"meshtastic_mqtt_server/message"
|
||||||
"meshtastic_mqtt_server/toolmanager"
|
"meshtastic_mqtt_server/toolmanager"
|
||||||
"meshtastic_mqtt_server/toolrouter"
|
"meshtastic_mqtt_server/toolrouter"
|
||||||
|
|
||||||
|
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -63,20 +65,21 @@ type BotSender interface {
|
|||||||
SendChannelText(ctx context.Context, botID uint64, channelID string, text string) error
|
SendChannelText(ctx context.Context, botID uint64, channelID string, text string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemPromptStore is the interface for getting the system prompt
|
// ToolConfigStore is the interface for getting tool configuration
|
||||||
type SystemPromptStore interface {
|
type ToolConfigStore interface {
|
||||||
GetLLMPrimaryConfigSystemPrompt() (string, error)
|
GetLLMPrimaryConfigSystemPrompt() (string, error)
|
||||||
|
GetLLMPrimaryConfigEnableTool() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service manages automatic AI replies for bots
|
// Service manages automatic AI replies for bots
|
||||||
type Service struct {
|
type Service struct {
|
||||||
llmState *llm.State
|
llmState *llm.State
|
||||||
toolRouter *toolrouter.State
|
toolRouter *toolrouter.State
|
||||||
toolMgr *toolmanager.Manager
|
toolMgr *toolmanager.Manager
|
||||||
convStore *conversation.Store
|
convStore *conversation.Store
|
||||||
msgQueue MessageQueue
|
msgQueue MessageQueue
|
||||||
botSender BotSender
|
botSender BotSender
|
||||||
systemPromptStore SystemPromptStore
|
toolConfigStore ToolConfigStore
|
||||||
|
|
||||||
running bool
|
running bool
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -92,16 +95,16 @@ func NewService(
|
|||||||
convStore *conversation.Store,
|
convStore *conversation.Store,
|
||||||
msgQueue MessageQueue,
|
msgQueue MessageQueue,
|
||||||
botSender BotSender,
|
botSender BotSender,
|
||||||
systemPromptStore SystemPromptStore,
|
toolConfigStore ToolConfigStore,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
llmState: llmState,
|
llmState: llmState,
|
||||||
toolRouter: toolRouter,
|
toolRouter: toolRouter,
|
||||||
toolMgr: toolMgr,
|
toolMgr: toolMgr,
|
||||||
convStore: convStore,
|
convStore: convStore,
|
||||||
msgQueue: msgQueue,
|
msgQueue: msgQueue,
|
||||||
botSender: botSender,
|
botSender: botSender,
|
||||||
systemPromptStore: systemPromptStore,
|
toolConfigStore: toolConfigStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,22 +256,61 @@ func (s *Service) processMessage(ctx context.Context, msg QueuedMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get system prompt from primary config
|
// Get system prompt and tool enable flag from primary config
|
||||||
var systemPrompt string
|
var systemPrompt string
|
||||||
if s.systemPromptStore != nil {
|
enableTool := false
|
||||||
systemPrompt, err = s.systemPromptStore.GetLLMPrimaryConfigSystemPrompt()
|
if s.toolConfigStore != nil {
|
||||||
|
systemPrompt, err = s.toolConfigStore.GetLLMPrimaryConfigSystemPrompt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printJSON(map[string]any{"event": "llm_system_prompt_warning", "msg_id": msg.ID, "error": err.Error()})
|
printJSON(map[string]any{"event": "llm_system_prompt_warning", "msg_id": msg.ID, "error": err.Error()})
|
||||||
}
|
}
|
||||||
|
enableTool, err = s.toolConfigStore.GetLLMPrimaryConfigEnableTool()
|
||||||
|
if err != nil {
|
||||||
|
printJSON(map[string]any{"event": "llm_enable_tool_warning", "msg_id": msg.ID, "error": err.Error()})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the tool loop to get augmented messages
|
// Print tool manager status for debugging
|
||||||
augmentedMessages, err := toolrouter.RunAgentToolLoop(procCtx, s.toolRouter, profile, conv.Messages, s.toolMgr, nil)
|
toolCount := 0
|
||||||
_ = augmentedMessages // We'll use this in the future with proper tool support
|
if s.toolMgr != nil {
|
||||||
|
tools := s.toolMgr.Tools()
|
||||||
|
toolCount = len(tools)
|
||||||
|
toolNames := make([]string, 0, toolCount)
|
||||||
|
for _, t := range tools {
|
||||||
|
toolNames = append(toolNames, t.Name())
|
||||||
|
}
|
||||||
|
printJSON(map[string]any{
|
||||||
|
"event": "llm_tool_manager_status",
|
||||||
|
"msg_id": msg.ID,
|
||||||
|
"tool_count": toolCount,
|
||||||
|
"tool_names": toolNames,
|
||||||
|
"enable_tool": enableTool,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// For now, use simple completion since we don't have tools registered yet
|
// Run the tool loop to get augmented messages - pass system prompt to tool router
|
||||||
printJSON(map[string]any{"event": "llm_process_completion_start", "msg_id": msg.ID, "has_system_prompt": systemPrompt != ""})
|
// Tool loop will handle system prompt and tool calling
|
||||||
reply, err := completion.CompleteText(procCtx, profile, systemPrompt, conv.Messages, 512)
|
var augmentedMessages []*model.ChatCompletionMessage
|
||||||
|
if enableTool && toolCount > 0 {
|
||||||
|
augmentedMessages, err = toolrouter.RunAgentToolLoop(procCtx, s.toolRouter, profile, systemPrompt, conv.Messages, s.toolMgr, nil)
|
||||||
|
if err != nil {
|
||||||
|
printJSON(map[string]any{"event": "llm_tool_loop_warning", "msg_id": msg.ID, "error": err.Error()})
|
||||||
|
// Continue with original messages if tool loop fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(map[string]any{"event": "llm_process_completion_start", "msg_id": msg.ID, "has_system_prompt": systemPrompt != "", "augmented_messages": len(augmentedMessages)})
|
||||||
|
|
||||||
|
// Use augmented messages from tool loop (already includes system prompt and tool results)
|
||||||
|
// If augmented messages is empty or nil, fallback to original messages with system prompt
|
||||||
|
var reply string
|
||||||
|
if len(augmentedMessages) > 0 {
|
||||||
|
// Use augmented messages from tool loop (already converted to model.ChatCompletionMessage)
|
||||||
|
reply, err = completion.CompleteTextWithArkMessages(procCtx, profile, augmentedMessages, 512)
|
||||||
|
} else {
|
||||||
|
// Fallback to simple completion
|
||||||
|
reply, err = completion.CompleteText(procCtx, profile, systemPrompt, conv.Messages, 512)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Sprintf("LLM completion failed: %v", err)
|
errMsg := fmt.Sprintf("LLM completion failed: %v", err)
|
||||||
printJSON(map[string]any{"event": "llm_process_failed", "msg_id": msg.ID, "step": "llm_completion", "error": errMsg})
|
printJSON(map[string]any{"event": "llm_process_failed", "msg_id": msg.ID, "step": "llm_completion", "error": errMsg})
|
||||||
|
|||||||
@@ -85,3 +85,31 @@ func CompleteText(ctx context.Context, profile *llm.Profile, systemPrompt string
|
|||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompleteTextWithArkMessages completes a text prompt using already converted Ark messages
|
||||||
|
// This is used when messages have already been converted (e.g. after tool loop)
|
||||||
|
func CompleteTextWithArkMessages(ctx context.Context, profile *llm.Profile, arkMessages []*model.ChatCompletionMessage, maxTokens int) (string, error) {
|
||||||
|
if profile == nil || profile.Client == nil {
|
||||||
|
return "", fmt.Errorf("llm profile or client is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := model.CreateChatCompletionRequest{
|
||||||
|
Model: profile.Config.Model,
|
||||||
|
Messages: arkMessages,
|
||||||
|
MaxTokens: &maxTokens,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := profile.Client.CreateChatCompletion(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("text completion failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Choices) == 0 {
|
||||||
|
return "", fmt.Errorf("no completion choices returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Choices[0].Message.Content != nil && resp.Choices[0].Message.Content.StringValue != nil {
|
||||||
|
return *resp.Choices[0].Message.Content.StringValue, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -170,6 +170,18 @@ func (s *store) GetLLMPrimaryConfigSystemPrompt() (string, error) {
|
|||||||
return record.SystemPrompt, nil
|
return record.SystemPrompt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLLMPrimaryConfigEnableTool 获取是否启用工具调用
|
||||||
|
func (s *store) GetLLMPrimaryConfigEnableTool() (bool, error) {
|
||||||
|
record, err := s.GetLLMPrimaryConfig()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return record.EnableTool, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateLLMPrimaryConfig 创建主 AI 回复配置
|
// CreateLLMPrimaryConfig 创建主 AI 回复配置
|
||||||
func (s *store) CreateLLMPrimaryConfig(record *llmPrimaryConfigRecord) error {
|
func (s *store) CreateLLMPrimaryConfig(record *llmPrimaryConfigRecord) error {
|
||||||
if err := s.db.Create(record).Error; err != nil {
|
if err := s.db.Create(record).Error; err != nil {
|
||||||
|
|||||||
@@ -297,10 +297,10 @@ func run(cfg *config) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
aiService, err = ai.NewService(ai.Config{
|
aiService, err = ai.NewService(ai.Config{
|
||||||
LLMProviders: providerConfigs,
|
LLMProviders: providerConfigs,
|
||||||
DataDir: cfg.DataDir,
|
DataDir: cfg.DataDir,
|
||||||
Enabled: cfg.AI.Enabled,
|
Enabled: cfg.AI.Enabled,
|
||||||
SystemPromptStore: store,
|
ToolConfigStore: store,
|
||||||
}, store.db, botSenderAdapter)
|
}, store.db, botSenderAdapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: failed to initialize AI service: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: failed to initialize AI service: %v\n", err)
|
||||||
|
|||||||
+37
-5
@@ -18,17 +18,21 @@ type Manager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load loads tools from the given directory
|
// Load loads tools from the given directory
|
||||||
|
// If directory doesn't exist or is empty, automatically loads all registered tools
|
||||||
func Load(root string, options agenttool.LoadOptions) (*Manager, error) {
|
func Load(root string, options agenttool.LoadOptions) (*Manager, error) {
|
||||||
|
manager := &Manager{tools: map[string]agenttool.LoadedTool{}}
|
||||||
|
|
||||||
|
// Try to read directory
|
||||||
entries, err := os.ReadDir(root)
|
entries, err := os.ReadDir(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Directory doesn't exist, create empty manager
|
if !os.IsNotExist(err) {
|
||||||
if os.IsNotExist(err) {
|
return nil, fmt.Errorf("failed to read tools directory: %w", err)
|
||||||
return &Manager{tools: map[string]agenttool.LoadedTool{}}, nil
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to read tools directory: %w", err)
|
// Directory doesn't exist, continue to load all registered tools
|
||||||
|
entries = []os.DirEntry{}
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := &Manager{tools: map[string]agenttool.LoadedTool{}}
|
// Load tools from directory if they exist
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
@@ -57,6 +61,34 @@ func Load(root string, options agenttool.LoadOptions) (*Manager, error) {
|
|||||||
manager.tools[toolName] = tool
|
manager.tools[toolName] = tool
|
||||||
manager.order = append(manager.order, toolName)
|
manager.order = append(manager.order, toolName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no tools loaded from directory, automatically load all registered tools
|
||||||
|
if len(manager.tools) == 0 {
|
||||||
|
registeredTools := agenttool.Names()
|
||||||
|
for _, name := range registeredTools {
|
||||||
|
descriptor, ok := agenttool.Lookup(name)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Use empty path for tools that don't require configuration files
|
||||||
|
tool, err := descriptor.Load("", options)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tool == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toolName := strings.ToLower(strings.TrimSpace(tool.Name()))
|
||||||
|
if toolName == "" {
|
||||||
|
toolName = name
|
||||||
|
}
|
||||||
|
if _, ok := manager.tools[toolName]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
manager.tools[toolName] = tool
|
||||||
|
manager.order = append(manager.order, toolName)
|
||||||
|
}
|
||||||
|
}
|
||||||
return manager, nil
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-2
@@ -18,7 +18,8 @@ import (
|
|||||||
const maxAgentToolIterations = 6
|
const maxAgentToolIterations = 6
|
||||||
|
|
||||||
// RunAgentToolLoop runs the agent tool calling loop
|
// RunAgentToolLoop runs the agent tool calling loop
|
||||||
func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, chatMessages []message.ChatMessage, manager *toolmanager.Manager, emit stream.EmitFunc) ([]*model.ChatCompletionMessage, error) {
|
// 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)
|
finalMessages, err := buildArkMessages(chatMessages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -29,6 +30,16 @@ func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, c
|
|||||||
}
|
}
|
||||||
tools := availableAgentTools(state, routerProfile, manager, emit)
|
tools := availableAgentTools(state, routerProfile, manager, emit)
|
||||||
if len(tools) == 0 {
|
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
|
return finalMessages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +61,25 @@ func RunAgentToolLoop(ctx context.Context, state *State, profile *llm.Profile, c
|
|||||||
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "prepare", Status: "success", Message: "已准备可用工具", Data: map[string]any{"tools": availableNames, "tool_descriptions": toolDescriptions}})
|
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 {
|
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
|
return finalMessages, nil
|
||||||
}
|
}
|
||||||
if prompt := strings.TrimSpace(state.cfg.SystemPrompt); prompt != "" {
|
// 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{
|
systemMessage := &model.ChatCompletionMessage{
|
||||||
Role: "system",
|
Role: "system",
|
||||||
Content: &model.ChatCompletionMessageContent{
|
Content: &model.ChatCompletionMessageContent{
|
||||||
|
|||||||
Reference in New Issue
Block a user