Use session document filter in agent, combine with document name filter if necessary
This commit is contained in:
parent
5b14038dc5
commit
30e9ff3038
4 changed files with 1601 additions and 4 deletions
|
|
@ -15,6 +15,8 @@ from haiku.rag.agents.chat.state import (
|
||||||
ChatSessionState,
|
ChatSessionState,
|
||||||
QAResponse,
|
QAResponse,
|
||||||
build_document_filter,
|
build_document_filter,
|
||||||
|
build_multi_document_filter,
|
||||||
|
combine_filters,
|
||||||
)
|
)
|
||||||
from haiku.rag.agents.research.dependencies import ResearchContext
|
from haiku.rag.agents.research.dependencies import ResearchContext
|
||||||
from haiku.rag.agents.research.graph import build_conversational_graph
|
from haiku.rag.agents.research.graph import build_conversational_graph
|
||||||
|
|
@ -72,8 +74,18 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
||||||
document_name: Optional document name/title to search within
|
document_name: Optional document name/title to search within
|
||||||
limit: Number of results to return (default: 5)
|
limit: Number of results to return (default: 5)
|
||||||
"""
|
"""
|
||||||
# Build filter from document_name
|
# Build session filter from document_filter
|
||||||
doc_filter = build_document_filter(document_name) if document_name else None
|
session_filter = None
|
||||||
|
if ctx.deps.session_state and ctx.deps.session_state.document_filter:
|
||||||
|
session_filter = build_multi_document_filter(
|
||||||
|
ctx.deps.session_state.document_filter
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build tool filter from document_name parameter
|
||||||
|
tool_filter = build_document_filter(document_name) if document_name else None
|
||||||
|
|
||||||
|
# Combine filters: session AND tool
|
||||||
|
doc_filter = combine_filters(session_filter, tool_filter)
|
||||||
|
|
||||||
# Use search agent for query expansion and deduplication
|
# Use search agent for query expansion and deduplication
|
||||||
search_agent = SearchAgent(ctx.deps.client, ctx.deps.config)
|
search_agent = SearchAgent(ctx.deps.client, ctx.deps.config)
|
||||||
|
|
@ -111,6 +123,9 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
||||||
session_context=get_cached_session_context(session_id)
|
session_context=get_cached_session_context(session_id)
|
||||||
if session_id
|
if session_id
|
||||||
else None,
|
else None,
|
||||||
|
document_filter=(
|
||||||
|
ctx.deps.session_state.document_filter if ctx.deps.session_state else []
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return detailed results for the agent to present
|
# Return detailed results for the agent to present
|
||||||
|
|
@ -158,8 +173,18 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
||||||
question: The question to answer
|
question: The question to answer
|
||||||
document_name: Optional document name/title to search within (e.g., "tbmed593", "army manual")
|
document_name: Optional document name/title to search within (e.g., "tbmed593", "army manual")
|
||||||
"""
|
"""
|
||||||
# Build filter from document_name
|
# Build session filter from document_filter
|
||||||
doc_filter = build_document_filter(document_name) if document_name else None
|
session_filter = None
|
||||||
|
if ctx.deps.session_state and ctx.deps.session_state.document_filter:
|
||||||
|
session_filter = build_multi_document_filter(
|
||||||
|
ctx.deps.session_state.document_filter
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build tool filter from document_name parameter
|
||||||
|
tool_filter = build_document_filter(document_name) if document_name else None
|
||||||
|
|
||||||
|
# Combine filters: session AND tool
|
||||||
|
doc_filter = combine_filters(session_filter, tool_filter)
|
||||||
|
|
||||||
# Build and run the conversational research graph
|
# Build and run the conversational research graph
|
||||||
graph = build_conversational_graph(config=ctx.deps.config)
|
graph = build_conversational_graph(config=ctx.deps.config)
|
||||||
|
|
@ -245,6 +270,9 @@ def create_chat_agent(config: AppConfig) -> Agent[ChatDeps, str]:
|
||||||
session_context=get_cached_session_context(session_id)
|
session_context=get_cached_session_context(session_id)
|
||||||
if session_id
|
if session_id
|
||||||
else None,
|
else None,
|
||||||
|
document_filter=(
|
||||||
|
ctx.deps.session_state.document_filter if ctx.deps.session_state else []
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Format answer with citation references and confidence
|
# Format answer with citation references and confidence
|
||||||
|
|
|
||||||
|
|
@ -583,3 +583,97 @@ def test_fifo_limit_enforcement():
|
||||||
assert session_state.qa_history[0].question == "Question 1"
|
assert session_state.qa_history[0].question == "Question 1"
|
||||||
# The last entry should be the last added question
|
# The last entry should be the last added question
|
||||||
assert session_state.qa_history[-1].question == f"Question {MAX_QA_HISTORY}"
|
assert session_state.qa_history[-1].question == f"Question {MAX_QA_HISTORY}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_session_state_document_filter():
|
||||||
|
"""Test ChatSessionState with document_filter."""
|
||||||
|
state = ChatSessionState(
|
||||||
|
session_id="test-filter",
|
||||||
|
document_filter=["doc1.pdf", "doc2.pdf"],
|
||||||
|
)
|
||||||
|
assert state.document_filter == ["doc1.pdf", "doc2.pdf"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_session_state_document_filter_default_empty():
|
||||||
|
"""Test ChatSessionState document_filter defaults to empty list."""
|
||||||
|
state = ChatSessionState(session_id="test")
|
||||||
|
assert state.document_filter == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.vcr()
|
||||||
|
async def test_chat_agent_search_with_session_filter(
|
||||||
|
allow_model_requests, temp_db_path
|
||||||
|
):
|
||||||
|
"""Test that session document_filter restricts search results."""
|
||||||
|
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||||
|
# Add two distinct documents
|
||||||
|
await client.create_document(
|
||||||
|
content=DOCLAYNET_CLASS_LABELS,
|
||||||
|
uri="doclaynet-labels",
|
||||||
|
title="DocLayNet Class Labels",
|
||||||
|
)
|
||||||
|
await client.create_document(
|
||||||
|
content=DOCLAYNET_DATA_SOURCES,
|
||||||
|
uri="doclaynet-sources",
|
||||||
|
title="DocLayNet Sources",
|
||||||
|
)
|
||||||
|
|
||||||
|
agent = create_chat_agent(Config)
|
||||||
|
# Set session filter to only include the labels document
|
||||||
|
session_state = ChatSessionState(
|
||||||
|
session_id="test-session-filter",
|
||||||
|
document_filter=["DocLayNet Class Labels"],
|
||||||
|
)
|
||||||
|
deps = ChatDeps(
|
||||||
|
client=client,
|
||||||
|
config=Config,
|
||||||
|
session_state=session_state,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Search should only return results from the filtered document
|
||||||
|
result = await agent.run(
|
||||||
|
"Search for information about DocLayNet",
|
||||||
|
deps=deps,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.output is not None
|
||||||
|
# Results should only reference the Labels document, not Sources
|
||||||
|
assert "Labels" in result.output or "class" in result.output.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.vcr()
|
||||||
|
async def test_search_agent_with_session_filter(allow_model_requests, temp_db_path):
|
||||||
|
"""Test SearchAgent respects session document filter."""
|
||||||
|
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||||
|
# Add two distinct documents
|
||||||
|
await client.create_document(
|
||||||
|
content=DOCLAYNET_CLASS_LABELS,
|
||||||
|
uri="doclaynet-labels",
|
||||||
|
title="DocLayNet Class Labels",
|
||||||
|
)
|
||||||
|
await client.create_document(
|
||||||
|
content=DOCLAYNET_DATA_SOURCES,
|
||||||
|
uri="doclaynet-sources",
|
||||||
|
title="DocLayNet Sources",
|
||||||
|
)
|
||||||
|
|
||||||
|
from haiku.rag.agents.chat.state import build_multi_document_filter
|
||||||
|
|
||||||
|
search_agent = SearchAgent(client, Config)
|
||||||
|
|
||||||
|
# Build filter for only the labels document
|
||||||
|
doc_filter = build_multi_document_filter(["DocLayNet Class Labels"])
|
||||||
|
|
||||||
|
results = await search_agent.search(
|
||||||
|
query="What information is available?",
|
||||||
|
filter=doc_filter,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(results, list)
|
||||||
|
# All results should be from the labels document
|
||||||
|
for r in results:
|
||||||
|
assert "labels" in (r.document_uri or "").lower() or "Labels" in (
|
||||||
|
r.document_title or ""
|
||||||
|
)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue