581 lines
18 KiB
Go
581 lines
18 KiB
Go
package store
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// ============================================
|
||
// LLM Provider (llm_providers) - 多 AI API 配置
|
||
// ============================================
|
||
|
||
// ListLLMProviders 列出所有 LLM Provider
|
||
func (s *Store) ListLLMProviders(includeInactive bool) ([]LLMProviderRecord, error) {
|
||
var rows []LLMProviderRecord
|
||
query := s.db.Model(&LLMProviderRecord{})
|
||
if !includeInactive {
|
||
query = query.Where("active = ?", true)
|
||
}
|
||
if err := query.Order("created_at DESC").Find(&rows).Error; err != nil {
|
||
return nil, fmt.Errorf("list llm providers: %w", err)
|
||
}
|
||
return rows, nil
|
||
}
|
||
|
||
// GetLLMProvider 获取单个 LLM Provider
|
||
func (s *Store) GetLLMProvider(name string) (*LLMProviderRecord, error) {
|
||
var record LLMProviderRecord
|
||
if err := s.db.Where("name = ?", name).Take(&record).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, err
|
||
}
|
||
return nil, fmt.Errorf("get llm provider %s: %w", name, err)
|
||
}
|
||
return &record, nil
|
||
}
|
||
|
||
// CreateLLMProvider 创建 LLM Provider
|
||
func (s *Store) CreateLLMProvider(record *LLMProviderRecord) error {
|
||
if err := s.db.Create(record).Error; err != nil {
|
||
return fmt.Errorf("create llm provider %s: %w", record.Name, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// UpdateLLMProvider 更新 LLM Provider
|
||
func (s *Store) UpdateLLMProvider(name string, updates map[string]any) error {
|
||
if err := s.db.Model(&LLMProviderRecord{}).Where("name = ?", name).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("update llm provider %s: %w", name, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DeleteLLMProvider 删除 LLM Provider
|
||
func (s *Store) DeleteLLMProvider(name string) error {
|
||
if err := s.db.Where("name = ?", name).Delete(&LLMProviderRecord{}).Error; err != nil {
|
||
return fmt.Errorf("delete llm provider %s: %w", name, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EnsureDefaultLLMProvider 确保存在默认 LLM Provider 配置
|
||
// 只有当数据库中完全没有任何 provider 配置时,才创建默认配置
|
||
func (s *Store) EnsureDefaultLLMProvider() error {
|
||
// 先检查是否已经有任何 provider 配置
|
||
providers, err := s.ListLLMProviders(true)
|
||
if err != nil {
|
||
return fmt.Errorf("list llm providers: %w", err)
|
||
}
|
||
if len(providers) > 0 {
|
||
return nil // 已有配置,不创建默认
|
||
}
|
||
// 创建默认配置
|
||
defaultConfig := &LLMProviderRecord{
|
||
Name: "default",
|
||
Active: true,
|
||
APIKey: "",
|
||
BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
|
||
Model: "",
|
||
Timeout: 120,
|
||
ContextWindowTokens: 262144,
|
||
}
|
||
return s.CreateLLMProvider(defaultConfig)
|
||
}
|
||
|
||
// ============================================
|
||
// LLM Tool Router (llm_tool_router) - 工具路由配置
|
||
// ============================================
|
||
|
||
// GetLLMToolRouter 获取当前激活的 Tool Router 配置
|
||
func (s *Store) GetLLMToolRouter() (*LLMToolRouterRecord, error) {
|
||
var record LLMToolRouterRecord
|
||
// 默认取第一条记录(ID 最小的),因为通常只需要一个配置
|
||
if err := s.db.Order("id ASC").First(&record).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, err
|
||
}
|
||
return nil, fmt.Errorf("get llm tool router: %w", err)
|
||
}
|
||
return &record, nil
|
||
}
|
||
|
||
// CreateLLMToolRouter 创建 Tool Router 配置
|
||
func (s *Store) CreateLLMToolRouter(record *LLMToolRouterRecord) error {
|
||
if err := s.db.Create(record).Error; err != nil {
|
||
return fmt.Errorf("create llm tool router: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// UpdateLLMToolRouter 更新 Tool Router 配置
|
||
func (s *Store) UpdateLLMToolRouter(id uint64, updates map[string]any) error {
|
||
if err := s.db.Model(&LLMToolRouterRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("update llm tool router %d: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EnsureDefaultLLMToolRouter 确保存在默认 Tool Router 配置
|
||
func (s *Store) EnsureDefaultLLMToolRouter() error {
|
||
_, err := s.GetLLMToolRouter()
|
||
if err == nil {
|
||
return nil // 已存在
|
||
}
|
||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return err
|
||
}
|
||
// 创建默认配置
|
||
defaultConfig := &LLMToolRouterRecord{
|
||
Enabled: true,
|
||
OpenAIName: "",
|
||
Timeout: 30,
|
||
MaxTokens: 512,
|
||
SystemPrompt: "你可以按需直接调用可用工具来回答用户问题。\n每个工具的 description 描述了它的适用场景和调用条件。\n工具结果优先于模型内置知识;工具失败时必须如实说明,不要编造结果。\n只调用确实必要的工具。",
|
||
}
|
||
return s.CreateLLMToolRouter(defaultConfig)
|
||
}
|
||
|
||
// ============================================
|
||
// LLM Topic Config (llm_topic_config) - 话题选择配置
|
||
// ============================================
|
||
|
||
// GetLLMTopicConfig 获取当前激活的话题选择配置
|
||
func (s *Store) GetLLMTopicConfig() (*LLMTopicConfigRecord, error) {
|
||
var record LLMTopicConfigRecord
|
||
// 默认取第一条记录(ID 最小的),因为通常只需要一个配置
|
||
if err := s.db.Order("id ASC").First(&record).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, err
|
||
}
|
||
return nil, fmt.Errorf("get llm topic config: %w", err)
|
||
}
|
||
return &record, nil
|
||
}
|
||
|
||
// CreateLLMTopicConfig 创建话题选择配置
|
||
func (s *Store) CreateLLMTopicConfig(record *LLMTopicConfigRecord) error {
|
||
if err := s.db.Create(record).Error; err != nil {
|
||
return fmt.Errorf("create llm topic config: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// UpdateLLMTopicConfig 更新话题选择配置
|
||
func (s *Store) UpdateLLMTopicConfig(id uint64, updates map[string]any) error {
|
||
if err := s.db.Model(&LLMTopicConfigRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("update llm topic config %d: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EnsureDefaultLLMTopicConfig 确保存在默认话题选择配置
|
||
func (s *Store) EnsureDefaultLLMTopicConfig() error {
|
||
_, err := s.GetLLMTopicConfig()
|
||
if err == nil {
|
||
return nil // 已存在
|
||
}
|
||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return err
|
||
}
|
||
// 创建默认配置(默认未启用)
|
||
defaultConfig := &LLMTopicConfigRecord{
|
||
Enabled: false,
|
||
OpenAIName: "",
|
||
Timeout: 30,
|
||
MaxTokens: 512,
|
||
SystemPrompt: "你是一个话题过滤器,判断用户最新消息是否属于应当回复的话题范围。\n如果应当回复,请输出 REPLY;如果不应当回复,请输出 IGNORE。\n只输出 REPLY 或 IGNORE,不要输出任何其他内容。",
|
||
}
|
||
return s.CreateLLMTopicConfig(defaultConfig)
|
||
}
|
||
|
||
// ============================================
|
||
// LLM Primary Config (llm_primary_config) - 主 AI 回复配置
|
||
// ============================================
|
||
|
||
// GetLLMPrimaryConfig 获取当前激活的主 AI 回复配置
|
||
func (s *Store) GetLLMPrimaryConfig() (*LLMPrimaryConfigRecord, error) {
|
||
var record LLMPrimaryConfigRecord
|
||
// 默认取第一条记录(ID 最小的),因为通常只需要一个配置
|
||
if err := s.db.Order("id ASC").First(&record).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, err
|
||
}
|
||
return nil, fmt.Errorf("get llm primary config: %w", err)
|
||
}
|
||
return &record, nil
|
||
}
|
||
|
||
// GetLLMPrimaryConfigSystemPrompt 获取主 AI 回复配置中的系统提示词
|
||
// 如果没有配置或出错,返回空字符串(autoreply service 会处理这种情况)
|
||
func (s *Store) GetLLMPrimaryConfigSystemPrompt() (string, error) {
|
||
record, err := s.GetLLMPrimaryConfig()
|
||
if err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
// 没有配置时返回空字符串,使用默认行为
|
||
return "", nil
|
||
}
|
||
return "", err
|
||
}
|
||
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 回复配置
|
||
func (s *Store) CreateLLMPrimaryConfig(record *LLMPrimaryConfigRecord) error {
|
||
if err := s.db.Create(record).Error; err != nil {
|
||
return fmt.Errorf("create llm primary config: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// UpdateLLMPrimaryConfig 更新主 AI 回复配置
|
||
func (s *Store) UpdateLLMPrimaryConfig(id uint64, updates map[string]any) error {
|
||
if err := s.db.Model(&LLMPrimaryConfigRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("update llm primary config %d: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EnsureDefaultLLMPrimaryConfig 确保存在默认主 AI 回复配置
|
||
func (s *Store) EnsureDefaultLLMPrimaryConfig() error {
|
||
_, err := s.GetLLMPrimaryConfig()
|
||
if err == nil {
|
||
return nil // 已存在
|
||
}
|
||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return err
|
||
}
|
||
// 创建默认配置
|
||
defaultConfig := &LLMPrimaryConfigRecord{
|
||
Enabled: false,
|
||
ProviderName: "",
|
||
Timeout: 120,
|
||
MaxTokens: 1024,
|
||
SystemPrompt: "你是一个 Meshtastic 网络助手。请简洁回答用户问题。\n回答要简短清晰,适合在低带宽无线电环境传输。每次回复限制在200 bytes以内。",
|
||
EnableTool: false,
|
||
}
|
||
return s.CreateLLMPrimaryConfig(defaultConfig)
|
||
}
|
||
|
||
// LLMMessageQueueInput 是添加 LLM 队列消息的输入
|
||
type LLMMessageQueueInput struct {
|
||
BotID uint64 // 0 表示频道消息
|
||
BotNodeID string // 频道消息可为空
|
||
BotNodeNum int64 // 频道消息可为 0
|
||
FromNodeID string
|
||
FromNodeNum int64
|
||
LongName *string
|
||
ShortName *string
|
||
Text string
|
||
PacketID int64
|
||
ChannelID *string
|
||
Topic string
|
||
MessageType string // "channel" 或 "direct"
|
||
ContentJSON *string
|
||
}
|
||
|
||
// EnqueueLLMMessage 将消息添加到 LLM 队列
|
||
func (s *Store) EnqueueLLMMessage(input LLMMessageQueueInput) (*LLMMessageQueueRecord, error) {
|
||
var err error
|
||
|
||
if input.BotID == 0 {
|
||
return nil, nil // bot_id 为 0 的消息不再入队
|
||
}
|
||
|
||
// 检查机器人级别的 LLM 队列设置
|
||
bot, err := s.GetBotNode(input.BotID)
|
||
if err != nil {
|
||
return nil, nil // 机器人不存在,静默返回
|
||
}
|
||
if !bot.LLMQueueEnabled {
|
||
return nil, nil // 机器人的 LLM 队列未启用,静默返回
|
||
}
|
||
|
||
if s.IsBotNodeID(input.FromNodeID) {
|
||
return nil, nil
|
||
}
|
||
|
||
if input.FromNodeID == "" {
|
||
return nil, fmt.Errorf("from_node_id is required")
|
||
}
|
||
if input.Text == "" {
|
||
return nil, fmt.Errorf("text is required")
|
||
}
|
||
|
||
// 检查是否存在重复消息
|
||
// packet_id > 0: 用 bot_id + packet_id 去重(频道消息)
|
||
// packet_id = 0: 用 bot_id + from_node_id + text 去重(私聊消息,可能没有 packet_id)
|
||
// 命中条件二选一:
|
||
// 1. 仍存在 pending/processing 状态的记录(尚未处理完)
|
||
// 2. 已软删除(processed)但未超过 dedup 窗口——防止网络延迟/重投导致同一包在刚处理完后又被重复入队
|
||
// error 状态允许重新入队;processed 软删除超过窗口后也允许重新入队。
|
||
// 阈值在 Go 侧算好作为参数传入,避免依赖 SQLite datetime('now') 的时区行为,与其它 time 字段读写保持一致。
|
||
processedCutoff := time.Now().Add(-llmQueueProcessedDedupWindow)
|
||
dupCondition := "(deleted_at IS NULL AND status IN (?, ?)) OR (deleted_at IS NOT NULL AND deleted_at > ?)"
|
||
|
||
var existing LLMMessageQueueRecord
|
||
if input.PacketID > 0 {
|
||
// 频道消息:用 bot_id + packet_id 去重
|
||
err = s.db.Where("bot_id = ? AND packet_id = ? AND "+dupCondition,
|
||
input.BotID, input.PacketID, LLMMessageStatusPending, LLMMessageStatusProcessing, processedCutoff).
|
||
Take(&existing).Error
|
||
} else {
|
||
// 私聊消息:用 bot_id + from_node_id + text 去重(避免同一人连续发相同内容被拒绝)
|
||
err = s.db.Where("bot_id = ? AND from_node_id = ? AND text = ? AND "+dupCondition,
|
||
input.BotID, input.FromNodeID, input.Text, LLMMessageStatusPending, LLMMessageStatusProcessing, processedCutoff).
|
||
Take(&existing).Error
|
||
}
|
||
if err == nil {
|
||
// 存在命中去重的记录(处理中 / 刚处理完未过窗口),直接返回
|
||
return &existing, nil
|
||
}
|
||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, fmt.Errorf("check duplicate llm message: %w", err)
|
||
}
|
||
|
||
now := time.Now()
|
||
messageType := input.MessageType
|
||
if messageType == "" {
|
||
messageType = "direct"
|
||
}
|
||
record := &LLMMessageQueueRecord{
|
||
BotID: input.BotID,
|
||
BotNodeID: input.BotNodeID,
|
||
BotNodeNum: input.BotNodeNum,
|
||
FromNodeID: input.FromNodeID,
|
||
FromNodeNum: input.FromNodeNum,
|
||
LongName: input.LongName,
|
||
ShortName: input.ShortName,
|
||
Text: input.Text,
|
||
PacketID: input.PacketID,
|
||
ChannelID: input.ChannelID,
|
||
Topic: input.Topic,
|
||
MessageType: messageType,
|
||
Status: LLMMessageStatusPending,
|
||
ReceivedAt: now,
|
||
ContentJSON: input.ContentJSON,
|
||
}
|
||
|
||
if err := s.db.Create(record).Error; err != nil {
|
||
return nil, fmt.Errorf("enqueue llm message: %w", err)
|
||
}
|
||
return record, nil
|
||
}
|
||
|
||
// ListLLMMessages 列出 LLM 队列消息
|
||
func (s *Store) ListLLMMessages(opts ListOptions, botID uint64, includeDeleted bool) ([]LLMMessageQueueRecord, int64, error) {
|
||
var rows []LLMMessageQueueRecord
|
||
query := s.db.Model(&LLMMessageQueueRecord{})
|
||
|
||
if botID > 0 {
|
||
query = query.Where("bot_id = ?", botID)
|
||
}
|
||
if !includeDeleted {
|
||
query = query.Where("deleted_at IS NULL")
|
||
}
|
||
|
||
// 先获取总数
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, fmt.Errorf("count llm messages: %w", err)
|
||
}
|
||
|
||
// 排序和分页
|
||
query = query.Order("created_at DESC")
|
||
if opts.Limit > 0 {
|
||
query = query.Limit(opts.Limit)
|
||
}
|
||
if opts.Offset > 0 {
|
||
query = query.Offset(opts.Offset)
|
||
}
|
||
|
||
if err := query.Find(&rows).Error; err != nil {
|
||
return nil, 0, fmt.Errorf("list llm messages: %w", err)
|
||
}
|
||
return rows, total, nil
|
||
}
|
||
|
||
// GetLLMMessage 获取单条 LLM 消息
|
||
func (s *Store) GetLLMMessage(id uint64) (*LLMMessageQueueRecord, error) {
|
||
var record LLMMessageQueueRecord
|
||
if err := s.db.Where("id = ?", id).Take(&record).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, err
|
||
}
|
||
return nil, fmt.Errorf("get llm message %d: %w", id, err)
|
||
}
|
||
return &record, nil
|
||
}
|
||
|
||
// UpdateLLMMessageStatus 更新 LLM 消息状态
|
||
func (s *Store) UpdateLLMMessageStatus(id uint64, status string, errorMsg string) error {
|
||
updates := map[string]any{
|
||
"status": status,
|
||
"error": errorMsg,
|
||
}
|
||
if status == LLMMessageStatusProcessed {
|
||
now := time.Now()
|
||
updates["processed_at"] = &now
|
||
}
|
||
if err := s.db.Model(&LLMMessageQueueRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||
return fmt.Errorf("update llm message status %d: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SoftDeleteLLMMessage 软删除 LLM 消息
|
||
func (s *Store) SoftDeleteLLMMessage(id uint64) error {
|
||
now := time.Now()
|
||
if err := s.db.Model(&LLMMessageQueueRecord{}).Where("id = ?", id).Update("deleted_at", &now).Error; err != nil {
|
||
return fmt.Errorf("soft delete llm message %d: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SoftDeleteLLMMessagesByBot 软删除指定机器人的所有消息
|
||
func (s *Store) SoftDeleteLLMMessagesByBot(botID uint64) error {
|
||
now := time.Now()
|
||
if err := s.db.Model(&LLMMessageQueueRecord{}).Where("bot_id = ? AND deleted_at IS NULL", botID).Update("deleted_at", &now).Error; err != nil {
|
||
return fmt.Errorf("soft delete llm messages for bot %d: %w", botID, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CleanupDeletedLLMMessages 清理已软删除超过指定时间的消息
|
||
func (s *Store) CleanupDeletedLLMMessages(before time.Time) (int64, error) {
|
||
result := s.db.Where("deleted_at IS NOT NULL AND deleted_at < ?", before).Delete(&LLMMessageQueueRecord{})
|
||
if result.Error != nil {
|
||
return 0, fmt.Errorf("cleanup deleted llm messages: %w", result.Error)
|
||
}
|
||
return result.RowsAffected, nil
|
||
}
|
||
|
||
// LLMMessageDTO 将数据库记录转换为 API 响应格式
|
||
func LLMMessageDTO(row LLMMessageQueueRecord) map[string]any {
|
||
return map[string]any{
|
||
"id": row.ID,
|
||
"bot_id": row.BotID,
|
||
"bot_node_id": row.BotNodeID,
|
||
"bot_node_num": row.BotNodeNum,
|
||
"from_node_id": row.FromNodeID,
|
||
"from_node_num": row.FromNodeNum,
|
||
"long_name": row.LongName,
|
||
"short_name": row.ShortName,
|
||
"text": row.Text,
|
||
"packet_id": row.PacketID,
|
||
"channel_id": row.ChannelID,
|
||
"topic": row.Topic,
|
||
"status": row.Status,
|
||
"error": row.Error,
|
||
"received_at": row.ReceivedAt,
|
||
"processed_at": row.ProcessedAt,
|
||
"deleted_at": row.DeletedAt,
|
||
"created_at": row.CreatedAt,
|
||
}
|
||
}
|
||
|
||
// enqueueChannelMessageToLLM 将频道消息添加到 LLM 队列
|
||
// 为每个启用了「包含频道消息」的机器人都创建一条独立的队列记录
|
||
func enqueueChannelMessageToLLM(s *Store, record map[string]any) error {
|
||
if s == nil {
|
||
return nil
|
||
}
|
||
|
||
text, _ := record["text"].(string)
|
||
if text == "" {
|
||
return nil
|
||
}
|
||
|
||
fromNodeID, _ := record["from"].(string)
|
||
if fromNodeID == "" {
|
||
return nil
|
||
}
|
||
|
||
fromNodeNum, err := int64FromAny(record["from_num"])
|
||
if err != nil {
|
||
fromNodeNum = 0
|
||
}
|
||
|
||
// record 来自 describePacket 直接构造的 map,packet_id 是 uint32,
|
||
// 并未经过 JSON 往返(不会变成 float64),必须用类型安全的转换兜底各种整型。
|
||
packetID, _ := int64FromAny(record["packet_id"])
|
||
|
||
topic, _ := record["topic"].(string)
|
||
|
||
var longName, shortName *string
|
||
if ln, ok := record["long_name"].(string); ok && ln != "" {
|
||
longName = &ln
|
||
}
|
||
if sn, ok := record["short_name"].(string); ok && sn != "" {
|
||
shortName = &sn
|
||
}
|
||
|
||
if s.IsBotNodeID(fromNodeID) {
|
||
return nil
|
||
}
|
||
|
||
var channelID *string
|
||
if cid, ok := record["channel_id"].(string); ok && cid != "" {
|
||
channelID = &cid
|
||
}
|
||
|
||
contentJSON, err := json.Marshal(record)
|
||
var contentPtr *string
|
||
if err == nil {
|
||
s := string(contentJSON)
|
||
contentPtr = &s
|
||
}
|
||
|
||
// 查询所有启用了 LLM 队列且包含频道消息的机器人
|
||
// SQLite 中 numeric 布尔值用 1/0 存储,必须用整数查询
|
||
var bots []BotNodeRecord
|
||
err = s.db.Where("llm_queue_enabled = ? AND llm_include_channel_messages = ?", 1, 1).Find(&bots).Error
|
||
if err != nil {
|
||
return fmt.Errorf("query bots for channel message enqueue: %w", err)
|
||
}
|
||
|
||
for _, bot := range bots {
|
||
_, err = s.EnqueueLLMMessage(LLMMessageQueueInput{
|
||
BotID: bot.ID,
|
||
BotNodeID: bot.NodeID,
|
||
BotNodeNum: bot.NodeNum,
|
||
FromNodeID: fromNodeID,
|
||
FromNodeNum: fromNodeNum,
|
||
LongName: longName,
|
||
ShortName: shortName,
|
||
Text: text,
|
||
PacketID: packetID,
|
||
ChannelID: channelID,
|
||
Topic: topic,
|
||
MessageType: "channel",
|
||
ContentJSON: contentPtr,
|
||
})
|
||
if err != nil {
|
||
printJSON(map[string]any{
|
||
"event": "llm_queue_enqueue_failed",
|
||
"bot_id": bot.ID,
|
||
"from": fromNodeID,
|
||
"text": text,
|
||
"error": err.Error(),
|
||
})
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|