159 lines
5.3 KiB
Python
159 lines
5.3 KiB
Python
from pydantic_ai import FunctionToolset
|
|
|
|
from haiku.rag.agents.chat.agent import (
|
|
DEFAULT_FEATURES,
|
|
FEATURE_ANALYSIS,
|
|
FEATURE_DOCUMENTS,
|
|
FEATURE_QA,
|
|
FEATURE_SEARCH,
|
|
ChatDeps,
|
|
create_chat_agent,
|
|
)
|
|
from haiku.rag.agents.chat.prompts import build_chat_prompt
|
|
from haiku.rag.client import HaikuRAG
|
|
from haiku.rag.config import Config
|
|
from haiku.rag.tools.context import ToolContext
|
|
from haiku.rag.tools.qa import QA_SESSION_NAMESPACE, QASessionState
|
|
from haiku.rag.tools.session import SESSION_NAMESPACE, SessionState
|
|
|
|
|
|
def _count_function_toolsets(agent) -> int:
|
|
"""Count FunctionToolset instances in an agent (excludes internal toolsets)."""
|
|
return sum(1 for t in agent.toolsets if type(t) is FunctionToolset)
|
|
|
|
|
|
# =============================================================================
|
|
# Feature Selection Tests
|
|
# =============================================================================
|
|
|
|
|
|
def test_default_features(temp_db_path):
|
|
"""Default features create search + document + qa toolsets and register both states."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
agent = create_chat_agent(Config, client, context)
|
|
|
|
# Should have 3 toolsets (search, document, qa)
|
|
assert _count_function_toolsets(agent) == 3
|
|
|
|
# Both SessionState and QASessionState should be registered
|
|
assert context.get(SESSION_NAMESPACE, SessionState) is not None
|
|
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is not None
|
|
client.close()
|
|
|
|
|
|
def test_search_only(temp_db_path):
|
|
"""features=["search"] creates only search toolset, no QASessionState."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
agent = create_chat_agent(Config, client, context, features=[FEATURE_SEARCH])
|
|
|
|
assert _count_function_toolsets(agent) == 1
|
|
|
|
# SessionState always registered, but QASessionState should NOT be
|
|
assert context.get(SESSION_NAMESPACE, SessionState) is not None
|
|
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
|
|
client.close()
|
|
|
|
|
|
def test_search_and_documents(temp_db_path):
|
|
"""features=["search", "documents"] creates both toolsets, no QASessionState."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
agent = create_chat_agent(
|
|
Config, client, context, features=[FEATURE_SEARCH, FEATURE_DOCUMENTS]
|
|
)
|
|
|
|
assert _count_function_toolsets(agent) == 2
|
|
|
|
assert context.get(SESSION_NAMESPACE, SessionState) is not None
|
|
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
|
|
client.close()
|
|
|
|
|
|
def test_all_features(temp_db_path):
|
|
"""All four features create four toolsets."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
agent = create_chat_agent(
|
|
Config,
|
|
client,
|
|
context,
|
|
features=[FEATURE_SEARCH, FEATURE_DOCUMENTS, FEATURE_QA, FEATURE_ANALYSIS],
|
|
)
|
|
|
|
assert _count_function_toolsets(agent) == 4
|
|
|
|
assert context.get(SESSION_NAMESPACE, SessionState) is not None
|
|
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is not None
|
|
client.close()
|
|
|
|
|
|
def test_no_qa_skips_qa_session_state(temp_db_path):
|
|
"""Without QA feature, QASessionState is not registered."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
create_chat_agent(
|
|
Config, client, context, features=[FEATURE_SEARCH, FEATURE_DOCUMENTS]
|
|
)
|
|
|
|
assert context.get(QA_SESSION_NAMESPACE, QASessionState) is None
|
|
client.close()
|
|
|
|
|
|
def test_chat_deps_state_without_qa(temp_db_path):
|
|
"""ChatDeps.state getter omits qa_history/session_context when QASessionState absent."""
|
|
client = HaikuRAG(temp_db_path, create=True)
|
|
context = ToolContext()
|
|
create_chat_agent(Config, client, context, features=[FEATURE_SEARCH])
|
|
|
|
deps = ChatDeps(config=Config, tool_context=context)
|
|
state = deps.state
|
|
|
|
# SessionState fields should be present
|
|
assert "document_filter" in state
|
|
assert "citation_registry" in state
|
|
assert "citations" in state
|
|
# QA fields should NOT be present
|
|
assert "qa_history" not in state
|
|
assert "session_context" not in state
|
|
client.close()
|
|
|
|
|
|
# =============================================================================
|
|
# Prompt Composition Tests
|
|
# =============================================================================
|
|
|
|
|
|
def test_build_chat_prompt_default():
|
|
"""Default features produce prompt mentioning all standard tools."""
|
|
prompt = build_chat_prompt(DEFAULT_FEATURES)
|
|
|
|
assert "list_documents" in prompt
|
|
assert "get_document" in prompt
|
|
assert "summarize_document" in prompt
|
|
assert "ask" in prompt
|
|
assert "search" in prompt
|
|
assert "analyze" not in prompt
|
|
|
|
|
|
def test_build_chat_prompt_search_only():
|
|
"""Search-only prompt doesn't mention ask or document tools."""
|
|
prompt = build_chat_prompt([FEATURE_SEARCH])
|
|
|
|
assert "search" in prompt
|
|
assert '"ask"' not in prompt
|
|
assert '"list_documents"' not in prompt
|
|
assert '"get_document"' not in prompt
|
|
assert '"summarize_document"' not in prompt
|
|
|
|
|
|
def test_build_chat_prompt_includes_analysis():
|
|
"""Analysis feature adds analyze guidance to prompt."""
|
|
prompt = build_chat_prompt(
|
|
[FEATURE_SEARCH, FEATURE_QA, FEATURE_DOCUMENTS, FEATURE_ANALYSIS]
|
|
)
|
|
|
|
assert "analyze" in prompt
|
|
assert "search" in prompt
|
|
assert "ask" in prompt
|