diff --git a/docs/tools.md b/docs/tools.md index 257577e9..4b384525 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -192,9 +192,42 @@ analysis = create_analysis_toolset(config) Executes a computational task via code execution and returns an `AnalysisResult`. Requires Docker — see [RLM Agent](agents/rlm.md) for setup. +## Tool Prompts + +`build_tools_prompt()` generates system prompt guidance for your toolsets — when to use each tool, the `document_name` parameter pattern, and usage examples. It's designed to be spliced into any agent's instructions alongside your own domain-specific guidance. + +```python +from haiku.rag.tools import build_tools_prompt + +# Generate guidance for the toolsets you're using +tools_prompt = build_tools_prompt(["search", "qa", "documents"]) +``` + +Combine it with your own instructions: + +```python +from pydantic_ai import Agent +from haiku.rag.tools import AgentDeps, build_tools_prompt + +tools_prompt = build_tools_prompt(["search", "qa"]) + +agent = Agent( + "anthropic:claude-sonnet-4-5-20250929", + deps_type=AgentDeps, + instructions=f"""You are a medical research assistant. +{tools_prompt} + +You also have access to: +- "check_interactions" - Use when the user asks about drug interactions.""", + toolsets=[search_toolset, qa_toolset, my_custom_toolset], +) +``` + +Available features: `"search"`, `"qa"`, `"documents"`, `"analysis"`. + ## Composing Custom Agents -Toolsets are designed to be composed into custom pydantic-ai agents. Use `AgentDeps` and `prepare_context` for minimal boilerplate: +Toolsets are designed to be composed into custom pydantic-ai agents. Use `AgentDeps`, `prepare_context`, and `build_tools_prompt` for minimal boilerplate: ```python from pydantic_ai import Agent @@ -202,6 +235,7 @@ from haiku.rag.client import HaikuRAG from haiku.rag.tools import ( AgentDeps, ToolContext, + build_tools_prompt, prepare_context, create_search_toolset, create_qa_toolset, @@ -213,16 +247,19 @@ search = create_search_toolset(config) qa = create_qa_toolset(config) docs = create_document_toolset(config) +features = ["search", "documents", "qa"] +tools_prompt = build_tools_prompt(features) + agent = Agent( "openai:gpt-4o", deps_type=AgentDeps, - instructions="You are a helpful research assistant.", + instructions=f"You are a helpful research assistant.\n{tools_prompt}", toolsets=[search, qa, docs], ) async with HaikuRAG("path/to/db.lancedb") as client: context = ToolContext() - prepare_context(context, features=["search", "documents", "qa"]) + prepare_context(context, features=features) deps = AgentDeps(client=client, tool_context=context) result = await agent.run("What documents do we have about climate?", deps=deps) diff --git a/examples/custom_agent.py b/examples/custom_agent.py index 91b4c51d..6b5a838c 100644 --- a/examples/custom_agent.py +++ b/examples/custom_agent.py @@ -21,6 +21,7 @@ from haiku.rag.client import HaikuRAG from haiku.rag.tools import ( AgentDeps, ToolContext, + build_tools_prompt, create_document_toolset, create_qa_toolset, create_search_toolset, @@ -36,13 +37,16 @@ async def main(db_path: str) -> None: qa_toolset = create_qa_toolset(config) document_toolset = create_document_toolset(config) + features = ["search", "documents", "qa"] + tools_prompt = build_tools_prompt(features) + agent = Agent( "anthropic:claude-haiku-4-5-20251001", deps_type=AgentDeps, output_type=str, instructions=( - "You are a helpful assistant with access to a knowledge base. " - "Use the available tools to answer questions." + "You are a helpful assistant with access to a knowledge base.\n" + f"{tools_prompt}" ), toolsets=[search_toolset, qa_toolset, document_toolset], ) diff --git a/examples/custom_agent_agui.py b/examples/custom_agent_agui.py index 4e4a928c..ce812d59 100644 --- a/examples/custom_agent_agui.py +++ b/examples/custom_agent_agui.py @@ -27,6 +27,7 @@ from haiku.rag.config.models import AppConfig from haiku.rag.tools import ( AgentDeps, ToolContextCache, + build_tools_prompt, create_qa_toolset, create_search_toolset, prepare_context, @@ -57,14 +58,16 @@ def get_client() -> HaikuRAG: return _client +features = ["search", "qa"] +tools_prompt = build_tools_prompt(features) + # Create the agent once at module level agent = Agent( "anthropic:claude-haiku-4-5-20251001", deps_type=AgentDeps, output_type=str, instructions=( - "You are a helpful assistant with access to a knowledge base. " - "Use the search and ask tools to answer questions." + f"You are a helpful assistant with access to a knowledge base.\n{tools_prompt}" ), toolsets=[ create_search_toolset(config), diff --git a/haiku_rag_slim/haiku/rag/agents/chat/prompts.py b/haiku_rag_slim/haiku/rag/agents/chat/prompts.py index a171344f..a39349b3 100644 --- a/haiku_rag_slim/haiku/rag/agents/chat/prompts.py +++ b/haiku_rag_slim/haiku/rag/agents/chat/prompts.py @@ -1,3 +1,5 @@ +from haiku.rag.tools.prompts import build_tools_prompt + _PROMPT_BASE = """You are a helpful research assistant powered by haiku.rag, a knowledge base system. You have access to a knowledge base of documents. Use your tools to search and answer questions. @@ -13,40 +15,8 @@ _PROMPT_QA_RULES = """ _PROMPT_SEARCH_RULES = """ 5. For searches: Use the "search" tool EXACTLY ONCE - it handles multi-query expansion internally""" -_PROMPT_TOOL_HEADER = """ - -How to decide which tool to use:""" - -_PROMPT_DOCUMENTS = """ -- "list_documents" - Use when the user wants to browse or see what documents are available (e.g., "what documents are available?", "show me the documents", "list available docs"). -- "summarize_document" - Use when the user wants an overview or summary of a specific document (e.g., "summarize document X", "what does Y cover?", "give me an overview of Z"). -- "get_document" - Use when the user wants the FULL content of a specific document (e.g., "get the paper about Y", "fetch 2412.00566", "show me the full document").""" - -_PROMPT_QA = """ -- "ask" - Use for questions about topics in the knowledge base. It automatically finds relevant prior answers from conversation history and searches across documents to return answers with citations.""" - -_PROMPT_SEARCH = """ -- "search" - Use when the user explicitly asks to search/find/explore documents. Call it ONCE. After calling search, copy the ENTIRE tool response to your output INCLUDING the content snippets. Do NOT shorten, summarize, or omit any part of the results.""" - -_PROMPT_ANALYSIS = """ -- "analyze" - Use when the user asks for computation, data analysis, or quantitative tasks that require code execution (e.g., "calculate the average", "compare the numbers", "plot the data"). It runs Python code in a sandbox to produce results.""" - -_PROMPT_DOCUMENT_NAME_HEADER = """ - -IMPORTANT - When user mentions a document in search/ask: -- If user says "search in ", "find in ", "answer from ", or " in ": - - Extract the TOPIC as `query`/`question` - - Extract the DOCUMENT NAME as `document_name`""" - -_PROMPT_SEARCH_EXAMPLES = """ -- Examples for search: - - "search for embeddings in the ML paper" → query="embeddings", document_name="ML paper" - - "find transformer architecture in 2412.00566" → query="transformer architecture", document_name="2412.00566" """ - -_PROMPT_QA_EXAMPLES = """ -- Examples for ask: - - "what does the ML paper say about embeddings?" → question="what are the embedding methods?", document_name="ML paper" - - "answer from 2412.00566 about model training" → question="how is the model trained?", document_name="2412.00566" """ +_PROMPT_SEARCH_OUTPUT = """ +After calling search, copy the ENTIRE tool response to your output INCLUDING the content snippets. Do NOT shorten, summarize, or omit any part of the results.""" _PROMPT_CLOSING = """ Be friendly and conversational.""" @@ -76,28 +46,14 @@ def build_chat_prompt(features: list[str]) -> str: if "search" in features: parts.append(_PROMPT_SEARCH_RULES) - # Tool guidance header + per-feature sections - tool_sections = [] - if "documents" in features: - tool_sections.append(_PROMPT_DOCUMENTS) - if "qa" in features: - tool_sections.append(_PROMPT_QA) + # Tool guidance (reusable across agents) + tools_prompt = build_tools_prompt(features) + if tools_prompt: + parts.append(tools_prompt) + + # Chat-specific search output rule if "search" in features: - tool_sections.append(_PROMPT_SEARCH) - if "analysis" in features: - tool_sections.append(_PROMPT_ANALYSIS) - - if tool_sections: - parts.append(_PROMPT_TOOL_HEADER) - parts.extend(tool_sections) - - # Document name examples (relevant when search or qa is active) - if "search" in features or "qa" in features: - parts.append(_PROMPT_DOCUMENT_NAME_HEADER) - if "search" in features: - parts.append(_PROMPT_SEARCH_EXAMPLES) - if "qa" in features: - parts.append(_PROMPT_QA_EXAMPLES) + parts.append(_PROMPT_SEARCH_OUTPUT) parts.append(_PROMPT_CLOSING) if "qa" in features: diff --git a/haiku_rag_slim/haiku/rag/tools/__init__.py b/haiku_rag_slim/haiku/rag/tools/__init__.py index b925b408..0964ece3 100644 --- a/haiku_rag_slim/haiku/rag/tools/__init__.py +++ b/haiku_rag_slim/haiku/rag/tools/__init__.py @@ -19,6 +19,7 @@ from haiku.rag.tools.filters import ( get_session_filter, ) from haiku.rag.tools.models import AnalysisResult, QAResult +from haiku.rag.tools.prompts import build_tools_prompt from haiku.rag.tools.qa import ( QA_SESSION_NAMESPACE, QAHistoryEntry, @@ -37,6 +38,7 @@ from haiku.rag.tools.session import ( __all__ = [ "AgentDeps", + "build_tools_prompt", "RAGDeps", "ToolContext", "ToolContextCache", diff --git a/haiku_rag_slim/haiku/rag/tools/prompts.py b/haiku_rag_slim/haiku/rag/tools/prompts.py new file mode 100644 index 00000000..74b8fd56 --- /dev/null +++ b/haiku_rag_slim/haiku/rag/tools/prompts.py @@ -0,0 +1,71 @@ +_TOOL_HEADER = """ + +How to decide which tool to use:""" + +_TOOL_DOCUMENTS = """ +- "list_documents" - Use when the user wants to browse or see what documents are available (e.g., "what documents are available?", "show me the documents", "list available docs"). +- "summarize_document" - Use when the user wants an overview or summary of a specific document (e.g., "summarize document X", "what does Y cover?", "give me an overview of Z"). +- "get_document" - Use when the user wants the FULL content of a specific document (e.g., "get the paper about Y", "fetch 2412.00566", "show me the full document").""" + +_TOOL_QA = """ +- "ask" - Use for questions about topics in the knowledge base. Searches across documents and returns answers with citations. Prior answers are recalled to avoid redundant work.""" + +_TOOL_SEARCH = """ +- "search" - Use when the user explicitly asks to search, find, or explore documents. Handles multi-query expansion internally and returns matching passages with surrounding context.""" + +_TOOL_ANALYSIS = """ +- "analyze" - Use when the user asks for computation, data analysis, or quantitative tasks that require code execution (e.g., "calculate the average", "compare the numbers", "plot the data"). Runs Python code in a sandbox to produce results.""" + +_DOCUMENT_NAME_HEADER = """ + +IMPORTANT - When user mentions a document in search/ask: +- If user says "search in ", "find in ", "answer from ", or " in ": + - Extract the TOPIC as `query`/`question` + - Extract the DOCUMENT NAME as `document_name`""" + +_DOCUMENT_NAME_SEARCH_EXAMPLES = """ +- Examples for search: + - "search for embeddings in the ML paper" → query="embeddings", document_name="ML paper" + - "find transformer architecture in 2412.00566" → query="transformer architecture", document_name="2412.00566" """ + +_DOCUMENT_NAME_QA_EXAMPLES = """ +- Examples for ask: + - "what does the ML paper say about embeddings?" → question="what are the embedding methods?", document_name="ML paper" + - "answer from 2412.00566 about model training" → question="how is the model trained?", document_name="2412.00566" """ + +_FEATURE_TOOLS: dict[str, str] = { + "documents": _TOOL_DOCUMENTS, + "qa": _TOOL_QA, + "search": _TOOL_SEARCH, + "analysis": _TOOL_ANALYSIS, +} + + +def build_tools_prompt(features: list[str]) -> str: + """Build tool guidance for the given features. + + Returns prompt text describing when and how to use each tool. + Designed to be spliced into a custom agent's system prompt. + + Args: + features: List of feature names (e.g., ["search", "documents", "qa"]). + + Returns: + Tool guidance prompt text. + """ + parts: list[str] = [] + + tool_sections = [_FEATURE_TOOLS[f] for f in features if f in _FEATURE_TOOLS] + + if tool_sections: + parts.append(_TOOL_HEADER) + parts.extend(tool_sections) + + if "search" in features or "qa" in features: + parts.append(_DOCUMENT_NAME_HEADER) + if "search" in features: + parts.append(_DOCUMENT_NAME_SEARCH_EXAMPLES) + if "qa" in features: + parts.append(_DOCUMENT_NAME_QA_EXAMPLES) + + return "".join(parts)