package io import ( "context" "fmt" "log/slog" "strings" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // KnowledgeRetriever is the subset of the runtime memory retriever the knowledge // tool depends on. *memory.Retriever implements it; nil means no knowledge base // is configured. type KnowledgeRetriever interface { Retrieve(ctx context.Context, personaID, query string, topK int) ([]memory.Result, error) } // Compile-time assertion that knowledgeTool implements sdk.Tool. var _ sdk.Tool = (*knowledgeTool)(nil) // knowledgeTool answers a question from the configured knowledge base via a // memory retriever, returning ranked results or a clear message when absent. type knowledgeTool struct { retriever KnowledgeRetriever maxResults int log *slog.Logger } func newKnowledgeTool(t *tools) *knowledgeTool { return &knowledgeTool{ retriever: t.retriever, maxResults: t.maxResults, log: t.log, } } // Name implements sdk.Tool. func (k *knowledgeTool) Name() string { return "knowledge" } // Description implements sdk.Tool. func (k *knowledgeTool) Description() string { return "Query the knowledge base and long-term memory store for context relevant to a question." } // Parameters implements sdk.Tool. func (k *knowledgeTool) Parameters() (map[string]any, error) { return map[string]any{ "type": "object", "properties": map[string]any{ "query": map[string]any{"type": "string", "description": "Query topic."}, "persona": map[string]any{"type": "string", "description": "Persona scope id (optional)."}, "top_k": map[string]any{"type": "integer", "description": "Max results."}, }, "required": []any{"query"}, }, nil } // Execute implements sdk.Tool. func (k *knowledgeTool) Execute(ctx context.Context, args any) (any, error) { if k.retriever == nil { return nil, fmt.Errorf("io: knowledge: no knowledge base configured") } m, ok := args.(map[string]any) if !ok { return nil, fmt.Errorf("io: knowledge: args must be an object") } query := argString(m, "query") if strings.TrimSpace(query) == "" { return nil, fmt.Errorf("io: knowledge: query is required") } persona := argString(m, "persona") topK := k.maxResults if v, ok := argInt(m, "top_k"); ok && v > 0 { topK = v } results, err := k.retriever.Retrieve(ctx, persona, query, topK) if err != nil { return nil, fmt.Errorf("io: knowledge: retrieve: %w", err) } out := make([]map[string]any, 0, len(results)) for _, r := range results { out = append(out, map[string]any{ "id": r.Item.ID, "kind": string(r.Item.Kind), "scope": string(r.Item.Scope), "content": r.Item.Content, "score": r.Score, }) } if len(out) == 0 { return map[string]any{ "query": query, "count": 0, "note": "no relevant knowledge found", "results": []map[string]any{}, }, nil } return map[string]any{"query": query, "count": len(out), "results": out}, nil }