|
|
from llama_index.llms.huggingface_api import HuggingFaceInferenceAPI |
|
|
from tools.simple_tools import ( |
|
|
search_web_tool, record_notes_tool, write_report_tool, review_report_tool, |
|
|
get_workflow_state, reset_workflow_state |
|
|
) |
|
|
from dotenv import load_dotenv |
|
|
import os |
|
|
from llama_index.core.agent.workflow import AgentWorkflow, ReActAgent |
|
|
from llama_index.core.workflow import Context |
|
|
|
|
|
load_dotenv(os.path.join(os.path.dirname(__file__), 'env.local')) |
|
|
|
|
|
class LlamaIndexReportAgent: |
|
|
def __init__(self): |
|
|
self.llm = HuggingFaceInferenceAPI( |
|
|
model_name="microsoft/Phi-3.5-mini-instruct", |
|
|
token=os.getenv("HUGGING_FACE_TOKEN") |
|
|
) |
|
|
|
|
|
self.research_agent = ReActAgent( |
|
|
name="ResearchAgent", |
|
|
description="Searches the web and records notes.", |
|
|
system_prompt=( |
|
|
"You are a Research Agent. Your ONLY job is to research and hand off to WriteAgent.\n" |
|
|
"\n" |
|
|
"STRICT WORKFLOW:\n" |
|
|
"1. Use search_web tool to search for information\n" |
|
|
"2. Use record_notes tool to save what you found\n" |
|
|
"3. Say: 'Research complete. I have gathered sufficient information. Handing off to WriteAgent.'\n" |
|
|
"\n" |
|
|
"CRITICAL RULES:\n" |
|
|
"- You can ONLY use search_web and record_notes tools\n" |
|
|
"- You CANNOT write reports - that's WriteAgent's job\n" |
|
|
"- You CANNOT use write_report tool - you don't have access to it\n" |
|
|
"- After research, you MUST hand off with the exact message above\n" |
|
|
"- Do NOT attempt to write any report content yourself\n" |
|
|
"\n" |
|
|
"AVAILABLE TOOLS: search_web, record_notes\n" |
|
|
"HANDOFF MESSAGE: 'Research complete. I have gathered sufficient information. Handing off to WriteAgent.'" |
|
|
), |
|
|
tools=[search_web_tool, record_notes_tool], |
|
|
llm=self.llm, |
|
|
can_handoff_to=["WriteAgent"], |
|
|
) |
|
|
|
|
|
self.write_agent = ReActAgent( |
|
|
name="WriteAgent", |
|
|
description="Writes a structured report based on research notes.", |
|
|
system_prompt=( |
|
|
"You are a Writing Agent. Your purpose is to create a concise, well-structured report.\n" |
|
|
"\n" |
|
|
"INSTRUCTIONS:\n" |
|
|
"1. Check if there's any feedback from ReviewAgent (not 'Review required.')\n" |
|
|
"2. If there's feedback, revise the report accordingly\n" |
|
|
"3. If no feedback, create initial report based on research\n" |
|
|
"4. MUST call write_report tool with these parameters:\n" |
|
|
" - report_content: Concise markdown report (200-400 words)\n" |
|
|
" - title: Descriptive report title\n" |
|
|
"5. Report structure (keep sections brief):\n" |
|
|
" - # Main Title\n" |
|
|
" - ## Introduction (1-2 sentences)\n" |
|
|
" - ## Key Points (2-3 bullet points)\n" |
|
|
" - ## Conclusion (1-2 sentences)\n" |
|
|
"6. After calling tool: 'Report written. Handing off to ReviewAgent.'\n" |
|
|
"\n" |
|
|
"CRITICAL: Keep the report_content CONCISE to avoid truncation!\n" |
|
|
"You MUST actually call the write_report tool with proper parameters!" |
|
|
), |
|
|
tools=[write_report_tool], |
|
|
llm=self.llm, |
|
|
can_handoff_to=["ReviewAgent"], |
|
|
) |
|
|
|
|
|
self.review_agent = ReActAgent( |
|
|
name="ReviewAgent", |
|
|
description="Reviews the written report.", |
|
|
system_prompt=( |
|
|
"You are a Reviewing Agent. Your purpose is to review the report quality.\n" |
|
|
"1. Check the report content that was written\n" |
|
|
"2. Use review_report tool to provide feedback\n" |
|
|
"3. If report is good quality, start feedback with 'APPROVED:'\n" |
|
|
"4. If needs improvement, provide specific suggestions and hand off to WriteAgent\n" |
|
|
"5. Quality criteria: clear structure, sufficient detail, proper formatting" |
|
|
), |
|
|
tools=[review_report_tool], |
|
|
llm=self.llm, |
|
|
can_handoff_to=["WriteAgent"], |
|
|
) |
|
|
|
|
|
self.agent_workflow = AgentWorkflow( |
|
|
agents=[self.research_agent, self.write_agent, self.review_agent], |
|
|
root_agent=self.research_agent.name, |
|
|
initial_state={ |
|
|
"research_notes": {}, |
|
|
"report_content": "Not written yet.", |
|
|
"review": "Review required.", |
|
|
}, |
|
|
) |
|
|
|
|
|
def get_final_state(self) -> dict: |
|
|
"""Get the final workflow state from the simple tools.""" |
|
|
return get_workflow_state() |
|
|
|
|
|
async def run_workflow(self, user_msg=None): |
|
|
if user_msg is None: |
|
|
user_msg = ( |
|
|
"Write me a report on the history of the internet. " |
|
|
"Briefly describe the history of the internet, including the development of the internet, the development of the web, " |
|
|
"and the development of the internet in the 21st century." |
|
|
) |
|
|
|
|
|
|
|
|
reset_workflow_state() |
|
|
|
|
|
|
|
|
ctx = Context(self.agent_workflow) |
|
|
await ctx.set("state", { |
|
|
"research_notes": {}, |
|
|
"report_content": "Not written yet.", |
|
|
"review": "Review required.", |
|
|
}) |
|
|
|
|
|
handler = self.agent_workflow.run(user_msg=user_msg, ctx=ctx) |
|
|
|
|
|
current_agent = None |
|
|
async for event in handler.stream_events(): |
|
|
if ( |
|
|
hasattr(event, "current_agent_name") |
|
|
and event.current_agent_name != current_agent |
|
|
): |
|
|
current_agent = event.current_agent_name |
|
|
print(f"\n{'='*50}") |
|
|
print(f"🤖 Agent: {current_agent}") |
|
|
print(f"{'='*50}\n") |
|
|
|
|
|
if hasattr(event, "response") and hasattr(event.response, "content"): |
|
|
if event.response.content: |
|
|
print("📤 Output:", event.response.content) |
|
|
if hasattr(event, "tool_calls") and event.tool_calls: |
|
|
print( |
|
|
"🛠️ Planning to use tools:", |
|
|
[call.tool_name for call in event.tool_calls], |
|
|
) |
|
|
elif hasattr(event, "tool_name") and hasattr(event, "tool_output"): |
|
|
print(f"🔧 Tool Result ({event.tool_name}):") |
|
|
print(f" Arguments: {getattr(event, 'tool_kwargs', {})}") |
|
|
print(f" Output: {event.tool_output}") |
|
|
elif hasattr(event, "tool_name") and hasattr(event, "tool_kwargs"): |
|
|
print(f"🔨 Calling Tool: {event.tool_name}") |
|
|
print(f" With arguments: {event.tool_kwargs}") |
|
|
|
|
|
|
|
|
final_state = self.get_final_state() |
|
|
print(f"\n📊 Final State:") |
|
|
print(f"Research notes: {len(final_state.get('research_notes', {}))}") |
|
|
print(f"Report written: {final_state.get('report_content', 'Not written') != 'Not written yet.'}") |
|
|
print(f"Review: {final_state.get('review', 'No review')[:100]}...") |
|
|
|
|
|
if final_state.get("structured_report"): |
|
|
print("\n📄 Final Report Generated Successfully!") |
|
|
report = final_state["structured_report"] |
|
|
print(f"Title: {report['title']}") |
|
|
print(f"Word count: {report['word_count']}") |
|
|
print(f"Sections: {len(report['sections'])}") |
|
|
else: |
|
|
print("\n⚠️ No final report was generated by the workflow.") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
import asyncio |
|
|
agent = LlamaIndexReportAgent() |
|
|
user_msg = input("Enter the topic or instructions for the report (leave blank for default): ").strip() |
|
|
if not user_msg: |
|
|
user_msg = None |
|
|
asyncio.run(agent.run_workflow(user_msg=user_msg)) |