feat(ai): 阶段 2 Ask|Agent 共用 aitools Runtime 真查库
- 侧栏 Ask|Agent 分段;Ask 仍走 /api/ai/chat,Agent 走 /api/ai/agent - RunAgent 多轮 tool call → 进程内 aitools(与 MCP 同源只读/白名单/审计) - 回复下步骤时间线;openai-chat tools(DeepSeek);agent-accept 冒烟
This commit is contained in:
1 parent
b8134c4503
commit
cd319ce7bd
14 files changed
+1270
-182
No files matched your search
@@ -0,0 +1,63 @@
|
||||
// opsxdb-agent-accept:阶段 2 冒烟 — 真 LLM(~/.opsxdb/ai.json)+ 真库 aitools。
|
||||
// 用法:cd server && go run ./cmd/opsxdb-agent-accept/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"opsxdb/server/internal/ai"
|
||||
"opsxdb/server/internal/aitools"
|
||||
)
|
||||
|
||||
func main() {
|
||||
llm, err := ai.DefaultToolChat()
|
||||
if err != nil {
|
||||
fmt.Println("SKIP: no ToolChat LLM configured:", err)
|
||||
fmt.Println("提示:Ask|Agent 单测已覆盖 tool loop;配置 DeepSeek 后重跑本命令做真库冒烟。")
|
||||
os.Exit(0)
|
||||
}
|
||||
rt, err := aitools.DefaultRuntime()
|
||||
if err != nil {
|
||||
fmt.Println("FAIL: runtime", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := ai.AgentReq{
|
||||
Prompt: "列出 demo 库有哪些表,再用 SELECT 查 users 表最多 3 行,简要中文回答。",
|
||||
ConnectionID: "accept-mysql-demo",
|
||||
Driver: "mysql",
|
||||
Database: "demo",
|
||||
MaxRounds: 8,
|
||||
}
|
||||
fmt.Println("RUN Agent:", req.Prompt)
|
||||
res, err := ai.RunAgent(ctx, llm, rt, req)
|
||||
b, _ := json.MarshalIndent(res, "", " ")
|
||||
fmt.Println(string(b))
|
||||
if err != nil {
|
||||
fmt.Println("FAIL:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
okTool := false
|
||||
for _, s := range res.Steps {
|
||||
fmt.Printf("STEP ok=%v tool=%s summary=%s ms=%d\n", s.OK, s.Tool, s.Summary, s.Ms)
|
||||
if s.OK && (s.Tool == aitools.ToolListTables || s.Tool == aitools.ToolExecuteQuery || s.Tool == aitools.ToolGetSchemaContext) {
|
||||
okTool = true
|
||||
}
|
||||
}
|
||||
if !okTool {
|
||||
fmt.Println("FAIL: Agent 未成功调用 list_tables / execute_query / get_schema_context")
|
||||
os.Exit(1)
|
||||
}
|
||||
if strings.TrimSpace(res.Content) == "" {
|
||||
fmt.Println("FAIL: empty content")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("OK: agent smoke passed, rounds=", res.Rounds)
|
||||
}
|
||||
@@ -33,17 +33,10 @@ func main() {
|
||||
server.WithToolCapabilities(true),
|
||||
)
|
||||
|
||||
register := func(name, desc string, props map[string]any, required []string) {
|
||||
schema := map[string]any{
|
||||
"type": "object",
|
||||
"properties": props,
|
||||
}
|
||||
if len(required) > 0 {
|
||||
schema["required"] = required
|
||||
}
|
||||
raw, _ := json.Marshal(schema)
|
||||
t := mcp.NewToolWithRawSchema(name, desc, raw)
|
||||
toolName := name
|
||||
for _, spec := range aitools.ToolSpecs() {
|
||||
raw, _ := json.Marshal(spec.InputSchema())
|
||||
t := mcp.NewToolWithRawSchema(spec.Name, spec.Description, raw)
|
||||
toolName := spec.Name
|
||||
s.AddTool(t, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
out, err := rt.Call(ctx, toolName, args)
|
||||
@@ -54,54 +47,6 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
connID := map[string]any{"type": "string", "description": "Saved connection id from opsxdb_list_connections"}
|
||||
database := map[string]any{"type": "string", "description": "Database / schema / Redis DB index"}
|
||||
|
||||
register(aitools.ToolListConnections,
|
||||
"List whitelisted OpsxDB connections (id/name/driver/host/port/database; never passwords).",
|
||||
map[string]any{}, nil)
|
||||
|
||||
register(aitools.ToolListDatabases,
|
||||
"List databases for a connection.",
|
||||
map[string]any{"connection_id": connID}, []string{"connection_id"})
|
||||
|
||||
register(aitools.ToolListTables,
|
||||
"List tables/views (or engine equivalents) in a database.",
|
||||
map[string]any{"connection_id": connID, "database": database}, []string{"connection_id"})
|
||||
|
||||
register(aitools.ToolDescribeTable,
|
||||
"Describe columns and metadata for a table.",
|
||||
map[string]any{
|
||||
"connection_id": connID,
|
||||
"database": database,
|
||||
"table": map[string]any{"type": "string", "description": "Table name"},
|
||||
}, []string{"connection_id", "table"})
|
||||
|
||||
register(aitools.ToolGetSchemaContext,
|
||||
"Compact schema text for LLM context (capped ~4k chars).",
|
||||
map[string]any{
|
||||
"connection_id": connID,
|
||||
"database": database,
|
||||
"max_tables": map[string]any{"type": "integer", "description": "Max tables to include (default 40)"},
|
||||
}, []string{"connection_id"})
|
||||
|
||||
register(aitools.ToolExecuteQuery,
|
||||
"Execute a READ-ONLY SQL query (SELECT/SHOW/EXPLAIN/WITH…SELECT). Writes and DDL are rejected in P0. Default limit 100, max 500.",
|
||||
map[string]any{
|
||||
"connection_id": connID,
|
||||
"sql": map[string]any{"type": "string", "description": "Read-only SQL"},
|
||||
"database": database,
|
||||
"limit": map[string]any{"type": "integer", "description": "Row limit (default 100, max 500)"},
|
||||
}, []string{"connection_id", "sql"})
|
||||
|
||||
register(aitools.ToolExecuteRedisCommand,
|
||||
"Execute a READ-ONLY Redis command (GET/SCAN/TYPE/TTL/…). FLUSH*/KEYS and writes are rejected in P0.",
|
||||
map[string]any{
|
||||
"connection_id": connID,
|
||||
"command": map[string]any{"type": "string", "description": "Single Redis command line"},
|
||||
"database": database,
|
||||
}, []string{"connection_id", "command"})
|
||||
|
||||
if err := server.ServeStdio(s); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "opsxdb-mcp: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
Reference in new issue
Block a user