Skip to content

Commit 462f2e9

Browse files
committed
mini refactors
1 parent c6ece17 commit 462f2e9

22 files changed

Lines changed: 2233 additions & 98 deletions

application/agents/agentic_agent.py

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from application.agents.base import BaseAgent
55
from application.agents.tools.internal_search import (
66
INTERNAL_TOOL_ID,
7-
build_internal_tool_config,
8-
build_internal_tool_entry,
7+
add_internal_search_tool,
98
)
109
from application.logging import LogContext
1110

@@ -32,24 +31,8 @@ def __init__(
3231
def _gen_inner(
3332
self, query: str, log_context: LogContext
3433
) -> Generator[Dict, None, None]:
35-
# 1. Get user tools (same as ClassicAgent)
3634
tools_dict = self.tool_executor.get_tools()
37-
38-
# 2. Add internal search as a synthetic tool (only if sources are configured)
39-
source = self.retriever_config.get("source", {})
40-
has_sources = bool(source.get("active_docs"))
41-
if self.retriever_config and has_sources:
42-
has_dir = _sources_have_directory_structure(source)
43-
internal_entry = build_internal_tool_entry(
44-
has_directory_structure=has_dir
45-
)
46-
internal_entry["config"] = build_internal_tool_config(
47-
**self.retriever_config,
48-
has_directory_structure=has_dir,
49-
)
50-
tools_dict[INTERNAL_TOOL_ID] = internal_entry
51-
52-
# 3. Prepare all tools for the LLM
35+
add_internal_search_tool(tools_dict, self.retriever_config)
5336
self._prepare_tools(tools_dict)
5437

5538
# 4. Build messages (prompt has NO pre-fetched docs)
@@ -78,40 +61,3 @@ def _collect_internal_sources(self):
7861
tool = self.tool_executor._loaded_tools.get(cache_key)
7962
if tool and hasattr(tool, "retrieved_docs") and tool.retrieved_docs:
8063
self.retrieved_docs = tool.retrieved_docs
81-
82-
83-
def _sources_have_directory_structure(source: Dict) -> bool:
84-
"""Check if any of the active sources have directory_structure in MongoDB."""
85-
active_docs = source.get("active_docs", [])
86-
if not active_docs:
87-
return False
88-
89-
try:
90-
from bson.objectid import ObjectId
91-
from application.core.mongo_db import MongoDB
92-
93-
mongo = MongoDB.get_client()
94-
db = mongo[settings.MONGO_DB_NAME]
95-
sources_collection = db["sources"]
96-
97-
if isinstance(active_docs, str):
98-
active_docs = [active_docs]
99-
100-
for doc_id in active_docs:
101-
try:
102-
source_doc = sources_collection.find_one(
103-
{"_id": ObjectId(doc_id)},
104-
{"directory_structure": 1},
105-
)
106-
if source_doc and source_doc.get("directory_structure"):
107-
return True
108-
except Exception:
109-
continue
110-
except Exception as e:
111-
logger.debug(f"Could not check directory structure: {e}")
112-
113-
return False
114-
115-
116-
# Import settings at module level for _sources_have_directory_structure
117-
from application.core.settings import settings # noqa: E402

application/agents/research_agent.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66

77
from application.agents.base import BaseAgent
88
from application.agents.tool_executor import ToolExecutor
9-
from application.agents.agentic_agent import _sources_have_directory_structure
109
from application.agents.tools.internal_search import (
1110
INTERNAL_TOOL_ID,
12-
build_internal_tool_config,
13-
build_internal_tool_entry,
11+
add_internal_search_tool,
1412
)
1513
from application.agents.tools.think import THINK_TOOL_ENTRY, THINK_TOOL_ID
1614
from application.logging import LogContext
@@ -130,6 +128,7 @@ def __init__(
130128
self.citations = CitationManager()
131129
self._start_time: float = 0
132130
self._tokens_used: int = 0
131+
self._last_token_snapshot: int = 0
133132

134133
# ------------------------------------------------------------------
135134
# Budget & timeout helpers
@@ -153,7 +152,9 @@ def _is_over_budget(self) -> bool:
153152
def _snapshot_llm_tokens(self) -> int:
154153
"""Read current token usage from LLM and return delta since last snapshot."""
155154
current = self.llm.token_usage.get("prompt_tokens", 0) + self.llm.token_usage.get("generated_tokens", 0)
156-
return current
155+
delta = current - self._last_token_snapshot
156+
self._last_token_snapshot = current
157+
return delta
157158

158159
# ------------------------------------------------------------------
159160
# Main orchestration
@@ -272,21 +273,7 @@ def _setup_tools(self) -> Dict:
272273
"""Build tools_dict with user tools + internal search + think."""
273274
tools_dict = self.tool_executor.get_tools()
274275

275-
# Only add internal search if sources are configured
276-
source = self.retriever_config.get("source", {})
277-
has_sources = bool(source.get("active_docs"))
278-
if self.retriever_config and has_sources:
279-
has_dir = _sources_have_directory_structure(source)
280-
internal_entry = build_internal_tool_entry(
281-
has_directory_structure=has_dir
282-
)
283-
internal_entry["config"] = build_internal_tool_config(
284-
**self.retriever_config,
285-
has_directory_structure=has_dir,
286-
)
287-
tools_dict[INTERNAL_TOOL_ID] = internal_entry
288-
elif self.retriever_config and not has_sources:
289-
logger.info("ResearchAgent: No sources configured, skipping internal_search tool")
276+
add_internal_search_tool(tools_dict, self.retriever_config)
290277

291278
think_entry = dict(THINK_TOOL_ENTRY)
292279
think_entry["config"] = {}
@@ -580,7 +567,14 @@ def _execute_step_tools_with_refinement(
580567
call_id = None
581568
while True:
582569
try:
583-
next(gen)
570+
event = next(gen)
571+
# Log tool_call status events instead of discarding them
572+
if isinstance(event, dict) and event.get("type") == "tool_call":
573+
logger.debug(
574+
"Tool %s status: %s",
575+
event.get("data", {}).get("action_name", ""),
576+
event.get("data", {}).get("status", ""),
577+
)
584578
except StopIteration as e:
585579
result, call_id = e.value
586580
break

application/agents/tools/internal_search.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,59 @@ def build_internal_tool_entry(has_directory_structure: bool = False) -> Dict:
354354
INTERNAL_TOOL_ENTRY = build_internal_tool_entry(has_directory_structure=False)
355355

356356

357+
def sources_have_directory_structure(source: Dict) -> bool:
358+
"""Check if any of the active sources have directory_structure in MongoDB."""
359+
active_docs = source.get("active_docs", [])
360+
if not active_docs:
361+
return False
362+
363+
try:
364+
from bson.objectid import ObjectId
365+
from application.core.mongo_db import MongoDB
366+
367+
mongo = MongoDB.get_client()
368+
db = mongo[settings.MONGO_DB_NAME]
369+
sources_collection = db["sources"]
370+
371+
if isinstance(active_docs, str):
372+
active_docs = [active_docs]
373+
374+
for doc_id in active_docs:
375+
try:
376+
source_doc = sources_collection.find_one(
377+
{"_id": ObjectId(doc_id)},
378+
{"directory_structure": 1},
379+
)
380+
if source_doc and source_doc.get("directory_structure"):
381+
return True
382+
except Exception:
383+
continue
384+
except Exception as e:
385+
logger.debug(f"Could not check directory structure: {e}")
386+
387+
return False
388+
389+
390+
def add_internal_search_tool(tools_dict: Dict, retriever_config: Dict) -> None:
391+
"""Add the internal search tool to tools_dict if sources are configured.
392+
393+
Shared by AgenticAgent and ResearchAgent to avoid duplicate setup logic.
394+
Mutates tools_dict in place.
395+
"""
396+
source = retriever_config.get("source", {})
397+
has_sources = bool(source.get("active_docs"))
398+
if not retriever_config or not has_sources:
399+
return
400+
401+
has_dir = sources_have_directory_structure(source)
402+
internal_entry = build_internal_tool_entry(has_directory_structure=has_dir)
403+
internal_entry["config"] = build_internal_tool_config(
404+
**retriever_config,
405+
has_directory_structure=has_dir,
406+
)
407+
tools_dict[INTERNAL_TOOL_ID] = internal_entry
408+
409+
357410
def build_internal_tool_config(
358411
source: Dict,
359412
retriever_name: str = "classic",

application/agents/workflows/node_agent.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from typing import Any, Dict, List, Optional, Type
44

5+
from application.agents.agentic_agent import AgenticAgent
56
from application.agents.base import BaseAgent
67
from application.agents.classic_agent import ClassicAgent
8+
from application.agents.research_agent import ResearchAgent
79
from application.agents.workflows.schemas import AgentType
810

911

@@ -35,7 +37,8 @@ def _get_tools(self, api_key: str = None) -> Dict[str, Dict[str, Any]]:
3537
return filtered_tools
3638

3739

38-
class WorkflowNodeClassicAgent(ToolFilterMixin, ClassicAgent):
40+
class _WorkflowNodeMixin:
41+
"""Common __init__ for all workflow node agents."""
3942

4043
def __init__(
4144
self,
@@ -56,11 +59,25 @@ def __init__(
5659
self._allowed_tool_ids = tool_ids or []
5760

5861

62+
class WorkflowNodeClassicAgent(ToolFilterMixin, _WorkflowNodeMixin, ClassicAgent):
63+
pass
64+
65+
66+
class WorkflowNodeAgenticAgent(ToolFilterMixin, _WorkflowNodeMixin, AgenticAgent):
67+
pass
68+
69+
70+
class WorkflowNodeResearchAgent(ToolFilterMixin, _WorkflowNodeMixin, ResearchAgent):
71+
pass
72+
73+
5974
class WorkflowNodeAgentFactory:
6075

6176
_agents: Dict[AgentType, Type[BaseAgent]] = {
6277
AgentType.CLASSIC: WorkflowNodeClassicAgent,
6378
AgentType.REACT: WorkflowNodeClassicAgent, # backwards compat
79+
AgentType.AGENTIC: WorkflowNodeAgenticAgent,
80+
AgentType.RESEARCH: WorkflowNodeResearchAgent,
6481
}
6582

6683
@classmethod

application/agents/workflows/schemas.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class NodeType(str, Enum):
1818
class AgentType(str, Enum):
1919
CLASSIC = "classic"
2020
REACT = "react"
21+
AGENTIC = "agentic"
22+
RESEARCH = "research"
2123

2224

2325
class ExecutionStatus(str, Enum):

application/agents/workflows/workflow_engine.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from application.agents.workflows.node_agent import WorkflowNodeAgentFactory
88
from application.agents.workflows.schemas import (
99
AgentNodeConfig,
10+
AgentType,
1011
ConditionNodeConfig,
1112
ExecutionStatus,
1213
NodeExecutionLog,
@@ -223,18 +224,32 @@ def _execute_agent_node(
223224
f'Model "{node_model_id}" does not support structured output for node "{node.title}"'
224225
)
225226

226-
node_agent = WorkflowNodeAgentFactory.create(
227-
agent_type=node_config.agent_type,
228-
endpoint=self.agent.endpoint,
229-
llm_name=node_llm_name,
230-
model_id=node_model_id,
231-
api_key=node_api_key,
232-
tool_ids=node_config.tools,
233-
prompt=node_config.system_prompt,
234-
chat_history=self.agent.chat_history,
235-
decoded_token=self.agent.decoded_token,
236-
json_schema=node_json_schema,
237-
)
227+
factory_kwargs = {
228+
"agent_type": node_config.agent_type,
229+
"endpoint": self.agent.endpoint,
230+
"llm_name": node_llm_name,
231+
"model_id": node_model_id,
232+
"api_key": node_api_key,
233+
"tool_ids": node_config.tools,
234+
"prompt": node_config.system_prompt,
235+
"chat_history": self.agent.chat_history,
236+
"decoded_token": self.agent.decoded_token,
237+
"json_schema": node_json_schema,
238+
}
239+
240+
# Agentic/research agents need retriever_config for on-demand search
241+
if node_config.agent_type in (AgentType.AGENTIC, AgentType.RESEARCH):
242+
factory_kwargs["retriever_config"] = {
243+
"source": {"active_docs": node_config.sources} if node_config.sources else {},
244+
"retriever_name": node_config.retriever or "classic",
245+
"chunks": int(node_config.chunks) if node_config.chunks else 2,
246+
"model_id": node_model_id,
247+
"llm_name": node_llm_name,
248+
"api_key": node_api_key,
249+
"decoded_token": self.agent.decoded_token,
250+
}
251+
252+
node_agent = WorkflowNodeAgentFactory.create(**factory_kwargs)
238253

239254
full_response_parts: List[str] = []
240255
structured_response_parts: List[str] = []

application/prompts/agentic/creative.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ Use the search_internal tool to find relevant information before answering quest
1313
You may search multiple times with different queries if needed.
1414
Do not guess when documents are available — search first, then answer based on what you find.
1515
If no relevant documents are found, use your general knowledge and tool capabilities.
16-
Allow yourself to be very creative and use your imagination.
16+
Allow yourself to be very creative and use your imagination.

application/prompts/agentic/default.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ You have access to a search tool that searches the user's uploaded documents and
1212
Use the search_internal tool to find relevant information before answering questions.
1313
You may search multiple times with different queries if needed.
1414
Do not guess when documents are available — search first, then answer based on what you find.
15-
If no relevant documents are found, use your general knowledge and tool capabilities.
15+
If no relevant documents are found, use your general knowledge and tool capabilities.

application/prompts/agentic/strict.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ Use the search_internal tool to find relevant information before answering quest
1313
You may search multiple times with different queries if needed.
1414
You MUST search before answering any factual question. Do not guess or use general knowledge when documents are available.
1515
If you dont have enough information from the search results or tools, answer "I don't know" or "I don't have enough information".
16-
Never make up information or provide false information!
16+
Never make up information or provide false information!

application/prompts/research/clarification.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ You MUST respond with ONLY a valid JSON object (no markdown, no code fences):
2020
"needs_clarification": true or false,
2121
"questions": ["question 1", "question 2"] (only if needs_clarification is true, max 3 questions),
2222
"reason": "brief explanation of why clarification is or isn't needed"
23-
}
23+
}

0 commit comments

Comments
 (0)