Commit
Β·
848db06
1
Parent(s):
05ecbdd
updated research agent
Browse files
__pycache__/langgraph_agent_system.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/langgraph_agent_system.cpython-310.pyc and b/__pycache__/langgraph_agent_system.cpython-310.pyc differ
|
|
|
agents/__pycache__/research_agent.cpython-313.pyc
CHANGED
|
Binary files a/agents/__pycache__/research_agent.cpython-313.pyc and b/agents/__pycache__/research_agent.cpython-313.pyc differ
|
|
|
agents/research_agent.py
CHANGED
|
@@ -29,86 +29,94 @@ from tools import (
|
|
| 29 |
load_dotenv("env.local")
|
| 30 |
|
| 31 |
|
| 32 |
-
def
|
| 33 |
-
"""Create
|
| 34 |
-
tools = []
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
from tools import get_tavily_tool, get_wikipedia_tool, get_arxiv_tool
|
| 39 |
-
|
| 40 |
-
# Tavily web search
|
| 41 |
try:
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
| 49 |
result = tavily_tools[0].call({"input": query})
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
description="Search the web for current information and facts using Tavily API",
|
| 58 |
-
func=tavily_search
|
| 59 |
-
)
|
| 60 |
-
tools.append(tavily_tool)
|
| 61 |
-
print(f"β
Added Tavily web search tool")
|
| 62 |
except Exception as e:
|
| 63 |
-
print(f"
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
try:
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
| 70 |
try:
|
| 71 |
-
result =
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
print("β
Added Wikipedia tool")
|
| 83 |
except Exception as e:
|
| 84 |
-
print(f"
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
| 87 |
try:
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
try:
|
| 92 |
-
result = arxiv_tool.call({"
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
print("β
Added ArXiv tool")
|
| 104 |
except Exception as e:
|
| 105 |
-
print(f"
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
|
| 111 |
-
return tools
|
| 112 |
|
| 113 |
|
| 114 |
def load_research_prompt() -> str:
|
|
@@ -133,15 +141,19 @@ When researching:
|
|
| 133 |
- Cross-reference information across sources
|
| 134 |
- Note any conflicting information found
|
| 135 |
|
|
|
|
|
|
|
|
|
|
| 136 |
Format your response as:
|
| 137 |
-
### Research
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
|
|
|
| 141 |
|
| 142 |
### Key Facts
|
| 143 |
- Fact 1
|
| 144 |
-
- Fact 2
|
| 145 |
- Fact 3
|
| 146 |
|
| 147 |
### Citations
|
|
@@ -150,6 +162,46 @@ Format your response as:
|
|
| 150 |
"""
|
| 151 |
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
def research_agent(state: Dict[str, Any]) -> Command:
|
| 154 |
"""
|
| 155 |
Research Agent node that gathers information using available tools.
|
|
@@ -163,28 +215,21 @@ def research_agent(state: Dict[str, Any]) -> Command:
|
|
| 163 |
# Get research prompt
|
| 164 |
research_prompt = load_research_prompt()
|
| 165 |
|
| 166 |
-
# Initialize LLM
|
| 167 |
llm = ChatGroq(
|
| 168 |
model="llama-3.3-70b-versatile",
|
| 169 |
temperature=0.3, # Slightly higher for research creativity
|
| 170 |
max_tokens=2048
|
| 171 |
)
|
| 172 |
|
| 173 |
-
# Get research
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
# Bind tools to LLM if available
|
| 177 |
-
if tools:
|
| 178 |
-
llm_with_tools = llm.bind_tools(tools)
|
| 179 |
-
else:
|
| 180 |
-
llm_with_tools = llm
|
| 181 |
-
print("β οΈ No tools available, proceeding with LLM only")
|
| 182 |
|
| 183 |
# Create agent span for tracing
|
| 184 |
with agent_span(
|
| 185 |
"research",
|
| 186 |
metadata={
|
| 187 |
-
"tools_available": len(
|
| 188 |
"user_id": state.get("user_id", "unknown"),
|
| 189 |
"session_id": state.get("session_id", "unknown")
|
| 190 |
}
|
|
@@ -198,65 +243,49 @@ def research_agent(state: Dict[str, Any]) -> Command:
|
|
| 198 |
user_query = msg.content
|
| 199 |
break
|
| 200 |
|
| 201 |
-
#
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
-
|
|
|
|
| 206 |
|
| 207 |
Current research status: {len(state.get('research_notes', ''))} characters already gathered
|
| 208 |
|
| 209 |
Instructions:
|
| 210 |
-
1.
|
| 211 |
-
2.
|
| 212 |
-
3.
|
| 213 |
-
4.
|
| 214 |
-
5. Structure your findings clearly
|
| 215 |
|
| 216 |
-
Please
|
| 217 |
"""
|
| 218 |
|
| 219 |
-
# Create messages for
|
| 220 |
-
|
| 221 |
SystemMessage(content=research_prompt),
|
| 222 |
-
HumanMessage(content=
|
| 223 |
]
|
| 224 |
|
| 225 |
-
# Get
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
response = llm_with_tools.invoke(research_messages)
|
| 229 |
-
|
| 230 |
-
# If the response contains tool calls, execute them
|
| 231 |
-
if hasattr(response, 'tool_calls') and response.tool_calls:
|
| 232 |
-
print(f"π οΈ Executing {len(response.tool_calls)} tool calls")
|
| 233 |
-
|
| 234 |
-
# Execute tool calls and collect results
|
| 235 |
-
tool_results = []
|
| 236 |
-
for tool_call in response.tool_calls:
|
| 237 |
-
try:
|
| 238 |
-
# Find the tool
|
| 239 |
-
tool = next((t for t in tools if t.name == tool_call['name']), None)
|
| 240 |
-
if tool:
|
| 241 |
-
result = tool.run(tool_call['args'])
|
| 242 |
-
tool_results.append(f"**{tool.name}**: {result}")
|
| 243 |
-
except Exception as e:
|
| 244 |
-
print(f"β οΈ Tool {tool_call['name']} failed: {e}")
|
| 245 |
-
tool_results.append(f"**{tool_call['name']}**: Error - {str(e)}")
|
| 246 |
-
|
| 247 |
-
# Compile research results
|
| 248 |
-
research_findings = "\n\n".join(tool_results) if tool_results else response.content
|
| 249 |
-
else:
|
| 250 |
-
research_findings = response.content
|
| 251 |
-
else:
|
| 252 |
-
# No tools available, use LLM knowledge only
|
| 253 |
-
research_findings = llm.invoke(research_messages).content
|
| 254 |
|
| 255 |
# Format research results
|
| 256 |
formatted_results = f"""
|
| 257 |
### Research Iteration {state.get('loop_counter', 0) + 1}
|
| 258 |
|
| 259 |
-
{
|
|
|
|
|
|
|
|
|
|
| 260 |
|
| 261 |
---
|
| 262 |
"""
|
|
|
|
| 29 |
load_dotenv("env.local")
|
| 30 |
|
| 31 |
|
| 32 |
+
def create_research_functions() -> List[callable]:
|
| 33 |
+
"""Create simple research functions that can be called directly"""
|
|
|
|
| 34 |
|
| 35 |
+
def web_search(query: str) -> str:
|
| 36 |
+
"""Search the web using Tavily API"""
|
|
|
|
|
|
|
|
|
|
| 37 |
try:
|
| 38 |
+
with tool_span("tavily_search", metadata={"query": query}):
|
| 39 |
+
from tools import get_tavily_tool
|
| 40 |
+
tavily_spec = get_tavily_tool()
|
| 41 |
+
if tavily_spec:
|
| 42 |
+
tavily_tools = tavily_spec.to_tool_list()
|
| 43 |
+
if tavily_tools and len(tavily_tools) > 0:
|
| 44 |
+
# Try different parameter formats for Tavily
|
| 45 |
+
try:
|
| 46 |
result = tavily_tools[0].call({"input": query})
|
| 47 |
+
except Exception:
|
| 48 |
+
try:
|
| 49 |
+
result = tavily_tools[0].call({"query": query})
|
| 50 |
+
except Exception:
|
| 51 |
+
result = tavily_tools[0].call(query)
|
| 52 |
+
return f"Web Search Results for '{query}':\n{str(result)}"
|
| 53 |
+
return "Web search tool not available"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
except Exception as e:
|
| 55 |
+
print(f"Web search failed: {e}")
|
| 56 |
+
return f"Web search not available: {str(e)}"
|
| 57 |
+
|
| 58 |
+
def wikipedia_search(query: str) -> str:
|
| 59 |
+
"""Search Wikipedia for information"""
|
| 60 |
try:
|
| 61 |
+
with tool_span("wikipedia_search", metadata={"query": query}):
|
| 62 |
+
from tools import get_wikipedia_tool
|
| 63 |
+
wiki_tool = get_wikipedia_tool()
|
| 64 |
+
if wiki_tool:
|
| 65 |
+
# Try different parameter formats for Wikipedia
|
| 66 |
try:
|
| 67 |
+
result = wiki_tool.call({"query_str": query})
|
| 68 |
+
except Exception:
|
| 69 |
+
try:
|
| 70 |
+
result = wiki_tool.call({"input": query})
|
| 71 |
+
except Exception:
|
| 72 |
+
try:
|
| 73 |
+
result = wiki_tool.call({"query": query})
|
| 74 |
+
except Exception:
|
| 75 |
+
result = wiki_tool.call(query)
|
| 76 |
+
return f"Wikipedia Results for '{query}':\n{str(result)}"
|
| 77 |
+
return "Wikipedia tool not available"
|
|
|
|
| 78 |
except Exception as e:
|
| 79 |
+
print(f"Wikipedia search failed: {e}")
|
| 80 |
+
return f"Wikipedia search not available: {str(e)}"
|
| 81 |
+
|
| 82 |
+
def arxiv_search(query: str) -> str:
|
| 83 |
+
"""Search ArXiv for academic papers"""
|
| 84 |
try:
|
| 85 |
+
with tool_span("arxiv_search", metadata={"query": query}):
|
| 86 |
+
from tools import get_arxiv_tool
|
| 87 |
+
arxiv_tool = get_arxiv_tool()
|
| 88 |
+
if arxiv_tool:
|
| 89 |
+
# Try different parameter formats for ArXiv
|
| 90 |
try:
|
| 91 |
+
result = arxiv_tool.call({"query_str": query})
|
| 92 |
+
except Exception:
|
| 93 |
+
try:
|
| 94 |
+
result = arxiv_tool.call({"input": query})
|
| 95 |
+
except Exception:
|
| 96 |
+
try:
|
| 97 |
+
result = arxiv_tool.call({"query": query})
|
| 98 |
+
except Exception:
|
| 99 |
+
result = arxiv_tool.call(query)
|
| 100 |
+
return f"ArXiv Results for '{query}':\n{str(result)}"
|
| 101 |
+
return "ArXiv tool not available"
|
|
|
|
| 102 |
except Exception as e:
|
| 103 |
+
print(f"ArXiv search failed: {e}")
|
| 104 |
+
return f"ArXiv search not available: {str(e)}"
|
| 105 |
|
| 106 |
+
# Add a simple fallback research function that doesn't use external APIs
|
| 107 |
+
def fallback_research(query: str) -> str:
|
| 108 |
+
"""Provide basic context when external tools fail"""
|
| 109 |
+
return f"""
|
| 110 |
+
Fallback Research for '{query}':
|
| 111 |
+
|
| 112 |
+
This research uses general knowledge available in the system.
|
| 113 |
+
For comprehensive research, external API access (Tavily, Wikipedia) would be needed.
|
| 114 |
+
|
| 115 |
+
Basic information may be available through the language model's training data,
|
| 116 |
+
but current information would require working API connections.
|
| 117 |
+
"""
|
| 118 |
|
| 119 |
+
return [web_search, wikipedia_search, arxiv_search, fallback_research]
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
def load_research_prompt() -> str:
|
|
|
|
| 141 |
- Cross-reference information across sources
|
| 142 |
- Note any conflicting information found
|
| 143 |
|
| 144 |
+
Important: You have access to research functions, but you cannot call them directly.
|
| 145 |
+
Instead, specify what searches you would like to perform and the system will execute them for you.
|
| 146 |
+
|
| 147 |
Format your response as:
|
| 148 |
+
### Research Strategy
|
| 149 |
+
[Describe what searches are needed]
|
| 150 |
+
|
| 151 |
+
### Analysis
|
| 152 |
+
[Analyze the information once gathered]
|
| 153 |
|
| 154 |
### Key Facts
|
| 155 |
- Fact 1
|
| 156 |
+
- Fact 2
|
| 157 |
- Fact 3
|
| 158 |
|
| 159 |
### Citations
|
|
|
|
| 162 |
"""
|
| 163 |
|
| 164 |
|
| 165 |
+
def perform_research_searches(query: str, research_functions: List[callable]) -> str:
|
| 166 |
+
"""
|
| 167 |
+
Intelligently perform research searches based on the query
|
| 168 |
+
"""
|
| 169 |
+
results = []
|
| 170 |
+
|
| 171 |
+
# Always try web search first for current info
|
| 172 |
+
web_search, wikipedia_search, arxiv_search, fallback_research = research_functions
|
| 173 |
+
|
| 174 |
+
print("π Performing web search...")
|
| 175 |
+
web_result = web_search(query)
|
| 176 |
+
results.append(web_result)
|
| 177 |
+
|
| 178 |
+
# Try Wikipedia for encyclopedic info
|
| 179 |
+
print("π Performing Wikipedia search...")
|
| 180 |
+
wiki_result = wikipedia_search(query)
|
| 181 |
+
results.append(wiki_result)
|
| 182 |
+
|
| 183 |
+
# For academic/technical queries, try ArXiv
|
| 184 |
+
academic_keywords = ['research', 'study', 'paper', 'algorithm', 'model', 'theory', 'analysis']
|
| 185 |
+
if any(keyword in query.lower() for keyword in academic_keywords):
|
| 186 |
+
print("π Performing ArXiv search...")
|
| 187 |
+
arxiv_result = arxiv_search(query)
|
| 188 |
+
results.append(arxiv_result)
|
| 189 |
+
|
| 190 |
+
# Check if we got meaningful results, if not, use fallback
|
| 191 |
+
meaningful_results = []
|
| 192 |
+
for result in results:
|
| 193 |
+
if result and not ("not available" in result or "error" in result.lower()):
|
| 194 |
+
meaningful_results.append(result)
|
| 195 |
+
|
| 196 |
+
# If no meaningful results, add fallback research
|
| 197 |
+
if not meaningful_results:
|
| 198 |
+
print("π Using fallback research...")
|
| 199 |
+
fallback_result = fallback_research(query)
|
| 200 |
+
results.append(fallback_result)
|
| 201 |
+
|
| 202 |
+
return "\n\n---\n\n".join(results)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
def research_agent(state: Dict[str, Any]) -> Command:
|
| 206 |
"""
|
| 207 |
Research Agent node that gathers information using available tools.
|
|
|
|
| 215 |
# Get research prompt
|
| 216 |
research_prompt = load_research_prompt()
|
| 217 |
|
| 218 |
+
# Initialize LLM without tool binding (we'll call tools manually)
|
| 219 |
llm = ChatGroq(
|
| 220 |
model="llama-3.3-70b-versatile",
|
| 221 |
temperature=0.3, # Slightly higher for research creativity
|
| 222 |
max_tokens=2048
|
| 223 |
)
|
| 224 |
|
| 225 |
+
# Get research functions
|
| 226 |
+
research_functions = create_research_functions()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
# Create agent span for tracing
|
| 229 |
with agent_span(
|
| 230 |
"research",
|
| 231 |
metadata={
|
| 232 |
+
"tools_available": len(research_functions),
|
| 233 |
"user_id": state.get("user_id", "unknown"),
|
| 234 |
"session_id": state.get("session_id", "unknown")
|
| 235 |
}
|
|
|
|
| 243 |
user_query = msg.content
|
| 244 |
break
|
| 245 |
|
| 246 |
+
# Perform actual research searches
|
| 247 |
+
print(f"π Researching: {user_query}")
|
| 248 |
+
research_raw_results = perform_research_searches(user_query, research_functions)
|
| 249 |
+
|
| 250 |
+
# Now ask LLM to analyze and structure the results
|
| 251 |
+
analysis_request = f"""
|
| 252 |
+
Based on the research results below, provide a structured analysis to help answer the user's question.
|
| 253 |
+
|
| 254 |
+
Original Question: {user_query}
|
| 255 |
|
| 256 |
+
Research Results:
|
| 257 |
+
{research_raw_results}
|
| 258 |
|
| 259 |
Current research status: {len(state.get('research_notes', ''))} characters already gathered
|
| 260 |
|
| 261 |
Instructions:
|
| 262 |
+
1. Analyze the search results for relevant information
|
| 263 |
+
2. Extract key facts that help answer the question
|
| 264 |
+
3. Note any important details or findings
|
| 265 |
+
4. Identify if additional specific searches might be needed
|
| 266 |
+
5. Structure your findings clearly with citations
|
| 267 |
|
| 268 |
+
Please provide a comprehensive analysis of the research findings.
|
| 269 |
"""
|
| 270 |
|
| 271 |
+
# Create messages for analysis
|
| 272 |
+
analysis_messages = [
|
| 273 |
SystemMessage(content=research_prompt),
|
| 274 |
+
HumanMessage(content=analysis_request)
|
| 275 |
]
|
| 276 |
|
| 277 |
+
# Get analysis response
|
| 278 |
+
response = llm.invoke(analysis_messages)
|
| 279 |
+
analysis_content = response.content if hasattr(response, 'content') else str(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
# Format research results
|
| 282 |
formatted_results = f"""
|
| 283 |
### Research Iteration {state.get('loop_counter', 0) + 1}
|
| 284 |
|
| 285 |
+
{analysis_content}
|
| 286 |
+
|
| 287 |
+
### Raw Search Results
|
| 288 |
+
{research_raw_results}
|
| 289 |
|
| 290 |
---
|
| 291 |
"""
|