From 393842b3f2f5307cc14c5bd5c31bcfd2770aacaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 17:22:27 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent/guideline_middleware.py | 6 +- mcp/rag_retrieve_server.py | 6 +- mcp/tools/rag_retrieve_tools.json | 10 +-- prompt/system_prompt.md | 67 ++++++++++++++----- .../rag-retrieve/Retrieval_Policy.md | 48 +++++++++++++ skills_developing/rag-retrieve/SKILL.md | 50 ++++++++++++++ 6 files changed, 162 insertions(+), 25 deletions(-) create mode 100644 skills_developing/rag-retrieve/Retrieval_Policy.md diff --git a/agent/guideline_middleware.py b/agent/guideline_middleware.py index 4597783..51dc4fb 100644 --- a/agent/guideline_middleware.py +++ b/agent/guideline_middleware.py @@ -38,8 +38,8 @@ class GuidelineMiddleware(AgentMiddleware): if not self.guidelines: self.guidelines = """ 1. General Inquiries -Condition: User inquiries about products, policies, troubleshooting, factual questions, etc. -Action: Priority given to invoking the 【Knowledge Base Retrieval】 tool to query the knowledge base. +Condition: User inquiries about products, policies, troubleshooting, factual questions, definitions, workflows, data lookups, or other knowledge-seeking requests. +Action: First choose the most suitable 【Knowledge Base Retrieval】 tool by scenario. Use table_rag_retrieve first for structured data, lists, statistics, comparisons, extraction, mixed requests, or unclear cases. Use rag_retrieve first only for clearly pure concept / definition / workflow / policy explanation questions. If the first retrieval result is empty, errored, irrelevant, or only partially answers the request, call the other retrieval tool before replying. Only reply that no relevant information was found after both retrieval tools have been tried and still provide no sufficient evidence. 2.Social Dialogue Condition: User intent involves small talk, greetings, expressions of thanks, compliments, or other non-substantive conversations. @@ -47,7 +47,7 @@ Action: Provide concise, friendly, and personified natural responses. """ if not self.tool_description: self.tool_description = """ -- **Knowledge Base Retrieval**: For knowledge queries/other inquiries, prioritize searching the knowledge base → rag_retrieve-rag_retrieve +- **Knowledge Base Retrieval**: Choose retrieval order by scenario. Default to `table_rag_retrieve -> rag_retrieve` for structured, list, mixed, or unclear requests. Use `rag_retrieve -> table_rag_retrieve` only for clearly pure concept or workflow questions. Do not answer with "no result" until both tools have been tried when retrieval is needed. """ def get_guideline_prompt(self, config: AgentConfig) -> str: diff --git a/mcp/rag_retrieve_server.py b/mcp/rag_retrieve_server.py index 80a659f..6caa9c8 100644 --- a/mcp/rag_retrieve_server.py +++ b/mcp/rag_retrieve_server.py @@ -7,6 +7,7 @@ RAG检索MCP服务器 import asyncio import hashlib import json +import re import sys import os from typing import Any, Dict, List @@ -218,11 +219,14 @@ def table_rag_retrieve(query: str) -> Dict[str, Any]: if "markdown" in response_data: markdown_content = response_data["markdown"] + text = markdown_content + if not re.search(r"^no excel files found", markdown_content, re.IGNORECASE): + text = TABLE_CITATION_INSTRUCTIONS + markdown_content return { "content": [ { "type": "text", - "text": TABLE_CITATION_INSTRUCTIONS + markdown_content + "text": text } ] } diff --git a/mcp/tools/rag_retrieve_tools.json b/mcp/tools/rag_retrieve_tools.json index 95a54fe..0db0f70 100644 --- a/mcp/tools/rag_retrieve_tools.json +++ b/mcp/tools/rag_retrieve_tools.json @@ -1,17 +1,17 @@ [ { "name": "rag_retrieve", - "description": "Retrieve relevant documents from the knowledge base. Returns markdown format results containing relevant content.\n\n[CALLING STRATEGY] This tool is the SECONDARY choice. Only call this tool FIRST when the question is clearly a pure knowledge/concept query (e.g. \"What does XX mean?\", \"How to use XX?\", \"What is the workflow for XX?\") that has NO relation to data, lists, summaries, or tabular output. In ALL other cases, call table_rag_retrieve FIRST, then use this tool to supplement if table_rag results are insufficient or need additional context.\n\n[PARAMETER USAGE — IMPORTANT]\n- Do NOT pass the user's raw question directly unless it already fits retrieval needs well.\n- You MUST construct `query` by following the rewriting strategy described in the `query` parameter schema below.\n- You MUST choose `top_k` dynamically by following the decision rules described in the `top_k` parameter schema below.\n- `query` and `top_k` are coupled: broader rewritten queries, list-style requests, historical coverage, or multi-branch recall usually require a higher `top_k`; narrow fact lookup should use a smaller `top_k`.\n- Prefer the smallest sufficient `top_k`, then expand only when coverage is insufficient.\n\n[WHEN TO USE AS SUPPLEMENT] After calling table_rag_retrieve, call this tool if:\n- table_rag_retrieve returned insufficient results and you need document context\n- The answer requires background explanation beyond the structured data\n- The user's question involves both data retrieval and conceptual understanding", + "description": "Retrieve relevant documents from the knowledge base. Returns markdown results. Use this tool first only for clearly pure concept, definition, workflow, policy, or explanation questions without structured data needs. If the result is insufficient, try table_rag_retrieve before replying with no result.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", - "description": "Retrieval query content. Before retrieval, rewrite the query to improve recall: extract the core entity, time scope, attributes, and intent; add meaningful variants such as synonyms, aliases, abbreviations, related titles, historical names, and category terms; expand enumeration-style queries more aggressively; preserve the original meaning and do not introduce unrelated topics; use both the original query and rewritten queries whenever possible. For historical or list-style queries, also add terms like title / organization variants, predecessor / successor, former / past / historical / all-time, and list / overview / roster / timeline / archive." + "description": "Retrieval query content. Rewrite the query when needed to improve recall." }, "top_k": { "type": "integer", - "description": "Number of top results to retrieve. Use the smallest sufficient top_k and expand only when coverage is insufficient: 30 for simple fact lookup about one specific thing; 50 for moderate synthesis, comparison, summarization, or disambiguation; 100 for broad-recall queries needing high coverage, such as comprehensive analysis, scattered knowledge, multiple entities or periods, list / catalog / timeline / roster / overview requests, or all items / historical succession / many records. Raise top_k when query rewrite produces many useful keyword branches or when results are too few, repetitive, incomplete, sparse, or too narrow in coverage. Do not raise top_k just because the query is longer. Expansion sequence: 30 -> 50 -> 100. If uncertain, prefer passing 100. Default: 100.", + "description": "Number of top results to retrieve. Choose dynamically based on retrieval breadth and coverage needs.", "default": 100 } }, @@ -20,13 +20,13 @@ }, { "name": "table_rag_retrieve", - "description": "Retrieve relevant data from Excel/spreadsheet files in the knowledge base. Returns markdown format results containing table data analysis.\n\n[CALLING STRATEGY] This tool is the DEFAULT first choice. Call this tool FIRST in any of the following situations:\n- Questions involving specific values, prices, quantities, inventory, specifications, rankings, comparisons, statistics\n- Requests for tabular output (e.g. \"make a table\", \"list in a table\", \"一覧表にして\", \"整理成表格\")\n- Information extraction/organization requests (e.g. \"extract\", \"list\", \"summarize and list\", \"抽出\", \"提取\", \"列举\", \"汇总\")\n- Queries about specific person names, project names, or product names (e.g. \"XX議員の答弁を一覧にして\")\n- ANY question where you are unsure whether table data is needed — default to calling this tool first\n\n[PARAMETER USAGE — IMPORTANT]\n- Do NOT pass the user's raw question directly unless it already fits retrieval needs well.\n- You MUST construct `query` by following the rewriting strategy described in the `query` parameter schema below.\n- For list, extraction, comparison, historical, or name-based requests, rewrite `query` more aggressively to cover entity variants and intent variants.\n\n[RESPONSE HANDLING] When processing the returned results:\n1. Follow all instructions in [INSTRUCTION] and [EXTRA_INSTRUCTION] sections of the response (e.g. output format, source citation requirements)\n2. If Query result hint indicates truncation (e.g. \"Only the first N rows are included; the remaining M rows were omitted\"), you MUST explicitly tell the user: total matches (N+M), displayed count (N), and omitted count (M)\n3. If query result is empty, respond truthfully that no relevant data was found — do NOT fabricate data\n4. Cite data sources using file names from file_ref_table in the response", + "description": "Retrieve relevant table data from Excel or spreadsheet files in the knowledge base. Returns markdown results. Use this tool first for structured data, lists, statistics, extraction, mixed questions, and unclear cases. If the result is insufficient, try rag_retrieve before replying with no result.", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", - "description": "Retrieval query content for table data. Before retrieval, rewrite the query to improve recall: extract the core entity, time scope, attributes, and intent; add meaningful variants such as synonyms, aliases, abbreviations, related titles, historical names, and category terms; expand enumeration-style queries more aggressively; preserve the original meaning and do not introduce unrelated topics; use both the original query and rewritten queries whenever possible. For historical or list-style queries, also add terms like title / organization variants, predecessor / successor, former / past / historical / all-time, and list / overview / roster / timeline / archive." + "description": "Retrieval query content for table data. Rewrite the query when needed to improve recall." } }, "required": ["query"] diff --git a/prompt/system_prompt.md b/prompt/system_prompt.md index a75b589..7751dd2 100644 --- a/prompt/system_prompt.md +++ b/prompt/system_prompt.md @@ -1,17 +1,6 @@ {extra_prompt} -## CITATION REQUIREMENTS - -When your answer uses learned knowledge, you MUST generate `` tags. Follow the specific citation format instructions returned by each tool (`rag_retrieve`, `table_rag_retrieve`). - -### General Placement Rules -1. Citations MUST appear IMMEDIATELY AFTER the paragraph or bullet list that uses the knowledge -2. NEVER collect all citations and place them at the end of your response -3. Limit to 1-2 citations per paragraph/bullet list - combine related facts under one citation -4. If your answer uses learned knowledge, you MUST generate at least 1 `` in the response - -### Current Working Directory - +# Current Working Directory PROJECT_ROOT: `{agent_dir_path}` The filesystem backend is currently operating in: `{agent_dir_path}` @@ -80,7 +69,56 @@ When creating scripts in `executable_code/`, follow these organization rules: - Temporary script (when needed): `{agent_dir_path}/executable_code/tmp/test.py` - Downloaded file: `{agent_dir_path}/download/report.pdf` -## System Information +# Retrieval Policy (Priority & Fallback) + +### 1. Retrieval Source Priority +- Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. +- Use this default source order: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. +- Treat the local filesystem as last resort. Do NOT browse or search files first when knowledge retrieval tools may answer the question. + +### 2. Tool Selection +- Start with `rag_retrieve` or `table_rag_retrieve` when knowledge retrieval is needed. Do NOT answer from model knowledge first. +- Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, name lookup, historical coverage, mixed questions, and unclear cases. +- Use `rag_retrieve` first only for clearly pure concept, definition, workflow, policy, or explanation questions without structured data needs. + +### 3. Query Preparation +- Do NOT pass the raw user question unless it already works well for retrieval. +- Rewrite for recall: extract entity, time scope, attributes, and intent. +- Add useful variants: synonyms, aliases, abbreviations, related titles, historical names, and category terms. +- Expand list-style, extraction, overview, historical, roster, timeline, and archive queries more aggressively. +- Preserve meaning. Do NOT introduce unrelated topics. + +### 4. Retrieval Breadth (`top_k`) +- Apply `top_k` only to `rag_retrieve`. Use the smallest sufficient value, then expand only if coverage is insufficient. +- Use `30` for simple fact lookup. +- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. +- Use `100` for broad recall, such as comprehensive analysis, scattered knowledge, multiple entities or periods, or list / catalog / timeline / roster / overview requests. +- Raise `top_k` when keyword branches are many or results are too few, repetitive, incomplete, sparse, or too narrow. +- Use this expansion order: `30 -> 50 -> 100`. If unsure, use `100`. + +### 5. Result Evaluation +- Treat results as insufficient if they are empty, start with `Error:`, say `no excel files found`, are off-topic, miss the core entity or scope, or provide no usable evidence. +- Also treat results as insufficient when they cover only part of the request, or when full-list, historical, comparison, or mixed data + explanation requests return only partial or truncated coverage. + +### 6. Fallback and Sequential Retry +- If the first retrieval result is insufficient, call the other retrieval tool before replying. +- If `table_rag_retrieve` is empty, continue with `rag_retrieve`. +- Say no relevant information was found only after both `rag_retrieve` and `table_rag_retrieve` have been tried and still do not provide enough evidence. + +### 7. Table Result Handling +- Follow all `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` content in `table_rag_retrieve` results. +- If results are truncated, explicitly tell the user total matches (`N+M`), displayed count (`N`), and omitted count (`M`). +- Cite data sources using filenames from `file_ref_table`. + +### 8. Citation Requirements for Retrieved Knowledge +- When using knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. +- Follow the citation format returned by each tool. +- Place citations immediately after the paragraph or bullet list that uses the knowledge. +- Do NOT collect citations at the end. +- Use 1-2 citations per paragraph or bullet list when possible. +- If learned knowledge is used, include at least 1 ``. + +# System Information Working directory: {agent_dir_path} Current User: {user_identifier} @@ -90,9 +128,6 @@ Trace Id: {trace_id} # Execution Guidelines - **Tool-Driven**: All operations are implemented through tool interfaces. -- **Retrieval Priority**: If earlier context does not explicitly specify a knowledge retrieval priority, the default order is: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval (including `datasets/` and any file browsing/search tools). -- **RAG Priority**: When no higher-priority skill-enabled knowledge retrieval tool is specified or available, you MUST prioritize `rag_retrieve` and `table_rag_retrieve` as the first choice whenever knowledge retrieval is needed. -- **Filesystem Last**: The local filesystem is the lowest-priority source. Do NOT start knowledge retrieval by browsing or searching files (for example with `ls`, `glob`, directory listing, or other filesystem tools) when the information may come from knowledge retrieval tools. Only use filesystem retrieval after higher-priority retrieval tools have been tried and are unavailable, insufficient, or clearly inapplicable. - **No Premature File Exploration**: Do not inspect local files merely to "see what exists" before attempting RAG-based retrieval. File inspection is a fallback, not the default path. - **Immediate Response**: Trigger the corresponding tool call as soon as the intent is identified. - **Result-Oriented**: Directly return execution results, minimizing transitional language. diff --git a/skills_developing/rag-retrieve/Retrieval_Policy.md b/skills_developing/rag-retrieve/Retrieval_Policy.md new file mode 100644 index 0000000..517261e --- /dev/null +++ b/skills_developing/rag-retrieve/Retrieval_Policy.md @@ -0,0 +1,48 @@ +## Retrieval Policy (Priority & Fallback) + +### 1. Retrieval Source Priority +- If earlier context does not explicitly specify a knowledge retrieval priority, the default order is: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval (including `datasets/` and any file browsing/search tools). +- Follow this `Retrieval Policy (Priority & Fallback)` section for retrieval source selection, tool selection order, query rewrite, `top_k`, result handling, fallback, and citation requirements. +- The local filesystem is the lowest-priority source. Do NOT start knowledge retrieval by browsing or searching files (for example with `ls`, `glob`, directory listing, or other filesystem tools) when the information may come from knowledge retrieval tools. Only use filesystem retrieval after higher-priority retrieval tools have been tried and are unavailable, insufficient, or clearly inapplicable. + +### 2. Tool Selection +- When knowledge retrieval is needed and no higher-priority skill-enabled retrieval tool is specified or available, you MUST start with `rag_retrieve` or `table_rag_retrieve` based on the question type. Do NOT answer from model knowledge before trying the appropriate retrieval tool. +- Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, person / project / product name lookup, historical coverage, mixed questions, or any unclear case. +- Use `rag_retrieve` first only for clearly pure concept / definition / workflow / policy / explanation questions that do not need structured data. + +### 3. Query Preparation +- Do NOT pass the user's raw question directly unless it already fits retrieval needs well. +- Rewrite the query to improve recall: extract the core entity, time scope, attributes, and intent. +- Add meaningful variants such as synonyms, aliases, abbreviations, related titles, historical names, and category terms. +- Expand enumeration-style, historical, roster, timeline, overview, archive, extraction, and list-style queries more aggressively. +- Preserve the original meaning and do not introduce unrelated topics. Use both the original query and rewritten variants whenever possible. + +### 4. Retrieval Breadth (`top_k`) +- `top_k` applies to `rag_retrieve`. Use the smallest sufficient `top_k` and expand only when coverage is insufficient. +- Use `30` for simple fact lookup about one specific thing. +- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. +- Use `100` for broad-recall queries needing high coverage, such as comprehensive analysis, scattered knowledge, multiple entities or periods, list / catalog / timeline / roster / overview requests, or all items / historical succession / many records. +- Raise `top_k` when query rewrite produces many useful keyword branches or when results are too few, repetitive, incomplete, sparse, or too narrow in coverage. Do not raise `top_k` just because the query is longer. +- Expansion sequence: `30 -> 50 -> 100`. If uncertain, prefer `100`. + +### 5. Result Evaluation +- Treat the result as insufficient when it is empty, starts with `Error:`, says `no excel files found`, is off-topic, does not match the user's core entity / scope, or clearly contains no usable evidence. +- Treat the result as insufficient when it only covers part of the user's request, or when the user asked for a complete list, historical coverage, comparison, or mixed data + explanation but the result is only partial or truncated. + +### 6. Fallback and Sequential Retry +- If the first retrieval tool returns empty results, errors, clearly irrelevant content, or only partial coverage of the user's request, you MUST try the other retrieval tool before replying to the user. +- If the table result is empty, continue with `rag_retrieve` before concluding that no relevant data exists. +- You may say that no relevant information was found only after both `rag_retrieve` and `table_rag_retrieve` have been tried and still do not provide enough evidence to answer. + +### 7. Table Result Handling +- When processing `table_rag_retrieve` results, follow all instructions in `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` sections of the response. +- If Query result hint indicates truncation (for example, `Only the first N rows are included; the remaining M rows were omitted`), you MUST explicitly tell the user the total matches (`N+M`), displayed count (`N`), and omitted count (`M`). +- Cite data sources using file names from `file_ref_table` in the response. + +### 8. Citation Requirements for Retrieved Knowledge +- When your answer uses learned knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. +- Follow the specific citation format instructions returned by each tool. +- Citations MUST appear IMMEDIATELY AFTER the paragraph or bullet list that uses the knowledge. +- NEVER collect all citations and place them at the end of your response. +- Limit to 1-2 citations per paragraph or bullet list, combining related facts under one citation when possible. +- If your answer uses learned knowledge, you MUST generate at least 1 `` in the response. diff --git a/skills_developing/rag-retrieve/SKILL.md b/skills_developing/rag-retrieve/SKILL.md index 0eb0fbb..4584796 100644 --- a/skills_developing/rag-retrieve/SKILL.md +++ b/skills_developing/rag-retrieve/SKILL.md @@ -145,3 +145,53 @@ Executable Python script for RAG retrieval. Handles: - Markdown response parsing The script can be executed directly without loading into context. + + +## Retrieval Policy (Priority & Fallback) + +### 1. Retrieval Source Priority +- If earlier context does not explicitly specify a knowledge retrieval priority, the default order is: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval (including `datasets/` and any file browsing/search tools). +- Follow this `Retrieval Policy (Priority & Fallback)` section for retrieval source selection, tool selection order, query rewrite, `top_k`, result handling, fallback, and citation requirements. +- The local filesystem is the lowest-priority source. Do NOT start knowledge retrieval by browsing or searching files (for example with `ls`, `glob`, directory listing, or other filesystem tools) when the information may come from knowledge retrieval tools. Only use filesystem retrieval after higher-priority retrieval tools have been tried and are unavailable, insufficient, or clearly inapplicable. + +### 2. Tool Selection +- When knowledge retrieval is needed and no higher-priority skill-enabled retrieval tool is specified or available, you MUST start with `rag_retrieve` or `table_rag_retrieve` based on the question type. Do NOT answer from model knowledge before trying the appropriate retrieval tool. +- Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, person / project / product name lookup, historical coverage, mixed questions, or any unclear case. +- Use `rag_retrieve` first only for clearly pure concept / definition / workflow / policy / explanation questions that do not need structured data. + +### 3. Query Preparation +- Do NOT pass the user's raw question directly unless it already fits retrieval needs well. +- Rewrite the query to improve recall: extract the core entity, time scope, attributes, and intent. +- Add meaningful variants such as synonyms, aliases, abbreviations, related titles, historical names, and category terms. +- Expand enumeration-style, historical, roster, timeline, overview, archive, extraction, and list-style queries more aggressively. +- Preserve the original meaning and do not introduce unrelated topics. Use both the original query and rewritten variants whenever possible. + +### 4. Retrieval Breadth (`top_k`) +- `top_k` applies to `rag_retrieve`. Use the smallest sufficient `top_k` and expand only when coverage is insufficient. +- Use `30` for simple fact lookup about one specific thing. +- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. +- Use `100` for broad-recall queries needing high coverage, such as comprehensive analysis, scattered knowledge, multiple entities or periods, list / catalog / timeline / roster / overview requests, or all items / historical succession / many records. +- Raise `top_k` when query rewrite produces many useful keyword branches or when results are too few, repetitive, incomplete, sparse, or too narrow in coverage. Do not raise `top_k` just because the query is longer. +- Expansion sequence: `30 -> 50 -> 100`. If uncertain, prefer `100`. + +### 5. Result Evaluation +- Treat the result as insufficient when it is empty, starts with `Error:`, says `no excel files found`, is off-topic, does not match the user's core entity / scope, or clearly contains no usable evidence. +- Treat the result as insufficient when it only covers part of the user's request, or when the user asked for a complete list, historical coverage, comparison, or mixed data + explanation but the result is only partial or truncated. + +### 6. Fallback and Sequential Retry +- If the first retrieval tool returns empty results, errors, clearly irrelevant content, or only partial coverage of the user's request, you MUST try the other retrieval tool before replying to the user. +- If the table result is empty, continue with `rag_retrieve` before concluding that no relevant data exists. +- You may say that no relevant information was found only after both `rag_retrieve` and `table_rag_retrieve` have been tried and still do not provide enough evidence to answer. + +### 7. Table Result Handling +- When processing `table_rag_retrieve` results, follow all instructions in `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` sections of the response. +- If Query result hint indicates truncation (for example, `Only the first N rows are included; the remaining M rows were omitted`), you MUST explicitly tell the user the total matches (`N+M`), displayed count (`N`), and omitted count (`M`). +- Cite data sources using file names from `file_ref_table` in the response. + +### 8. Citation Requirements for Retrieved Knowledge +- When your answer uses learned knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. +- Follow the specific citation format instructions returned by each tool. +- Citations MUST appear IMMEDIATELY AFTER the paragraph or bullet list that uses the knowledge. +- NEVER collect all citations and place them at the end of your response. +- Limit to 1-2 citations per paragraph or bullet list, combining related facts under one citation when possible. +- If your answer uses learned knowledge, you MUST generate at least 1 `` in the response. From 753f38a072248f1693bcffdbdc5639612e023489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 17:50:35 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E6=A3=80=E7=B4=A2=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prompt/system_prompt.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/prompt/system_prompt.md b/prompt/system_prompt.md index 7751dd2..6607564 100644 --- a/prompt/system_prompt.md +++ b/prompt/system_prompt.md @@ -71,24 +71,25 @@ When creating scripts in `executable_code/`, follow these organization rules: # Retrieval Policy (Priority & Fallback) -### 1. Retrieval Source Priority +### 1. Retrieval Source Priority and Tool Selection - Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. -- Use this default source order: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. -- Treat the local filesystem as last resort. Do NOT browse or search files first when knowledge retrieval tools may answer the question. - -### 2. Tool Selection -- Start with `rag_retrieve` or `table_rag_retrieve` when knowledge retrieval is needed. Do NOT answer from model knowledge first. +- Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. +- Do NOT answer from model knowledge first. +- Do NOT skip directly to local filesystem retrieval when an earlier retrieval source may answer the question. +- When a suitable skill-enabled knowledge retrieval tool is available, use it first. +- If no suitable skill-enabled retrieval tool is available, or if its result is insufficient, continue with `rag_retrieve` or `table_rag_retrieve`. - Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, name lookup, historical coverage, mixed questions, and unclear cases. - Use `rag_retrieve` first only for clearly pure concept, definition, workflow, policy, or explanation questions without structured data needs. +- After each retrieval step, evaluate sufficiency before moving to the next source. Do NOT run these retrieval sources in parallel. -### 3. Query Preparation +### 2. Query Preparation - Do NOT pass the raw user question unless it already works well for retrieval. - Rewrite for recall: extract entity, time scope, attributes, and intent. - Add useful variants: synonyms, aliases, abbreviations, related titles, historical names, and category terms. - Expand list-style, extraction, overview, historical, roster, timeline, and archive queries more aggressively. - Preserve meaning. Do NOT introduce unrelated topics. -### 4. Retrieval Breadth (`top_k`) +### 3. Retrieval Breadth (`top_k`) - Apply `top_k` only to `rag_retrieve`. Use the smallest sufficient value, then expand only if coverage is insufficient. - Use `30` for simple fact lookup. - Use `50` for moderate synthesis, comparison, summarization, or disambiguation. @@ -96,21 +97,25 @@ When creating scripts in `executable_code/`, follow these organization rules: - Raise `top_k` when keyword branches are many or results are too few, repetitive, incomplete, sparse, or too narrow. - Use this expansion order: `30 -> 50 -> 100`. If unsure, use `100`. -### 5. Result Evaluation +### 4. Result Evaluation - Treat results as insufficient if they are empty, start with `Error:`, say `no excel files found`, are off-topic, miss the core entity or scope, or provide no usable evidence. - Also treat results as insufficient when they cover only part of the request, or when full-list, historical, comparison, or mixed data + explanation requests return only partial or truncated coverage. -### 6. Fallback and Sequential Retry -- If the first retrieval result is insufficient, call the other retrieval tool before replying. -- If `table_rag_retrieve` is empty, continue with `rag_retrieve`. -- Say no relevant information was found only after both `rag_retrieve` and `table_rag_retrieve` have been tried and still do not provide enough evidence. +### 5. Fallback and Sequential Retry +- If the first retrieval result is insufficient, call the next retrieval source in the default order before replying. +- If the first RAG tool is insufficient, call the other RAG tool next before moving to local filesystem retrieval. +- If `table_rag_retrieve` is insufficient or empty, continue with `rag_retrieve`. +- If `rag_retrieve` is insufficient or empty, continue with `table_rag_retrieve`. +- If both `rag_retrieve` and `table_rag_retrieve` are insufficient, continue with local filesystem retrieval. +- Say no relevant information was found only after all applicable skill-enabled retrieval tools, both `rag_retrieve` and `table_rag_retrieve`, and local filesystem retrieval have been tried and still do not provide enough evidence. +- Do NOT reply that no relevant information was found before the final local filesystem fallback has also been tried. -### 7. Table Result Handling +### 6. Table RAG Result Handling - Follow all `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` content in `table_rag_retrieve` results. - If results are truncated, explicitly tell the user total matches (`N+M`), displayed count (`N`), and omitted count (`M`). - Cite data sources using filenames from `file_ref_table`. -### 8. Citation Requirements for Retrieved Knowledge +### 7. Citation Requirements for Retrieved Knowledge - When using knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. - Follow the citation format returned by each tool. - Place citations immediately after the paragraph or bullet list that uses the knowledge. @@ -128,7 +133,7 @@ Trace Id: {trace_id} # Execution Guidelines - **Tool-Driven**: All operations are implemented through tool interfaces. -- **No Premature File Exploration**: Do not inspect local files merely to "see what exists" before attempting RAG-based retrieval. File inspection is a fallback, not the default path. +- **No Premature File Exploration**: Do not inspect local files merely to "see what exists" before attempting earlier knowledge retrieval sources. Local filesystem retrieval is the final fallback, not the default path, but do not skip it when earlier retrieval sources are insufficient. - **Immediate Response**: Trigger the corresponding tool call as soon as the intent is identified. - **Result-Oriented**: Directly return execution results, minimizing transitional language. - **Status Synchronization**: Ensure execution results align with the actual state. From 53fb98e44eac11871fdf5143f0413c96e517d8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 17:55:34 +0800 Subject: [PATCH 3/9] Retrieval Policy --- prompt/system_prompt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prompt/system_prompt.md b/prompt/system_prompt.md index 6607564..c0c8be9 100644 --- a/prompt/system_prompt.md +++ b/prompt/system_prompt.md @@ -69,9 +69,9 @@ When creating scripts in `executable_code/`, follow these organization rules: - Temporary script (when needed): `{agent_dir_path}/executable_code/tmp/test.py` - Downloaded file: `{agent_dir_path}/download/report.pdf` -# Retrieval Policy (Priority & Fallback) +# Retrieval Policy -### 1. Retrieval Source Priority and Tool Selection +### 1. Retrieval Order and Tool Selection - Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. - Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. - Do NOT answer from model knowledge first. From e1bf685314f752243ca709150bdf529f07436ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 19:38:13 +0800 Subject: [PATCH 4/9] add rag_retrieve autoload --- mcp/mcp_settings.json | 11 +- prompt/system_prompt.md | 54 ---- .../rag-retrieve/.claude-plugin/plugin.json | 22 ++ skills_autoload/rag-retrieve/README.md | 153 +++++++++++ .../rag-retrieve/hooks/pre_prompt.py | 20 ++ .../rag-retrieve/hooks/retrieval-policy.md | 53 ++++ skills_autoload/rag-retrieve/mcp_common.py | 251 ++++++++++++++++++ .../rag-retrieve}/rag_retrieve_server.py | 0 .../rag-retrieve}/rag_retrieve_tools.json | 0 utils/multi_project_manager.py | 38 ++- 10 files changed, 525 insertions(+), 77 deletions(-) create mode 100644 skills_autoload/rag-retrieve/.claude-plugin/plugin.json create mode 100644 skills_autoload/rag-retrieve/README.md create mode 100644 skills_autoload/rag-retrieve/hooks/pre_prompt.py create mode 100644 skills_autoload/rag-retrieve/hooks/retrieval-policy.md create mode 100644 skills_autoload/rag-retrieve/mcp_common.py rename {mcp => skills_autoload/rag-retrieve}/rag_retrieve_server.py (100%) rename {mcp/tools => skills_autoload/rag-retrieve}/rag_retrieve_tools.json (100%) diff --git a/mcp/mcp_settings.json b/mcp/mcp_settings.json index ddf9962..3aa61ae 100644 --- a/mcp/mcp_settings.json +++ b/mcp/mcp_settings.json @@ -1,14 +1,5 @@ [ { - "mcpServers": { - "rag_retrieve": { - "transport": "stdio", - "command": "python", - "args": [ - "./mcp/rag_retrieve_server.py", - "{bot_id}" - ] - } - } + "mcpServers": {} } ] diff --git a/prompt/system_prompt.md b/prompt/system_prompt.md index c0c8be9..7888edf 100644 --- a/prompt/system_prompt.md +++ b/prompt/system_prompt.md @@ -69,60 +69,6 @@ When creating scripts in `executable_code/`, follow these organization rules: - Temporary script (when needed): `{agent_dir_path}/executable_code/tmp/test.py` - Downloaded file: `{agent_dir_path}/download/report.pdf` -# Retrieval Policy - -### 1. Retrieval Order and Tool Selection -- Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. -- Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. -- Do NOT answer from model knowledge first. -- Do NOT skip directly to local filesystem retrieval when an earlier retrieval source may answer the question. -- When a suitable skill-enabled knowledge retrieval tool is available, use it first. -- If no suitable skill-enabled retrieval tool is available, or if its result is insufficient, continue with `rag_retrieve` or `table_rag_retrieve`. -- Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, name lookup, historical coverage, mixed questions, and unclear cases. -- Use `rag_retrieve` first only for clearly pure concept, definition, workflow, policy, or explanation questions without structured data needs. -- After each retrieval step, evaluate sufficiency before moving to the next source. Do NOT run these retrieval sources in parallel. - -### 2. Query Preparation -- Do NOT pass the raw user question unless it already works well for retrieval. -- Rewrite for recall: extract entity, time scope, attributes, and intent. -- Add useful variants: synonyms, aliases, abbreviations, related titles, historical names, and category terms. -- Expand list-style, extraction, overview, historical, roster, timeline, and archive queries more aggressively. -- Preserve meaning. Do NOT introduce unrelated topics. - -### 3. Retrieval Breadth (`top_k`) -- Apply `top_k` only to `rag_retrieve`. Use the smallest sufficient value, then expand only if coverage is insufficient. -- Use `30` for simple fact lookup. -- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. -- Use `100` for broad recall, such as comprehensive analysis, scattered knowledge, multiple entities or periods, or list / catalog / timeline / roster / overview requests. -- Raise `top_k` when keyword branches are many or results are too few, repetitive, incomplete, sparse, or too narrow. -- Use this expansion order: `30 -> 50 -> 100`. If unsure, use `100`. - -### 4. Result Evaluation -- Treat results as insufficient if they are empty, start with `Error:`, say `no excel files found`, are off-topic, miss the core entity or scope, or provide no usable evidence. -- Also treat results as insufficient when they cover only part of the request, or when full-list, historical, comparison, or mixed data + explanation requests return only partial or truncated coverage. - -### 5. Fallback and Sequential Retry -- If the first retrieval result is insufficient, call the next retrieval source in the default order before replying. -- If the first RAG tool is insufficient, call the other RAG tool next before moving to local filesystem retrieval. -- If `table_rag_retrieve` is insufficient or empty, continue with `rag_retrieve`. -- If `rag_retrieve` is insufficient or empty, continue with `table_rag_retrieve`. -- If both `rag_retrieve` and `table_rag_retrieve` are insufficient, continue with local filesystem retrieval. -- Say no relevant information was found only after all applicable skill-enabled retrieval tools, both `rag_retrieve` and `table_rag_retrieve`, and local filesystem retrieval have been tried and still do not provide enough evidence. -- Do NOT reply that no relevant information was found before the final local filesystem fallback has also been tried. - -### 6. Table RAG Result Handling -- Follow all `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` content in `table_rag_retrieve` results. -- If results are truncated, explicitly tell the user total matches (`N+M`), displayed count (`N`), and omitted count (`M`). -- Cite data sources using filenames from `file_ref_table`. - -### 7. Citation Requirements for Retrieved Knowledge -- When using knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. -- Follow the citation format returned by each tool. -- Place citations immediately after the paragraph or bullet list that uses the knowledge. -- Do NOT collect citations at the end. -- Use 1-2 citations per paragraph or bullet list when possible. -- If learned knowledge is used, include at least 1 ``. - # System Information Working directory: {agent_dir_path} diff --git a/skills_autoload/rag-retrieve/.claude-plugin/plugin.json b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json new file mode 100644 index 0000000..b57fdf3 --- /dev/null +++ b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "rag-retrieve", + "description": "rag-retrieve and table-rag-retrieve", + "hooks": { + "PrePrompt": [ + { + "type": "command", + "command": "python hooks/pre_prompt.py" + } + ] + }, + "mcpServers": { + "rag_retrieve": { + "transport": "stdio", + "command": "python", + "args": [ + "./skills_autoload/rag-retrieve/rag_retrieve_server.py", + "{bot_id}" + ] + } + } +} diff --git a/skills_autoload/rag-retrieve/README.md b/skills_autoload/rag-retrieve/README.md new file mode 100644 index 0000000..9177f2a --- /dev/null +++ b/skills_autoload/rag-retrieve/README.md @@ -0,0 +1,153 @@ +# User Context Loader + +用户上下文加载器示例 Skill,演示 Claude Plugins 模式的 hooks 机制。 + +## 功能说明 + +本 Skill 演示了三种 Hook 类型: + +### PrePrompt Hook +在 system_prompt 加载时执行,动态注入用户上下文信息。 +- 文件: `hooks/pre_prompt.py` +- 用途: 查询用户信息、偏好设置、历史记录等,注入到 prompt 中 + +### PostAgent Hook +在 agent 执行完成后执行,用于后处理。 +- 文件: `hooks/post_agent.py` +- 用途: 记录分析数据、触发异步任务、发送通知等 + +### PreSave Hook +在消息保存前执行,用于内容处理。 +- 文件: `hooks/pre_save.py` +- 用途: 内容过滤、敏感信息脱敏、格式转换等 + +## 目录结构 + +``` +user-context-loader/ +├── README.md # Skill 说明文档 +├── .claude-plugin/ +│ └── plugin.json # Hook 和 MCP 配置文件 +└── hooks/ + ├── pre_prompt.py # PrePrompt hook 脚本 + ├── post_agent.py # PostAgent hook 脚本 + └── pre_save.py # PreSave hook 脚本 +``` + +## plugin.json 格式 + +```json +{ + "name": "user-context-loader", + "description": "用户上下文加载器示例 Skill", + "hooks": { + "PrePrompt": [ + { + "type": "command", + "command": "python hooks/pre_prompt.py" + } + ], + "PostAgent": [ + { + "type": "command", + "command": "python hooks/post_agent.py" + } + ], + "PreSave": [ + { + "type": "command", + "command": "python hooks/pre_save.py" + } + ] + }, + "mcpServers": { + "server-name": { + "command": "node", + "args": ["path/to/server.js"], + "env": { + "API_KEY": "${API_KEY}" + } + } + } +} +``` + +## Hook 脚本格式 + +Hook 脚本通过子进程执行,通过环境变量接收参数,通过 stdout 返回结果。 + +### 可用环境变量 + +| 环境变量 | 说明 | 适用于 | +|---------|------|--------| +| `ASSISTANT_ID` | Bot ID | 所有 hook | +| `USER_IDENTIFIER` | 用户标识 | 所有 hook | +| `SESSION_ID` | 会话 ID | 所有 hook | +| `LANGUAGE` | 语言代码 | 所有 hook | +| `HOOK_TYPE` | Hook 类型 | 所有 hook | +| `CONTENT` | 消息内容 | PreSave | +| `ROLE` | 消息角色 | PreSave | +| `RESPONSE` | Agent 响应 | PostAgent | +| `METADATA` | 元数据 JSON | PostAgent | + +### PrePrompt 示例 + +```python +#!/usr/bin/env python3 +import os +import sys + +def main(): + user_identifier = os.environ.get('USER_IDENTIFIER', '') + bot_id = os.environ.get('ASSISTANT_ID', '') + + # 输出要注入到 prompt 中的内容 + print(f"## User Context\n\n用户: {user_identifier}") + return 0 + +if __name__ == '__main__': + sys.exit(main()) +``` + +### PreSave 示例 + +```python +#!/usr/bin/env python3 +import os +import sys + +def main(): + content = os.environ.get('CONTENT', '') + + # 处理内容并输出 + print(content) # 输出处理后的内容 + return 0 + +if __name__ == '__main__': + sys.exit(main()) +``` + +### PostAgent 示例 + +```python +#!/usr/bin/env python3 +import os +import sys + +def main(): + response = os.environ.get('RESPONSE', '') + session_id = os.environ.get('SESSION_ID', '') + + # 记录日志(输出到 stderr) + print(f"Session {session_id}: Response length {len(response)}", file=sys.stderr) + return 0 + +if __name__ == '__main__': + sys.exit(main()) +``` + +## 使用场景 + +1. **PrePrompt**: 用户登录时自动加载其偏好设置、历史订单等 +2. **PostAgent**: 记录对话分析数据,触发后续业务流程 +3. **PreSave**: 敏感信息脱敏后再存储,如手机号、邮箱等 diff --git a/skills_autoload/rag-retrieve/hooks/pre_prompt.py b/skills_autoload/rag-retrieve/hooks/pre_prompt.py new file mode 100644 index 0000000..11f445d --- /dev/null +++ b/skills_autoload/rag-retrieve/hooks/pre_prompt.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +PreMemoryPrompt Hook - 用户上下文加载器示例 + +在记忆提取提示词(FACT_RETRIEVAL_PROMPT)加载时执行, +读取同目录下的 memory_prompt.md 作为自定义记忆提取提示词模板。 +""" +import sys +from pathlib import Path + + +def main(): + prompt_file = Path(__file__).parent / "retrieval-policy.md" + if prompt_file.exists(): + print(prompt_file.read_text(encoding="utf-8")) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/skills_autoload/rag-retrieve/hooks/retrieval-policy.md b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md new file mode 100644 index 0000000..6527185 --- /dev/null +++ b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md @@ -0,0 +1,53 @@ +# Retrieval Policy + +### 1. Retrieval Order and Tool Selection +- Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. +- Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. +- Do NOT answer from model knowledge first. +- Do NOT skip directly to local filesystem retrieval when an earlier retrieval source may answer the question. +- When a suitable skill-enabled knowledge retrieval tool is available, use it first. +- If no suitable skill-enabled retrieval tool is available, or if its result is insufficient, continue with `rag_retrieve` or `table_rag_retrieve`. +- Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, name lookup, historical coverage, mixed questions, and unclear cases. +- Use `rag_retrieve` first only for clearly pure concept, definition, workflow, policy, or explanation questions without structured data needs. +- After each retrieval step, evaluate sufficiency before moving to the next source. Do NOT run these retrieval sources in parallel. + +### 2. Query Preparation +- Do NOT pass the raw user question unless it already works well for retrieval. +- Rewrite for recall: extract entity, time scope, attributes, and intent. +- Add useful variants: synonyms, aliases, abbreviations, related titles, historical names, and category terms. +- Expand list-style, extraction, overview, historical, roster, timeline, and archive queries more aggressively. +- Preserve meaning. Do NOT introduce unrelated topics. + +### 3. Retrieval Breadth (`top_k`) +- Apply `top_k` only to `rag_retrieve`. Use the smallest sufficient value, then expand only if coverage is insufficient. +- Use `30` for simple fact lookup. +- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. +- Use `100` for broad recall, such as comprehensive analysis, scattered knowledge, multiple entities or periods, or list / catalog / timeline / roster / overview requests. +- Raise `top_k` when keyword branches are many or results are too few, repetitive, incomplete, sparse, or too narrow. +- Use this expansion order: `30 -> 50 -> 100`. If unsure, use `100`. + +### 4. Result Evaluation +- Treat results as insufficient if they are empty, start with `Error:`, say `no excel files found`, are off-topic, miss the core entity or scope, or provide no usable evidence. +- Also treat results as insufficient when they cover only part of the request, or when full-list, historical, comparison, or mixed data + explanation requests return only partial or truncated coverage. + +### 5. Fallback and Sequential Retry +- If the first retrieval result is insufficient, call the next retrieval source in the default order before replying. +- If the first RAG tool is insufficient, call the other RAG tool next before moving to local filesystem retrieval. +- If `table_rag_retrieve` is insufficient or empty, continue with `rag_retrieve`. +- If `rag_retrieve` is insufficient or empty, continue with `table_rag_retrieve`. +- If both `rag_retrieve` and `table_rag_retrieve` are insufficient, continue with local filesystem retrieval. +- Say no relevant information was found only after all applicable skill-enabled retrieval tools, both `rag_retrieve` and `table_rag_retrieve`, and local filesystem retrieval have been tried and still do not provide enough evidence. +- Do NOT reply that no relevant information was found before the final local filesystem fallback has also been tried. + +### 6. Table RAG Result Handling +- Follow all `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` content in `table_rag_retrieve` results. +- If results are truncated, explicitly tell the user total matches (`N+M`), displayed count (`N`), and omitted count (`M`). +- Cite data sources using filenames from `file_ref_table`. + +### 7. Citation Requirements for Retrieved Knowledge +- When using knowledge from `rag_retrieve` or `table_rag_retrieve`, you MUST generate `` tags. +- Follow the citation format returned by each tool. +- Place citations immediately after the paragraph or bullet list that uses the knowledge. +- Do NOT collect citations at the end. +- Use 1-2 citations per paragraph or bullet list when possible. +- If learned knowledge is used, include at least 1 ``. diff --git a/skills_autoload/rag-retrieve/mcp_common.py b/skills_autoload/rag-retrieve/mcp_common.py new file mode 100644 index 0000000..5bf5935 --- /dev/null +++ b/skills_autoload/rag-retrieve/mcp_common.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +""" +MCP服务器通用工具函数 +提供路径处理、文件验证、请求处理等公共功能 +""" + +import json +import os +import sys +import asyncio +from typing import Any, Dict, List, Optional, Union +import re + +def get_allowed_directory(): + """获取允许访问的目录""" + # 优先使用命令行参数传入的dataset_dir + if len(sys.argv) > 1: + dataset_dir = sys.argv[1] + return os.path.abspath(dataset_dir) + + # 从环境变量读取项目数据目录 + project_dir = os.getenv("PROJECT_DATA_DIR", "./projects/data") + return os.path.abspath(project_dir) + + +def resolve_file_path(file_path: str, default_subfolder: str = "default") -> str: + """ + 解析文件路径,支持 folder/document.txt 和 document.txt 两种格式 + + Args: + file_path: 输入的文件路径 + default_subfolder: 当只传入文件名时使用的默认子文件夹名称 + + Returns: + 解析后的完整文件路径 + """ + # 如果路径包含文件夹分隔符,直接使用 + if '/' in file_path or '\\' in file_path: + clean_path = file_path.replace('\\', '/') + + # 移除 projects/ 前缀(如果存在) + if clean_path.startswith('projects/'): + clean_path = clean_path[9:] # 移除 'projects/' 前缀 + elif clean_path.startswith('./projects/'): + clean_path = clean_path[11:] # 移除 './projects/' 前缀 + else: + # 如果只有文件名,添加默认子文件夹 + clean_path = f"{default_subfolder}/{file_path}" + + # 获取允许的目录 + project_data_dir = get_allowed_directory() + + # 尝试在项目目录中查找文件 + full_path = os.path.join(project_data_dir, clean_path.lstrip('./')) + if os.path.exists(full_path): + return full_path + + # 如果直接路径不存在,尝试递归查找 + found = find_file_in_project(clean_path, project_data_dir) + if found: + return found + + # 如果是纯文件名且在default子文件夹中不存在,尝试在根目录查找 + if '/' not in file_path and '\\' not in file_path: + root_path = os.path.join(project_data_dir, file_path) + if os.path.exists(root_path): + return root_path + + raise FileNotFoundError(f"File not found: {file_path} (searched in {project_data_dir})") + + +def find_file_in_project(filename: str, project_dir: str) -> Optional[str]: + """在项目目录中递归查找文件""" + # 如果filename包含路径,只搜索指定的路径 + if '/' in filename: + parts = filename.split('/') + target_file = parts[-1] + search_dir = os.path.join(project_dir, *parts[:-1]) + + if os.path.exists(search_dir): + target_path = os.path.join(search_dir, target_file) + if os.path.exists(target_path): + return target_path + else: + # 纯文件名,递归搜索整个项目目录 + for root, dirs, files in os.walk(project_dir): + if filename in files: + return os.path.join(root, filename) + return None + + +def load_tools_from_json(tools_file_name: str) -> List[Dict[str, Any]]: + """从 JSON 文件加载工具定义""" + try: + tools_file = os.path.join(os.path.dirname(__file__), tools_file_name) + if os.path.exists(tools_file): + with open(tools_file, 'r', encoding='utf-8') as f: + return json.load(f) + else: + # 如果 JSON 文件不存在,使用默认定义 + return [] + except Exception as e: + print(f"Warning: Unable to load tool definition JSON file: {str(e)}") + return [] + + +def create_error_response(request_id: Any, code: int, message: str) -> Dict[str, Any]: + """创建标准化的错误响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": code, + "message": message + } + } + + +def create_success_response(request_id: Any, result: Any) -> Dict[str, Any]: + """创建标准化的成功响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": result + } + + +def create_initialize_response(request_id: Any, server_name: str, server_version: str = "1.0.0") -> Dict[str, Any]: + """创建标准化的初始化响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": server_name, + "version": server_version + } + } + } + + +def create_ping_response(request_id: Any) -> Dict[str, Any]: + """创建标准化的ping响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "pong": True + } + } + + +def create_tools_list_response(request_id: Any, tools: List[Dict[str, Any]]) -> Dict[str, Any]: + """创建标准化的工具列表响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + +def is_regex_pattern(pattern: str) -> bool: + """检测字符串是否为正则表达式模式""" + # 检查 /pattern/ 格式 + if pattern.startswith('/') and pattern.endswith('/') and len(pattern) > 2: + return True + + # 检查 r"pattern" 或 r'pattern' 格式 + if pattern.startswith(('r"', "r'")) and pattern.endswith(('"', "'")) and len(pattern) > 3: + return True + + # 检查是否包含正则特殊字符 + regex_chars = {'*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '^', '$', '\\', '.'} + return any(char in pattern for char in regex_chars) + + +def compile_pattern(pattern: str) -> Union[re.Pattern, str, None]: + """编译正则表达式模式,如果不是正则则返回原字符串""" + if not is_regex_pattern(pattern): + return pattern + + try: + # 处理 /pattern/ 格式 + if pattern.startswith('/') and pattern.endswith('/'): + regex_body = pattern[1:-1] + return re.compile(regex_body) + + # 处理 r"pattern" 或 r'pattern' 格式 + if pattern.startswith(('r"', "r'")) and pattern.endswith(('"', "'")): + regex_body = pattern[2:-1] + return re.compile(regex_body) + + # 直接编译包含正则字符的字符串 + return re.compile(pattern) + except re.error as e: + # 如果编译失败,返回None表示无效的正则 + print(f"Warning: Regular expression '{pattern}' compilation failed: {e}") + return None + + +async def handle_mcp_streaming(request_handler): + """处理MCP请求的标准主循环""" + try: + while True: + # Read from stdin + line = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline) + if not line: + break + + line = line.strip() + if not line: + continue + + try: + request = json.loads(line) + response = await request_handler(request) + + # Write to stdout + sys.stdout.write(json.dumps(response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except json.JSONDecodeError: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": "Parse error" + } + } + sys.stdout.write(json.dumps(error_response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except Exception as e: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + } + sys.stdout.write(json.dumps(error_response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except KeyboardInterrupt: + pass diff --git a/mcp/rag_retrieve_server.py b/skills_autoload/rag-retrieve/rag_retrieve_server.py similarity index 100% rename from mcp/rag_retrieve_server.py rename to skills_autoload/rag-retrieve/rag_retrieve_server.py diff --git a/mcp/tools/rag_retrieve_tools.json b/skills_autoload/rag-retrieve/rag_retrieve_tools.json similarity index 100% rename from mcp/tools/rag_retrieve_tools.json rename to skills_autoload/rag-retrieve/rag_retrieve_tools.json diff --git a/utils/multi_project_manager.py b/utils/multi_project_manager.py index be29923..a5bca91 100644 --- a/utils/multi_project_manager.py +++ b/utils/multi_project_manager.py @@ -320,6 +320,12 @@ def create_robot_project(dataset_ids: List[str], bot_id: str, force_rebuild: boo str: 机器人项目目录路径 """ + skills = list(skills or []) + has_rag_retrieve = any(Path(skill.lstrip("@")).name == "rag-retrieve" for skill in skills) + if dataset_ids and not has_rag_retrieve: + skills.append("@skills_autoload/rag-retrieve") + logger.info("Auto loaded skill '@skills_autoload/rag-retrieve' because dataset_ids is not empty") + logger.info(f"Ensuring robot project exists: {bot_id}, skills: {skills}") # 创建机器人目录结构(如果不存在) @@ -375,27 +381,27 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) - 如果是简单名称(如 "rag-retrieve"),从以下目录按优先级顺序查找: 1. projects/uploads/{bot_id}/skills/ 2. skills/ + - 如果是以 @ 开头的仓库相对路径(如 "@skills_autoload/rag-retrieve"),则从仓库根目录直接解析 搜索目录优先级:先搜索 projects/uploads/{bot_id}/skills/,再搜索 skills/ Args: bot_id: 机器人 ID - skills: 技能文件名列表(如 ["rag-retrieve", "projects/uploads/{bot_id}/skills/rag-retrieve"]) + skills: 技能文件名列表(如 ["rag-retrieve", "@skills_autoload/rag-retrieve", "projects/uploads/{bot_id}/skills/rag-retrieve"]) project_path: 项目路径 """ - import zipfile - # skills 源目录(按优先级顺序) + repo_root = Path(__file__).resolve().parent.parent skills_source_dirs = [ project_path / "uploads" / bot_id / "skills", - Path("skills"), + repo_root / "skills", ] skills_target_dir = project_path / "robot" / bot_id / "skills" skills_target_dir.mkdir(parents=True, exist_ok=True) logger.info(f"Copying skills to {skills_target_dir}") # 清理不在列表中的多余 skill 文件夹 - expected_skill_names = {os.path.basename(skill) for skill in skills} + expected_skill_names = {Path(skill.lstrip("@")).name for skill in skills} if skills_target_dir.exists(): for item in skills_target_dir.iterdir(): if item.is_dir() and item.name not in expected_skill_names: @@ -403,7 +409,8 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) shutil.rmtree(item) for skill in skills: - target_dir = skills_target_dir / os.path.basename(skill) + skill_name = Path(skill.lstrip("@")).name + target_dir = skills_target_dir / skill_name # 如果目标目录已存在,跳过复制 if target_dir.exists(): @@ -412,20 +419,25 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) source_dir = None - # 简单名称:按优先级顺序在多个目录中查找 - for base_dir in skills_source_dirs: - candidate_dir = base_dir / skill + if skill.startswith("@"): + candidate_dir = repo_root / skill.lstrip("@") if candidate_dir.exists(): source_dir = candidate_dir - logger.info(f" Found skill '{skill}' in {base_dir}") - break + logger.info(f" Found skill '{skill}' at {candidate_dir}") + + # 简单名称:按优先级顺序在多个目录中查找 + if source_dir is None: + for base_dir in skills_source_dirs: + candidate_dir = base_dir / skill + if candidate_dir.exists(): + source_dir = candidate_dir + logger.info(f" Found skill '{skill}' in {base_dir}") + break if source_dir is None: logger.warning(f" Skill directory '{skill}' not found in any source directory: {[str(d) for d in skills_source_dirs]}") continue - target_dir = skills_target_dir / os.path.basename(skill) - try: shutil.copytree(source_dir, target_dir) logger.info(f" Copied: {source_dir} -> {target_dir}") From 9d47324a76289dfb4dffe19146e2ab61cf28efeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 20:09:02 +0800 Subject: [PATCH 5/9] add rag_retrieve-only --- .../.claude-plugin/plugin.json | 22 ++ skills/rag-retrieve-only/README.md | 34 +++ skills/rag-retrieve-only/hooks/pre_prompt.py | 20 ++ .../hooks/retrieval-policy.md | 31 +++ skills/rag-retrieve-only/mcp_common.py | 251 ++++++++++++++++++ .../rag-retrieve-only/rag_retrieve_server.py | 222 ++++++++++++++++ .../rag-retrieve-only/rag_retrieve_tools.json | 21 ++ .../rag-retrieve/.claude-plugin/plugin.json | 2 +- skills_autoload/rag-retrieve/README.md | 154 ++++------- .../Retrieval_Policy.md | 0 .../SKILL.md | 0 .../scripts/rag_retrieve.py | 0 .../skill.yaml | 0 utils/multi_project_manager.py | 3 +- 14 files changed, 654 insertions(+), 106 deletions(-) create mode 100644 skills/rag-retrieve-only/.claude-plugin/plugin.json create mode 100644 skills/rag-retrieve-only/README.md create mode 100644 skills/rag-retrieve-only/hooks/pre_prompt.py create mode 100644 skills/rag-retrieve-only/hooks/retrieval-policy.md create mode 100644 skills/rag-retrieve-only/mcp_common.py create mode 100644 skills/rag-retrieve-only/rag_retrieve_server.py create mode 100644 skills/rag-retrieve-only/rag_retrieve_tools.json rename skills_developing/{rag-retrieve => rag-retrieve-cli}/Retrieval_Policy.md (100%) rename skills_developing/{rag-retrieve => rag-retrieve-cli}/SKILL.md (100%) rename skills_developing/{rag-retrieve => rag-retrieve-cli}/scripts/rag_retrieve.py (100%) rename skills_developing/{rag-retrieve => rag-retrieve-cli}/skill.yaml (100%) diff --git a/skills/rag-retrieve-only/.claude-plugin/plugin.json b/skills/rag-retrieve-only/.claude-plugin/plugin.json new file mode 100644 index 0000000..704ff52 --- /dev/null +++ b/skills/rag-retrieve-only/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "rag-retrieve-only", + "description": "Only provides rag_retrieve. table_rag_retrieve and local file retrieval are disabled.", + "hooks": { + "PrePrompt": [ + { + "type": "command", + "command": "python hooks/pre_prompt.py" + } + ] + }, + "mcpServers": { + "rag_retrieve": { + "transport": "stdio", + "command": "python", + "args": [ + "./skills/rag-retrieve-only/rag_retrieve_server.py", + "{bot_id}" + ] + } + } +} diff --git a/skills/rag-retrieve-only/README.md b/skills/rag-retrieve-only/README.md new file mode 100644 index 0000000..55bdbdc --- /dev/null +++ b/skills/rag-retrieve-only/README.md @@ -0,0 +1,34 @@ +# rag-retrieve + +只保留 `rag_retrieve` 的精简版插件示例。 + +## 功能说明 + +- 通过 `PrePrompt` Hook 注入检索策略 +- 暴露 `rag_retrieve` MCP Server +- 插件仅支持 `rag_retrieve` +- 已禁用 `table_rag_retrieve` +- 已禁用本地文件检索 + +## 目录结构 + +```text +rag-retrieve-only/ +├── README.md +├── .claude-plugin/ +│ └── plugin.json +├── hooks/ +│ ├── pre_prompt.py +│ └── retrieval-policy.md +├── rag_retrieve_server.py +└── rag_retrieve_tools.json +``` + +## 当前检索策略 + +默认顺序:skill-enabled knowledge retrieval tools > `rag_retrieve` + +- 优先使用可用的技能内知识检索工具 +- 不足时使用 `rag_retrieve` +- 不并行执行多个检索源 +- 插件仅支持 `rag_retrieve` diff --git a/skills/rag-retrieve-only/hooks/pre_prompt.py b/skills/rag-retrieve-only/hooks/pre_prompt.py new file mode 100644 index 0000000..11f445d --- /dev/null +++ b/skills/rag-retrieve-only/hooks/pre_prompt.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +PreMemoryPrompt Hook - 用户上下文加载器示例 + +在记忆提取提示词(FACT_RETRIEVAL_PROMPT)加载时执行, +读取同目录下的 memory_prompt.md 作为自定义记忆提取提示词模板。 +""" +import sys +from pathlib import Path + + +def main(): + prompt_file = Path(__file__).parent / "retrieval-policy.md" + if prompt_file.exists(): + print(prompt_file.read_text(encoding="utf-8")) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/skills/rag-retrieve-only/hooks/retrieval-policy.md b/skills/rag-retrieve-only/hooks/retrieval-policy.md new file mode 100644 index 0000000..09732b7 --- /dev/null +++ b/skills/rag-retrieve-only/hooks/retrieval-policy.md @@ -0,0 +1,31 @@ +# Retrieval Policy + +- `rag_retrieve` is the only knowledge source. +- Do NOT answer from model knowledge first. + +## 1.Query Preparation +- Do NOT pass the raw user question unless it already works well for retrieval. +- Rewrite for recall: extract entity, time scope, attributes, and intent. +- Add useful variants: synonyms, aliases, abbreviations, related titles, historical names, and category terms. +- Expand list-style, extraction, overview, historical, roster, timeline, and archive queries more aggressively. +- Preserve meaning. Do NOT introduce unrelated topics. + +## 2.Retrieval Breadth (`top_k`) +- Apply `top_k` only to `rag_retrieve`. Use the smallest sufficient value, then expand only if coverage is insufficient. +- Use `30` for simple fact lookup. +- Use `50` for moderate synthesis, comparison, summarization, or disambiguation. +- Use `100` for broad recall, such as comprehensive analysis, scattered knowledge, multiple entities or periods, or list / catalog / timeline / roster / overview requests. +- Raise `top_k` when keyword branches are many or results are too few, repetitive, incomplete, sparse, or too narrow. +- Use this expansion order: `30 -> 50 -> 100`. If unsure, use `100`. + +## 3.Retry +- If the result is insufficient, retry `rag_retrieve` with a better rewritten query or a larger `top_k`. +- Only say no relevant information was found after `rag_retrieve` has been tried and still provides insufficient evidence. + +## 4.Citation Requirements for Retrieved Knowledge +- When using knowledge from `rag_retrieve`, you MUST generate `` tags. +- Follow the citation format returned by each tool. +- Place citations immediately after the paragraph or bullet list that uses the knowledge. +- Do NOT collect citations at the end. +- Use 1-2 citations per paragraph or bullet list when possible. +- If learned knowledge is used, include at least 1 ``. diff --git a/skills/rag-retrieve-only/mcp_common.py b/skills/rag-retrieve-only/mcp_common.py new file mode 100644 index 0000000..5bf5935 --- /dev/null +++ b/skills/rag-retrieve-only/mcp_common.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +""" +MCP服务器通用工具函数 +提供路径处理、文件验证、请求处理等公共功能 +""" + +import json +import os +import sys +import asyncio +from typing import Any, Dict, List, Optional, Union +import re + +def get_allowed_directory(): + """获取允许访问的目录""" + # 优先使用命令行参数传入的dataset_dir + if len(sys.argv) > 1: + dataset_dir = sys.argv[1] + return os.path.abspath(dataset_dir) + + # 从环境变量读取项目数据目录 + project_dir = os.getenv("PROJECT_DATA_DIR", "./projects/data") + return os.path.abspath(project_dir) + + +def resolve_file_path(file_path: str, default_subfolder: str = "default") -> str: + """ + 解析文件路径,支持 folder/document.txt 和 document.txt 两种格式 + + Args: + file_path: 输入的文件路径 + default_subfolder: 当只传入文件名时使用的默认子文件夹名称 + + Returns: + 解析后的完整文件路径 + """ + # 如果路径包含文件夹分隔符,直接使用 + if '/' in file_path or '\\' in file_path: + clean_path = file_path.replace('\\', '/') + + # 移除 projects/ 前缀(如果存在) + if clean_path.startswith('projects/'): + clean_path = clean_path[9:] # 移除 'projects/' 前缀 + elif clean_path.startswith('./projects/'): + clean_path = clean_path[11:] # 移除 './projects/' 前缀 + else: + # 如果只有文件名,添加默认子文件夹 + clean_path = f"{default_subfolder}/{file_path}" + + # 获取允许的目录 + project_data_dir = get_allowed_directory() + + # 尝试在项目目录中查找文件 + full_path = os.path.join(project_data_dir, clean_path.lstrip('./')) + if os.path.exists(full_path): + return full_path + + # 如果直接路径不存在,尝试递归查找 + found = find_file_in_project(clean_path, project_data_dir) + if found: + return found + + # 如果是纯文件名且在default子文件夹中不存在,尝试在根目录查找 + if '/' not in file_path and '\\' not in file_path: + root_path = os.path.join(project_data_dir, file_path) + if os.path.exists(root_path): + return root_path + + raise FileNotFoundError(f"File not found: {file_path} (searched in {project_data_dir})") + + +def find_file_in_project(filename: str, project_dir: str) -> Optional[str]: + """在项目目录中递归查找文件""" + # 如果filename包含路径,只搜索指定的路径 + if '/' in filename: + parts = filename.split('/') + target_file = parts[-1] + search_dir = os.path.join(project_dir, *parts[:-1]) + + if os.path.exists(search_dir): + target_path = os.path.join(search_dir, target_file) + if os.path.exists(target_path): + return target_path + else: + # 纯文件名,递归搜索整个项目目录 + for root, dirs, files in os.walk(project_dir): + if filename in files: + return os.path.join(root, filename) + return None + + +def load_tools_from_json(tools_file_name: str) -> List[Dict[str, Any]]: + """从 JSON 文件加载工具定义""" + try: + tools_file = os.path.join(os.path.dirname(__file__), tools_file_name) + if os.path.exists(tools_file): + with open(tools_file, 'r', encoding='utf-8') as f: + return json.load(f) + else: + # 如果 JSON 文件不存在,使用默认定义 + return [] + except Exception as e: + print(f"Warning: Unable to load tool definition JSON file: {str(e)}") + return [] + + +def create_error_response(request_id: Any, code: int, message: str) -> Dict[str, Any]: + """创建标准化的错误响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": code, + "message": message + } + } + + +def create_success_response(request_id: Any, result: Any) -> Dict[str, Any]: + """创建标准化的成功响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": result + } + + +def create_initialize_response(request_id: Any, server_name: str, server_version: str = "1.0.0") -> Dict[str, Any]: + """创建标准化的初始化响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": server_name, + "version": server_version + } + } + } + + +def create_ping_response(request_id: Any) -> Dict[str, Any]: + """创建标准化的ping响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "pong": True + } + } + + +def create_tools_list_response(request_id: Any, tools: List[Dict[str, Any]]) -> Dict[str, Any]: + """创建标准化的工具列表响应""" + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": tools + } + } + + +def is_regex_pattern(pattern: str) -> bool: + """检测字符串是否为正则表达式模式""" + # 检查 /pattern/ 格式 + if pattern.startswith('/') and pattern.endswith('/') and len(pattern) > 2: + return True + + # 检查 r"pattern" 或 r'pattern' 格式 + if pattern.startswith(('r"', "r'")) and pattern.endswith(('"', "'")) and len(pattern) > 3: + return True + + # 检查是否包含正则特殊字符 + regex_chars = {'*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '^', '$', '\\', '.'} + return any(char in pattern for char in regex_chars) + + +def compile_pattern(pattern: str) -> Union[re.Pattern, str, None]: + """编译正则表达式模式,如果不是正则则返回原字符串""" + if not is_regex_pattern(pattern): + return pattern + + try: + # 处理 /pattern/ 格式 + if pattern.startswith('/') and pattern.endswith('/'): + regex_body = pattern[1:-1] + return re.compile(regex_body) + + # 处理 r"pattern" 或 r'pattern' 格式 + if pattern.startswith(('r"', "r'")) and pattern.endswith(('"', "'")): + regex_body = pattern[2:-1] + return re.compile(regex_body) + + # 直接编译包含正则字符的字符串 + return re.compile(pattern) + except re.error as e: + # 如果编译失败,返回None表示无效的正则 + print(f"Warning: Regular expression '{pattern}' compilation failed: {e}") + return None + + +async def handle_mcp_streaming(request_handler): + """处理MCP请求的标准主循环""" + try: + while True: + # Read from stdin + line = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline) + if not line: + break + + line = line.strip() + if not line: + continue + + try: + request = json.loads(line) + response = await request_handler(request) + + # Write to stdout + sys.stdout.write(json.dumps(response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except json.JSONDecodeError: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": "Parse error" + } + } + sys.stdout.write(json.dumps(error_response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except Exception as e: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + } + sys.stdout.write(json.dumps(error_response, ensure_ascii=False) + "\n") + sys.stdout.flush() + + except KeyboardInterrupt: + pass diff --git a/skills/rag-retrieve-only/rag_retrieve_server.py b/skills/rag-retrieve-only/rag_retrieve_server.py new file mode 100644 index 0000000..88b093f --- /dev/null +++ b/skills/rag-retrieve-only/rag_retrieve_server.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +""" +RAG检索MCP服务器 +调用本地RAG API进行文档检索 +""" + +import asyncio +import hashlib +import json +import sys +import os +from typing import Any, Dict + +try: + import requests +except ImportError: + print("Error: requests module is required. Please install it with: pip install requests") + sys.exit(1) + +from mcp_common import ( + create_error_response, + create_success_response, + create_initialize_response, + create_ping_response, + create_tools_list_response, + load_tools_from_json, + handle_mcp_streaming +) +BACKEND_HOST = os.getenv("BACKEND_HOST", "https://api-dev.gptbase.ai") +MASTERKEY = os.getenv("MASTERKEY", "master") + +# Citation instruction prefixes injected into tool results +DOCUMENT_CITATION_INSTRUCTIONS = """ +When using the retrieved knowledge below, you MUST add XML citation tags for factual claims. + +## Document Knowledge +Format: `` +- Use `file` attribute with the UUID from document markers +- Use `filename` attribute with the actual filename from document markers +- Use `page` attribute (singular) with the page number +- `page` MUST be 0-based and must match the `pages:` values shown in the learned knowledge context + +## Web Page Knowledge +Format: `` +- Use `url` attribute with the web page URL from the source metadata +- Do not use `file`, `filename`, or `page` attributes for web sources +- If content is grounded in a web source, prefer a web citation with `url` over a file citation + +## Placement Rules +- Citations MUST appear IMMEDIATELY AFTER the paragraph or bullet list that uses the knowledge +- NEVER collect all citations and place them at the end of your response +- Limit to 1-2 citations per paragraph/bullet list +- If your answer uses learned knowledge, you MUST generate at least 1 `` in the response + + +""" + +def rag_retrieve(query: str, top_k: int = 100) -> Dict[str, Any]: + """调用RAG检索API""" + try: + bot_id = "" + if len(sys.argv) > 1: + bot_id = sys.argv[1] + + url = f"{BACKEND_HOST}/v1/rag_retrieve/{bot_id}" + if not url: + return { + "content": [ + { + "type": "text", + "text": "Error: RAG API URL not provided. Please provide URL as command line argument." + } + ] + } + + masterkey = MASTERKEY + token_input = f"{masterkey}:{bot_id}" + auth_token = hashlib.md5(token_input.encode()).hexdigest() + + headers = { + "content-type": "application/json", + "authorization": f"Bearer {auth_token}" + } + data = { + "query": query, + "top_k": top_k + } + + response = requests.post(url, json=data, headers=headers, timeout=30) + + if response.status_code != 200: + return { + "content": [ + { + "type": "text", + "text": f"Error: RAG API returned status code {response.status_code}. Response: {response.text}" + } + ] + } + + try: + response_data = response.json() + except json.JSONDecodeError as e: + return { + "content": [ + { + "type": "text", + "text": f"Error: Failed to parse API response as JSON. Error: {str(e)}, Raw response: {response.text}" + } + ] + } + + if "markdown" in response_data: + markdown_content = response_data["markdown"] + return { + "content": [ + { + "type": "text", + "text": DOCUMENT_CITATION_INSTRUCTIONS + markdown_content + } + ] + } + else: + return { + "content": [ + { + "type": "text", + "text": f"Error: 'markdown' field not found in API response. Response: {json.dumps(response_data, indent=2, ensure_ascii=False)}" + } + ] + } + + except requests.exceptions.RequestException as e: + return { + "content": [ + { + "type": "text", + "text": f"Error: Failed to connect to RAG API. {str(e)}" + } + ] + } + except Exception as e: + return { + "content": [ + { + "type": "text", + "text": f"Error: {str(e)}" + } + ] + } + + +async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: + """Handle MCP request""" + try: + method = request.get("method") + params = request.get("params", {}) + request_id = request.get("id") + + if method == "initialize": + return create_initialize_response(request_id, "rag-retrieve") + + elif method == "ping": + return create_ping_response(request_id) + + elif method == "tools/list": + tools = load_tools_from_json("rag_retrieve_tools.json") + if not tools: + tools = [ + { + "name": "rag_retrieve", + "description": "调用RAG检索API,根据查询内容检索相关文档。返回包含相关内容的markdown格式结果。", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "检索查询内容" + } + }, + "required": ["query"] + } + } + ] + return create_tools_list_response(request_id, tools) + + elif method == "tools/call": + tool_name = params.get("name") + arguments = params.get("arguments", {}) + + if tool_name == "rag_retrieve": + query = arguments.get("query", "") + top_k = arguments.get("top_k", 100) + + if not query: + return create_error_response(request_id, -32602, "Missing required parameter: query") + + result = rag_retrieve(query, top_k) + + return { + "jsonrpc": "2.0", + "id": request_id, + "result": result + } + + else: + return create_error_response(request_id, -32601, f"Unknown tool: {tool_name}") + + else: + return create_error_response(request_id, -32601, f"Unknown method: {method}") + + except Exception as e: + return create_error_response(request.get("id"), -32603, f"Internal error: {str(e)}") + + +async def main(): + """Main entry point.""" + await handle_mcp_streaming(handle_request) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/skills/rag-retrieve-only/rag_retrieve_tools.json b/skills/rag-retrieve-only/rag_retrieve_tools.json new file mode 100644 index 0000000..91d52cf --- /dev/null +++ b/skills/rag-retrieve-only/rag_retrieve_tools.json @@ -0,0 +1,21 @@ +[ + { + "name": "rag_retrieve", + "description": "Retrieve relevant documents from the knowledge base. Returns markdown results. Use this tool for concept, definition, workflow, policy, explanation, and general knowledge lookup. Rewrite the query when needed to improve recall.", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Retrieval query content. Rewrite the query when needed to improve recall." + }, + "top_k": { + "type": "integer", + "description": "Number of top results to retrieve. Choose dynamically based on retrieval breadth and coverage needs.", + "default": 100 + } + }, + "required": ["query"] + } + } +] diff --git a/skills_autoload/rag-retrieve/.claude-plugin/plugin.json b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json index b57fdf3..925751c 100644 --- a/skills_autoload/rag-retrieve/.claude-plugin/plugin.json +++ b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "rag-retrieve", - "description": "rag-retrieve and table-rag-retrieve", + "description": "Provides RAG and table RAG retrieval tools through a PrePrompt hook and MCP server.", "hooks": { "PrePrompt": [ { diff --git a/skills_autoload/rag-retrieve/README.md b/skills_autoload/rag-retrieve/README.md index 9177f2a..acf13ac 100644 --- a/skills_autoload/rag-retrieve/README.md +++ b/skills_autoload/rag-retrieve/README.md @@ -1,153 +1,99 @@ -# User Context Loader +# RAG Retrieve -用户上下文加载器示例 Skill,演示 Claude Plugins 模式的 hooks 机制。 +An example autoload skill that demonstrates how to integrate `rag-retrieve` and `table-rag-retrieve` through Claude Plugins hooks and an MCP server. -## 功能说明 +## Overview -本 Skill 演示了三种 Hook 类型: +This skill uses a `PrePrompt` hook to inject retrieval guidance into the prompt, and starts an MCP server that exposes retrieval capabilities for the current bot. ### PrePrompt Hook -在 system_prompt 加载时执行,动态注入用户上下文信息。 -- 文件: `hooks/pre_prompt.py` -- 用途: 查询用户信息、偏好设置、历史记录等,注入到 prompt 中 +Runs when the system prompt is loaded and injects retrieval policy content. +- File: `hooks/pre_prompt.py` +- Purpose: load retrieval instructions and add them to the prompt context -### PostAgent Hook -在 agent 执行完成后执行,用于后处理。 -- 文件: `hooks/post_agent.py` -- 用途: 记录分析数据、触发异步任务、发送通知等 +### MCP Server +Provides retrieval tools over stdio for the current `bot_id`. +- File: `rag_retrieve_server.py` +- Purpose: expose `rag-retrieve` and related retrieval tools to the agent -### PreSave Hook -在消息保存前执行,用于内容处理。 -- 文件: `hooks/pre_save.py` -- 用途: 内容过滤、敏感信息脱敏、格式转换等 +## Directory Structure -## 目录结构 - -``` -user-context-loader/ -├── README.md # Skill 说明文档 +```text +rag-retrieve/ +├── README.md # Skill documentation ├── .claude-plugin/ -│ └── plugin.json # Hook 和 MCP 配置文件 -└── hooks/ - ├── pre_prompt.py # PrePrompt hook 脚本 - ├── post_agent.py # PostAgent hook 脚本 - └── pre_save.py # PreSave hook 脚本 +│ └── plugin.json # Hook and MCP server configuration +├── hooks/ +│ ├── pre_prompt.py # PrePrompt hook script +│ └── retrieval-policy.md # Retrieval policy injected into the prompt +├── mcp_common.py # Shared MCP utilities +├── rag_retrieve_server.py # MCP server entrypoint +└── rag_retrieve_tools.json # Tool definitions ``` -## plugin.json 格式 +## `plugin.json` Format ```json { - "name": "user-context-loader", - "description": "用户上下文加载器示例 Skill", + "name": "rag-retrieve", + "description": "rag-retrieve and table-rag-retrieve", "hooks": { "PrePrompt": [ { "type": "command", "command": "python hooks/pre_prompt.py" } - ], - "PostAgent": [ - { - "type": "command", - "command": "python hooks/post_agent.py" - } - ], - "PreSave": [ - { - "type": "command", - "command": "python hooks/pre_save.py" - } ] }, "mcpServers": { - "server-name": { - "command": "node", - "args": ["path/to/server.js"], - "env": { - "API_KEY": "${API_KEY}" - } + "rag_retrieve": { + "transport": "stdio", + "command": "python", + "args": [ + "./skills_autoload/rag-retrieve/rag_retrieve_server.py", + "{bot_id}" + ] } } } ``` -## Hook 脚本格式 +## Hook Script Behavior -Hook 脚本通过子进程执行,通过环境变量接收参数,通过 stdout 返回结果。 +The hook script runs as a subprocess, receives input through environment variables, and writes the injected content to stdout. -### 可用环境变量 +### Available Environment Variables -| 环境变量 | 说明 | 适用于 | -|---------|------|--------| -| `ASSISTANT_ID` | Bot ID | 所有 hook | -| `USER_IDENTIFIER` | 用户标识 | 所有 hook | -| `SESSION_ID` | 会话 ID | 所有 hook | -| `LANGUAGE` | 语言代码 | 所有 hook | -| `HOOK_TYPE` | Hook 类型 | 所有 hook | -| `CONTENT` | 消息内容 | PreSave | -| `ROLE` | 消息角色 | PreSave | -| `RESPONSE` | Agent 响应 | PostAgent | -| `METADATA` | 元数据 JSON | PostAgent | +| Environment Variable | Description | Applies To | +|----------------------|-------------|------------| +| `ASSISTANT_ID` | Bot ID | All hooks | +| `USER_IDENTIFIER` | User identifier | All hooks | +| `SESSION_ID` | Session ID | All hooks | +| `LANGUAGE` | Language code | All hooks | +| `HOOK_TYPE` | Hook type | All hooks | -### PrePrompt 示例 +### PrePrompt Example ```python #!/usr/bin/env python3 import os import sys + def main(): user_identifier = os.environ.get('USER_IDENTIFIER', '') bot_id = os.environ.get('ASSISTANT_ID', '') - # 输出要注入到 prompt 中的内容 - print(f"## User Context\n\n用户: {user_identifier}") + print(f"## Retrieval Context\n\nUser: {user_identifier}\nBot: {bot_id}") return 0 + if __name__ == '__main__': sys.exit(main()) ``` -### PreSave 示例 +## Example Use Cases -```python -#!/usr/bin/env python3 -import os -import sys - -def main(): - content = os.environ.get('CONTENT', '') - - # 处理内容并输出 - print(content) # 输出处理后的内容 - return 0 - -if __name__ == '__main__': - sys.exit(main()) -``` - -### PostAgent 示例 - -```python -#!/usr/bin/env python3 -import os -import sys - -def main(): - response = os.environ.get('RESPONSE', '') - session_id = os.environ.get('SESSION_ID', '') - - # 记录日志(输出到 stderr) - print(f"Session {session_id}: Response length {len(response)}", file=sys.stderr) - return 0 - -if __name__ == '__main__': - sys.exit(main()) -``` - -## 使用场景 - -1. **PrePrompt**: 用户登录时自动加载其偏好设置、历史订单等 -2. **PostAgent**: 记录对话分析数据,触发后续业务流程 -3. **PreSave**: 敏感信息脱敏后再存储,如手机号、邮箱等 +1. **Prompt-time retrieval guidance**: inject retrieval rules before the model starts reasoning +2. **Bot-specific retrieval setup**: start the MCP server with the current `bot_id` +3. **Unified retrieval access**: expose RAG and table RAG tools through a single skill diff --git a/skills_developing/rag-retrieve/Retrieval_Policy.md b/skills_developing/rag-retrieve-cli/Retrieval_Policy.md similarity index 100% rename from skills_developing/rag-retrieve/Retrieval_Policy.md rename to skills_developing/rag-retrieve-cli/Retrieval_Policy.md diff --git a/skills_developing/rag-retrieve/SKILL.md b/skills_developing/rag-retrieve-cli/SKILL.md similarity index 100% rename from skills_developing/rag-retrieve/SKILL.md rename to skills_developing/rag-retrieve-cli/SKILL.md diff --git a/skills_developing/rag-retrieve/scripts/rag_retrieve.py b/skills_developing/rag-retrieve-cli/scripts/rag_retrieve.py similarity index 100% rename from skills_developing/rag-retrieve/scripts/rag_retrieve.py rename to skills_developing/rag-retrieve-cli/scripts/rag_retrieve.py diff --git a/skills_developing/rag-retrieve/skill.yaml b/skills_developing/rag-retrieve-cli/skill.yaml similarity index 100% rename from skills_developing/rag-retrieve/skill.yaml rename to skills_developing/rag-retrieve-cli/skill.yaml diff --git a/utils/multi_project_manager.py b/utils/multi_project_manager.py index a5bca91..5ca31bd 100644 --- a/utils/multi_project_manager.py +++ b/utils/multi_project_manager.py @@ -7,6 +7,7 @@ import os import shutil import json import logging +import re from pathlib import Path from typing import List, Dict, Optional from datetime import datetime @@ -321,7 +322,7 @@ def create_robot_project(dataset_ids: List[str], bot_id: str, force_rebuild: boo """ skills = list(skills or []) - has_rag_retrieve = any(Path(skill.lstrip("@")).name == "rag-retrieve" for skill in skills) + has_rag_retrieve = any(re.search(r"rag-retrieve", skill) for skill in skills) if dataset_ids and not has_rag_retrieve: skills.append("@skills_autoload/rag-retrieve") logger.info("Auto loaded skill '@skills_autoload/rag-retrieve' because dataset_ids is not empty") From 4939a702092d8fb1a2cac9abef1ef69cf48115df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Thu, 16 Apr 2026 21:37:43 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E8=BF=99?= =?UTF-8?q?=E5=B1=82=E7=BA=A6=E6=9D=9F=EF=BC=9A=20=20=20-=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=96=87=E4=BB=B6=E6=A3=80=E7=B4=A2=E4=B8=8E=20rag=5F?= =?UTF-8?q?retrieve=20/=20table=5Frag=5Fretrieve=20=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E5=90=8C=E4=B8=80=E6=95=B0=E6=8D=AE=E6=BA=90=20=20=20-=20?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E5=9B=A0=E4=B8=BA=20RAG=20=E6=9F=A5=E5=88=B0?= =?UTF-8?q?=E4=BA=86=EF=BC=8C=E5=B0=B1=E5=81=87=E8=AE=BE=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B9=9F=E8=A2=AB=E8=A6=86=E7=9B=96=20=20=20?= =?UTF-8?q?-=20=E4=B9=9F=E4=B8=8D=E8=83=BD=E5=9B=A0=E4=B8=BA=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=96=87=E4=BB=B6=E6=B2=A1=E6=9F=A5=E5=88=B0=EF=BC=8C?= =?UTF-8?q?=E5=B0=B1=E6=8E=A8=E6=96=AD=20RAG=20=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E9=87=8C=E4=B9=9F=E6=B2=A1=E6=9C=89=20=20=20-=20=E8=BF=9B?= =?UTF-8?q?=E5=85=A5=20fallback=20=E6=97=B6=EF=BC=8C=E4=BB=8D=E9=9C=80?= =?UTF-8?q?=E6=8C=89=E9=A1=BA=E5=BA=8F=E7=BB=A7=E7=BB=AD=E5=B0=9D=E8=AF=95?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills_autoload/rag-retrieve/hooks/retrieval-policy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skills_autoload/rag-retrieve/hooks/retrieval-policy.md b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md index 6527185..fce1bfc 100644 --- a/skills_autoload/rag-retrieve/hooks/retrieval-policy.md +++ b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md @@ -3,6 +3,7 @@ ### 1. Retrieval Order and Tool Selection - Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. - Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. +- Important: local filesystem retrieval and `rag_retrieve` / `table_rag_retrieve` do NOT share the same underlying data source. They are not equivalent, and one source cannot be used to assume coverage in the other. - Do NOT answer from model knowledge first. - Do NOT skip directly to local filesystem retrieval when an earlier retrieval source may answer the question. - When a suitable skill-enabled knowledge retrieval tool is available, use it first. @@ -32,6 +33,9 @@ ### 5. Fallback and Sequential Retry - If the first retrieval result is insufficient, call the next retrieval source in the default order before replying. +- Important: local filesystem retrieval and `rag_retrieve` / `table_rag_retrieve` do NOT share the same underlying data source. They are not equivalent, and results from one cannot be used to assume coverage in the other. +- Even if `rag_retrieve` or `table_rag_retrieve` returns relevant results, local filesystem retrieval may still contain different or additional information; likewise, local files may contain information absent from the RAG knowledge base. +- Therefore, when fallback is required, do NOT skip local filesystem retrieval on the assumption that RAG already covers the same documents, and do NOT treat a local-file miss as evidence that the RAG knowledge base also lacks the information. - If the first RAG tool is insufficient, call the other RAG tool next before moving to local filesystem retrieval. - If `table_rag_retrieve` is insufficient or empty, continue with `rag_retrieve`. - If `rag_retrieve` is insufficient or empty, continue with `table_rag_retrieve`. From 9f12a633bc56d1f26d91975388eec7d64eb29e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Fri, 17 Apr 2026 11:05:16 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BC=98=E5=8C=96mcp=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .features/skill/MEMORY.md | 5 +-- README.md | 5 +-- agent/deep_assistant.py | 10 +---- agent/plugin_hook_loader.py | 45 ++++++++++++++++++- agent/prompt_loader.py | 30 +------------ mcp/mcp_settings.json | 5 --- .../.claude-plugin/plugin.json | 2 +- .../rag-retrieve/.claude-plugin/plugin.json | 2 +- 8 files changed, 51 insertions(+), 53 deletions(-) delete mode 100644 mcp/mcp_settings.json diff --git a/.features/skill/MEMORY.md b/.features/skill/MEMORY.md index 7d6fc1a..e8f40d5 100644 --- a/.features/skill/MEMORY.md +++ b/.features/skill/MEMORY.md @@ -88,9 +88,8 @@ skill-name/ ## Skill 加载优先级 -1. Skill MCP 配置(最高) -2. 默认 MCP 配置 (`mcp/mcp_settings.json`) -3. 用户传入参数(覆盖所有) +1. Skill MCP 配置 +2. 用户传入参数(覆盖已有同名配置) ## 安全措施 diff --git a/README.md b/README.md index 980f892..6c5e3c0 100644 --- a/README.md +++ b/README.md @@ -396,7 +396,6 @@ dataset_name/ │ ├── document.txt # 原始文本内容 │ ├── serialization.txt # 结构化数据 │ └── schema.json # 字段定义和元数据 -├── mcp_settings.json # MCP 工具配置 └── system_prompt.md # 系统提示词(可选) ``` @@ -405,7 +404,6 @@ dataset_name/ - **document.txt**: 原始 Markdown 文本,提供完整上下文 - **serialization.txt**: 格式化结构数据,每行 `字段1:值1;字段2:值2` - **schema.json**: 字段定义、枚举值映射和文件关联关系 -- **mcp_settings.json**: MCP 工具配置,定义可用的数据处理工具 --- @@ -565,8 +563,7 @@ qwen-agent/ │ ├── multi_keyword_search_server.py # 多关键词搜索服务 │ ├── excel_csv_operator_server.py # Excel/CSV 操作服务 │ ├── json_reader_server.py # JSON 读取服务 -│ ├── mcp_settings.json # MCP 配置文件 -│ └── tools/ # 工具定义文件 +│ └── tools/ # 工具定义文件 ├── models/ # 模型文件 ├── projects/ # 项目目录 │ └── queue_data/ # 队列数据 diff --git a/agent/deep_assistant.py b/agent/deep_assistant.py index 126a842..ce827de 100644 --- a/agent/deep_assistant.py +++ b/agent/deep_assistant.py @@ -117,13 +117,6 @@ def read_system_prompt(): return f.read().strip() -def read_mcp_settings(): - """读取MCP工具配置""" - with open("./mcp/mcp_settings.json", "r") as f: - mcp_settings_json = json.load(f) - return mcp_settings_json - - async def get_tools_from_mcp(mcp): """从MCP配置中提取工具(带缓存)""" start_time = time.time() @@ -195,8 +188,7 @@ async def init_agent(config: AgentConfig): final_system_prompt = await load_system_prompt_async(config) final_mcp_settings = await load_mcp_settings_async(config) - # 如果没有提供mcp,使用config中的mcp_settings - mcp_settings = final_mcp_settings if final_mcp_settings else read_mcp_settings() + mcp_settings = final_mcp_settings if final_mcp_settings else [] system_prompt = final_system_prompt if final_system_prompt else read_system_prompt() config.system_prompt = mcp_settings diff --git a/agent/plugin_hook_loader.py b/agent/plugin_hook_loader.py index 90b9059..5796e59 100644 --- a/agent/plugin_hook_loader.py +++ b/agent/plugin_hook_loader.py @@ -5,6 +5,7 @@ Claude Plugins 模式的 Hook 加载器 """ import os import json +import copy import logging import asyncio import subprocess @@ -116,7 +117,8 @@ async def merge_skill_mcp_configs(bot_id: str) -> List[Dict]: plugin_config = json.load(f) servers = plugin_config.get('mcpServers', {}) if servers: - merged_servers.update(servers) + normalized_servers = _normalize_skill_mcp_servers(servers, skill_path) + merged_servers.update(normalized_servers) logger.info(f"Loaded MCP config from skill: {skill_name}") except Exception as e: logger.error(f"Failed to load mcpServers from {skill_name}: {e}") @@ -127,6 +129,47 @@ async def merge_skill_mcp_configs(bot_id: str) -> List[Dict]: return [] +def _normalize_skill_mcp_servers(servers: Dict[str, Any], skill_path: str) -> Dict[str, Any]: + """将 skill plugin 中 stdio MCP server 的相对路径归一化为基于 skill 目录的绝对路径。""" + normalized_servers = copy.deepcopy(servers) + + for server_name, server_config in normalized_servers.items(): + if not isinstance(server_config, dict): + continue + + transport = server_config.get('transport') + if not transport: + transport = 'http' if 'url' in server_config else 'stdio' + if transport != 'stdio': + continue + + command = server_config.get('command') + if isinstance(command, str): + server_config['command'] = _resolve_skill_relative_path(command, skill_path) + + args = server_config.get('args') + if isinstance(args, list): + server_config['args'] = [ + _resolve_skill_relative_path(arg, skill_path) if isinstance(arg, str) else arg + for arg in args + ] + + return normalized_servers + + +def _resolve_skill_relative_path(value: str, skill_path: str) -> str: + """将 ./ 或 ../ 开头且不含占位符的路径转为基于 skill 目录的绝对路径。""" + if '{' in value or '}' in value: + return value + + if not value.startswith(('./', '../')): + return value + + normalized_path = os.path.abspath(os.path.join(skill_path, value)) + logger.debug(f"Resolved skill MCP path: {value} -> {normalized_path}") + return normalized_path + + def _load_plugin_config(plugin_json_path: str) -> Dict: """加载 plugin.json 配置""" try: diff --git a/agent/prompt_loader.py b/agent/prompt_loader.py index 1642d21..5e5407d 100644 --- a/agent/prompt_loader.py +++ b/agent/prompt_loader.py @@ -203,10 +203,9 @@ async def load_mcp_settings_async(config) -> List[Dict]: List[Dict]: 合并后的MCP设置列表 Note: - 支持在 mcp_settings.json 的 args 中使用 {dataset_dir} 占位符, + 支持在传入或合并后的 mcp_settings 的 args 中使用 {dataset_dir} 占位符, 会在 init_modified_agent_service_with_files 中被替换为实际的路径。 """ - from agent.config_cache import config_cache # 从config中获取参数 project_dir = getattr(config, 'project_dir', None) @@ -222,33 +221,6 @@ async def load_mcp_settings_async(config) -> List[Dict]: skill_mcp_servers = skill_mcp_settings[0].get('mcpServers', {}) logger.info(f"Loaded {len(skill_mcp_servers)} MCP servers from skills") # =========================================================================================== - - # 2. 读取默认MCP设置(使用缓存) - default_mcp_settings = [] - try: - default_mcp_file = os.path.join("mcp", f"mcp_settings.json") - default_mcp_settings = await config_cache.get_json_file(default_mcp_file) or [] - if default_mcp_settings: - logger.info(f"Using cached default mcp_settings from mcp folder") - except Exception as e: - logger.error(f"Failed to load default mcp_settings: {str(e)}") - default_mcp_settings = [] - - # 3. 合并默认设置到merged_settings(默认设置被skill覆盖) - if default_mcp_settings and len(default_mcp_settings) > 0: - default_mcp_servers = default_mcp_settings[0].get('mcpServers', {}) - if merged_settings and len(merged_settings) > 0: - # skill配置已存在,将默认配置合并进去(skill优先) - skill_mcp_servers = merged_settings[0].get('mcpServers', {}) - # 默认配置中不存在的才添加 - for server_name, server_config in default_mcp_servers.items(): - if server_name not in skill_mcp_servers: - skill_mcp_servers[server_name] = server_config - else: - # 没有skill配置,直接使用默认配置 - merged_settings = default_mcp_settings.copy() - - # 遍历mcpServers工具,给每个工具增加env参数 if merged_settings and len(merged_settings) > 0: mcp_servers = merged_settings[0].get('mcpServers', {}) for server_name, server_config in mcp_servers.items(): diff --git a/mcp/mcp_settings.json b/mcp/mcp_settings.json deleted file mode 100644 index 3aa61ae..0000000 --- a/mcp/mcp_settings.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - { - "mcpServers": {} - } -] diff --git a/skills/rag-retrieve-only/.claude-plugin/plugin.json b/skills/rag-retrieve-only/.claude-plugin/plugin.json index 704ff52..a1a38c5 100644 --- a/skills/rag-retrieve-only/.claude-plugin/plugin.json +++ b/skills/rag-retrieve-only/.claude-plugin/plugin.json @@ -14,7 +14,7 @@ "transport": "stdio", "command": "python", "args": [ - "./skills/rag-retrieve-only/rag_retrieve_server.py", + "./rag_retrieve_server.py", "{bot_id}" ] } diff --git a/skills_autoload/rag-retrieve/.claude-plugin/plugin.json b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json index 925751c..a929aa6 100644 --- a/skills_autoload/rag-retrieve/.claude-plugin/plugin.json +++ b/skills_autoload/rag-retrieve/.claude-plugin/plugin.json @@ -14,7 +14,7 @@ "transport": "stdio", "command": "python", "args": [ - "./skills_autoload/rag-retrieve/rag_retrieve_server.py", + "./rag_retrieve_server.py", "{bot_id}" ] } From 90229ffeaff7b85c90b7ba35a8ed475f45f8dd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Fri, 17 Apr 2026 11:43:20 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E4=BC=98=E5=8C=96skill=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/multi_project_manager.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/utils/multi_project_manager.py b/utils/multi_project_manager.py index 5ca31bd..59bcf98 100644 --- a/utils/multi_project_manager.py +++ b/utils/multi_project_manager.py @@ -401,23 +401,30 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) skills_target_dir.mkdir(parents=True, exist_ok=True) logger.info(f"Copying skills to {skills_target_dir}") + managed_skill_names = set() + for base_dir in skills_source_dirs: + if not base_dir.exists(): + continue + for item in base_dir.iterdir(): + if item.is_dir(): + managed_skill_names.add(item.name) + # 清理不在列表中的多余 skill 文件夹 expected_skill_names = {Path(skill.lstrip("@")).name for skill in skills} if skills_target_dir.exists(): for item in skills_target_dir.iterdir(): - if item.is_dir() and item.name not in expected_skill_names: - logger.info(f" Removing stale skill directory: {item}") + if not item.is_dir() or item.name in expected_skill_names: + continue + if item.name in managed_skill_names: + logger.info(f" Removing managed stale skill directory: {item}") shutil.rmtree(item) + else: + logger.info(f" Keeping unmanaged skill directory: {item}") for skill in skills: skill_name = Path(skill.lstrip("@")).name target_dir = skills_target_dir / skill_name - # 如果目标目录已存在,跳过复制 - if target_dir.exists(): - logger.info(f" Skill '{skill}' already exists in {target_dir}, skipping") - continue - source_dir = None if skill.startswith("@"): @@ -440,7 +447,7 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) continue try: - shutil.copytree(source_dir, target_dir) - logger.info(f" Copied: {source_dir} -> {target_dir}") + shutil.copytree(source_dir, target_dir, dirs_exist_ok=True) + logger.info(f" Synced: {source_dir} -> {target_dir}") except Exception as e: logger.error(f" Failed to copy {source_dir}: {e}") From ebec9e2cba928d692983556a56ec079d7ac1d7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Fri, 17 Apr 2026 15:54:51 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E7=A6=81=E6=AD=A2=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rag-retrieve/hooks/retrieval-policy.md | 22 +++++++++---------- .../rag-retrieve/rag_retrieve_server.py | 11 ++++++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/skills_autoload/rag-retrieve/hooks/retrieval-policy.md b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md index fce1bfc..3d92ef5 100644 --- a/skills_autoload/rag-retrieve/hooks/retrieval-policy.md +++ b/skills_autoload/rag-retrieve/hooks/retrieval-policy.md @@ -2,10 +2,12 @@ ### 1. Retrieval Order and Tool Selection - Follow this section for source choice, tool choice, query rewrite, `top_k`, fallback, result handling, and citations. -- Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve` > local filesystem retrieval. -- Important: local filesystem retrieval and `rag_retrieve` / `table_rag_retrieve` do NOT share the same underlying data source. They are not equivalent, and one source cannot be used to assume coverage in the other. +- Use this default retrieval order and execute it sequentially: skill-enabled knowledge retrieval tools > `rag_retrieve` / `table_rag_retrieve`. - Do NOT answer from model knowledge first. -- Do NOT skip directly to local filesystem retrieval when an earlier retrieval source may answer the question. +- Do NOT bypass the retrieval flow and inspect local filesystem documents on your own. +- Do NOT use local filesystem retrieval as a fallback knowledge source. +- Local filesystem documents are not a recommended retrieval source here because file formats are inconsistent and have not been normalized or parsed for reliable knowledge lookup. +- Knowledge must be retrieved through the supported knowledge tools only: skill-enabled retrieval scripts, `table_rag_retrieve`, and `rag_retrieve`. - When a suitable skill-enabled knowledge retrieval tool is available, use it first. - If no suitable skill-enabled retrieval tool is available, or if its result is insufficient, continue with `rag_retrieve` or `table_rag_retrieve`. - Use `table_rag_retrieve` first for values, prices, quantities, inventory, specifications, rankings, comparisons, summaries, extraction, lists, tables, name lookup, historical coverage, mixed questions, and unclear cases. @@ -32,16 +34,12 @@ - Also treat results as insufficient when they cover only part of the request, or when full-list, historical, comparison, or mixed data + explanation requests return only partial or truncated coverage. ### 5. Fallback and Sequential Retry -- If the first retrieval result is insufficient, call the next retrieval source in the default order before replying. -- Important: local filesystem retrieval and `rag_retrieve` / `table_rag_retrieve` do NOT share the same underlying data source. They are not equivalent, and results from one cannot be used to assume coverage in the other. -- Even if `rag_retrieve` or `table_rag_retrieve` returns relevant results, local filesystem retrieval may still contain different or additional information; likewise, local files may contain information absent from the RAG knowledge base. -- Therefore, when fallback is required, do NOT skip local filesystem retrieval on the assumption that RAG already covers the same documents, and do NOT treat a local-file miss as evidence that the RAG knowledge base also lacks the information. -- If the first RAG tool is insufficient, call the other RAG tool next before moving to local filesystem retrieval. -- If `table_rag_retrieve` is insufficient or empty, continue with `rag_retrieve`. +- If the first retrieval result is insufficient, call the next supported retrieval source in the default order before replying. +- `table_rag_retrieve` now performs an internal fallback to `rag_retrieve` when it returns `no excel files found`, but this does NOT change the higher-level retrieval order. +- If `table_rag_retrieve` is still insufficient after its internal fallback, continue with the next supported retrieval source instead of checking local filesystem documents directly. - If `rag_retrieve` is insufficient or empty, continue with `table_rag_retrieve`. -- If both `rag_retrieve` and `table_rag_retrieve` are insufficient, continue with local filesystem retrieval. -- Say no relevant information was found only after all applicable skill-enabled retrieval tools, both `rag_retrieve` and `table_rag_retrieve`, and local filesystem retrieval have been tried and still do not provide enough evidence. -- Do NOT reply that no relevant information was found before the final local filesystem fallback has also been tried. +- Say no relevant information was found only after all applicable skill-enabled retrieval tools, `rag_retrieve`, and `table_rag_retrieve` have been tried and still do not provide enough evidence. +- Do NOT reply that no relevant information was found before the supported knowledge retrieval flow has been exhausted. ### 6. Table RAG Result Handling - Follow all `[INSTRUCTION]` and `[EXTRA_INSTRUCTION]` content in `table_rag_retrieve` results. diff --git a/skills_autoload/rag-retrieve/rag_retrieve_server.py b/skills_autoload/rag-retrieve/rag_retrieve_server.py index 6caa9c8..1ef7a2d 100644 --- a/skills_autoload/rag-retrieve/rag_retrieve_server.py +++ b/skills_autoload/rag-retrieve/rag_retrieve_server.py @@ -219,14 +219,17 @@ def table_rag_retrieve(query: str) -> Dict[str, Any]: if "markdown" in response_data: markdown_content = response_data["markdown"] - text = markdown_content - if not re.search(r"^no excel files found", markdown_content, re.IGNORECASE): - text = TABLE_CITATION_INSTRUCTIONS + markdown_content + if re.search(r"^no excel files found", markdown_content, re.IGNORECASE): + rag_result = rag_retrieve(query) + content = rag_result.get("content", []) + if content and content[0].get("type") == "text": + content[0]["text"] = "No table_rag_retrieve results were found. The content below is the fallback result from rag_retrieve:\n\n" + content[0]["text"] + return rag_result return { "content": [ { "type": "text", - "text": text + "text": TABLE_CITATION_INSTRUCTIONS + markdown_content } ] }