Skip to content

Commit 175ed58

Browse files
authored
Merge pull request #2327 from arc53/research-agent
Research agent
2 parents ed34c2b + 820ee3a commit 175ed58

50 files changed

Lines changed: 4465 additions & 1268 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
__pycache__/
33
*.py[cod]
44
*$py.class
5+
results.txt
56
experiments/
67

78
experiments

application/agents/agent_creator.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
22

3+
from application.agents.agentic_agent import AgenticAgent
34
from application.agents.classic_agent import ClassicAgent
4-
from application.agents.react_agent import ReActAgent
5+
from application.agents.research_agent import ResearchAgent
56
from application.agents.workflow_agent import WorkflowAgent
67

78
logger = logging.getLogger(__name__)
@@ -10,7 +11,9 @@
1011
class AgentCreator:
1112
agents = {
1213
"classic": ClassicAgent,
13-
"react": ReActAgent,
14+
"react": ClassicAgent, # backwards compat: react falls back to classic
15+
"agentic": AgenticAgent,
16+
"research": ResearchAgent,
1417
"workflow": WorkflowAgent,
1518
}
1619

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import logging
2+
from typing import Dict, Generator, Optional
3+
4+
from application.agents.base import BaseAgent
5+
from application.agents.tools.internal_search import (
6+
INTERNAL_TOOL_ID,
7+
add_internal_search_tool,
8+
)
9+
from application.logging import LogContext
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class AgenticAgent(BaseAgent):
15+
"""Agent where the LLM controls retrieval via tools.
16+
17+
Unlike ClassicAgent which pre-fetches docs into the prompt,
18+
AgenticAgent gives the LLM an internal_search tool so it can
19+
decide when, what, and whether to search.
20+
"""
21+
22+
def __init__(
23+
self,
24+
retriever_config: Optional[Dict] = None,
25+
*args,
26+
**kwargs,
27+
):
28+
super().__init__(*args, **kwargs)
29+
self.retriever_config = retriever_config or {}
30+
31+
def _gen_inner(
32+
self, query: str, log_context: LogContext
33+
) -> Generator[Dict, None, None]:
34+
tools_dict = self.tool_executor.get_tools()
35+
add_internal_search_tool(tools_dict, self.retriever_config)
36+
self._prepare_tools(tools_dict)
37+
38+
# 4. Build messages (prompt has NO pre-fetched docs)
39+
messages = self._build_messages(self.prompt, query)
40+
41+
# 5. Call LLM — the handler manages the tool loop
42+
llm_response = self._llm_gen(messages, log_context)
43+
44+
yield from self._handle_response(
45+
llm_response, tools_dict, messages, log_context
46+
)
47+
48+
# 6. Collect sources from internal search tool results
49+
self._collect_internal_sources()
50+
51+
yield {"sources": self.retrieved_docs}
52+
yield {"tool_calls": self._get_truncated_tool_calls()}
53+
54+
log_context.stacks.append(
55+
{"component": "agent", "data": {"tool_calls": self.tool_calls.copy()}}
56+
)
57+
58+
def _collect_internal_sources(self):
59+
"""Collect retrieved docs from the cached InternalSearchTool instance."""
60+
cache_key = f"internal_search:{INTERNAL_TOOL_ID}:{self.user or ''}"
61+
tool = self.tool_executor._loaded_tools.get(cache_key)
62+
if tool and hasattr(tool, "retrieved_docs") and tool.retrieved_docs:
63+
self.retrieved_docs = tool.retrieved_docs

0 commit comments

Comments
 (0)