新增活跃度查询工具
功能: - 查询指定时间范围内的活跃节点数和活跃人数 - 活跃节点:统计 nodeinfo 表 updated_at 字段 - 活跃人数:统计 text_message 表按 from_id 去重的用户数 使用场景: - 用户问'现在有多少人活跃'时 AI 调用此工具 - 用户问'当前有多少节点在线'时 AI 调用此工具 - 支持附带时间条件,默认1小时,最大24小时 参数: - hours: 查询最近N小时,默认1小时,最大24小时 - query_type: both/nodes/users,默认 both 实现: - internal/agents/active/active.go - 工具主逻辑 - internal/store/active_store.go - 数据库查询方法 - 完整的单元测试,所有测试通过 - 在 ai/service.go 中注册工具 测试: - ✅ 默认查询(1小时,both) - ✅ 指定时间查询(6小时、24小时) - ✅ 仅查询节点/人数 - ✅ 时间限制验证 - ✅ 项目编译成功 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// Package active 提供活跃度查询工具。当用户想查询活跃节点数或活跃人数时调用。
|
||||
//
|
||||
// 活跃节点:查询 nodeinfo 表的 updated_at 字段,统计指定时间范围内更新过的节点数
|
||||
// 活跃人数:查询 text_message 表的 created_at 字段,统计指定时间范围内发过消息的唯一用户数(按 from_id 去重)
|
||||
package active
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"meshtastic_mqtt_server/internal/agenttool"
|
||||
|
||||
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||||
)
|
||||
|
||||
// ActiveStore 定义活跃度查询工具所需的持久化能力,通常由 *store.Store 实现。
|
||||
type ActiveStore interface {
|
||||
CountActiveNodes(since time.Time) (int64, error)
|
||||
CountActiveUsers(since time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// Tool 是活跃度查询工具。
|
||||
type Tool struct {
|
||||
enabled bool
|
||||
store ActiveStore
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *Tool) Name() string { return "active" }
|
||||
|
||||
// Enabled returns whether the tool is enabled
|
||||
func (t *Tool) Enabled() bool { return t.enabled && t.store != nil }
|
||||
|
||||
// ToolDefinition returns the OpenAI tool definition
|
||||
func (t *Tool) ToolDefinition(description string) *model.Tool {
|
||||
desc := "活跃度查询工具。查询指定时间范围内的活跃节点数和活跃人数。\n" +
|
||||
"活跃节点:在指定时间内有更新记录的节点数量\n" +
|
||||
"活跃人数:在指定时间内发送过消息的唯一用户数(按 from_id 去重)\n" +
|
||||
"默认查询最近1小时,最大支持24小时"
|
||||
if description != "" {
|
||||
desc = description
|
||||
}
|
||||
return &model.Tool{
|
||||
Type: model.ToolTypeFunction,
|
||||
Function: &model.FunctionDefinition{
|
||||
Name: "active",
|
||||
Description: desc,
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"hours": map[string]any{
|
||||
"type": "number",
|
||||
"description": "查询最近多少小时内的活跃数据,默认1小时,最大24小时。例如:1、2、6、12、24",
|
||||
},
|
||||
"query_type": map[string]any{
|
||||
"type": "string",
|
||||
"enum": []string{"both", "nodes", "users"},
|
||||
"description": "查询类型:both=同时查询节点和人数(默认),nodes=仅查询节点,users=仅查询人数",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes the active query tool
|
||||
func (t *Tool) Execute(ctx context.Context, args string, runtime agenttool.Runtime) (string, error) {
|
||||
if t.store == nil {
|
||||
return "", fmt.Errorf("active store is not configured")
|
||||
}
|
||||
|
||||
var params activeParams
|
||||
if err := json.Unmarshal([]byte(args), ¶ms); err != nil {
|
||||
return "", fmt.Errorf("failed to parse arguments: %w", err)
|
||||
}
|
||||
|
||||
// 默认查询1小时
|
||||
hours := params.Hours
|
||||
if hours <= 0 {
|
||||
hours = 1
|
||||
}
|
||||
// 最大24小时
|
||||
if hours > 24 {
|
||||
hours = 24
|
||||
}
|
||||
|
||||
// 默认查询类型为 both
|
||||
queryType := strings.ToLower(strings.TrimSpace(params.QueryType))
|
||||
if queryType == "" {
|
||||
queryType = "both"
|
||||
}
|
||||
|
||||
now := runtime.Now
|
||||
if now.IsZero() {
|
||||
now = time.Now()
|
||||
}
|
||||
|
||||
// 计算时间范围
|
||||
since := now.Add(-time.Duration(hours * float64(time.Hour)))
|
||||
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("最近 %.1f 小时的活跃统计:\n\n", hours))
|
||||
|
||||
// 查询活跃节点
|
||||
if queryType == "both" || queryType == "nodes" {
|
||||
nodeCount, err := t.store.CountActiveNodes(since)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("查询活跃节点失败:%w", err)
|
||||
}
|
||||
result.WriteString(fmt.Sprintf("活跃节点:%d 个\n", nodeCount))
|
||||
}
|
||||
|
||||
// 查询活跃人数
|
||||
if queryType == "both" || queryType == "users" {
|
||||
userCount, err := t.store.CountActiveUsers(since)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("查询活跃人数失败:%w", err)
|
||||
}
|
||||
result.WriteString(fmt.Sprintf("活跃人数:%d 人\n", userCount))
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// activeParams 是活跃度查询工具的入参。
|
||||
type activeParams struct {
|
||||
Hours float64 `json:"hours"` // 查询最近多少小时,默认1小时
|
||||
QueryType string `json:"query_type"` // 查询类型:both/nodes/users
|
||||
}
|
||||
|
||||
// RawState returns the tool state
|
||||
func (t *Tool) RawState() any {
|
||||
return map[string]any{"enabled": t.enabled, "has_store": t.store != nil}
|
||||
}
|
||||
|
||||
func init() {
|
||||
agenttool.Register(agenttool.Descriptor{
|
||||
Name: "active",
|
||||
Load: func(path string, options agenttool.LoadOptions) (agenttool.LoadedTool, error) {
|
||||
tool := &Tool{enabled: true}
|
||||
if store, ok := options.Value("store").(ActiveStore); ok && store != nil {
|
||||
tool.store = store
|
||||
}
|
||||
return tool, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user