chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:18 +08:00
commit 6d5d58c1a9
18293 changed files with 3502153 additions and 0 deletions
@@ -0,0 +1,232 @@
"""
Analysis Agent - Analyzes research findings using ADK + Gemini.
Exposes A2A Protocol endpoint, returns structured JSON.
"""
import uvicorn
import os
import json
from typing import List
from dotenv import load_dotenv
from pydantic import BaseModel, Field
load_dotenv()
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
AgentCapabilities,
AgentCard,
AgentSkill,
)
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.utils import new_agent_text_message
from google.adk.agents.llm_agent import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.artifacts import InMemoryArtifactService
from google.genai import types
class InsightItem(BaseModel):
title: str = Field(description="Title of the insight")
description: str = Field(description="Detailed description of the insight")
importance: str = Field(description="Why this insight matters")
class StructuredAnalysis(BaseModel):
topic: str = Field(description="The topic being analyzed")
overview: str = Field(description="Brief overview of the analysis")
insights: List[InsightItem] = Field(description="List of key insights")
conclusion: str = Field(description="Concluding thoughts")
class AnalysisAgent:
def __init__(self):
self._agent = self._build_agent()
self._user_id = "remote_agent"
self._runner = Runner(
app_name=self._agent.name,
agent=self._agent,
artifact_service=InMemoryArtifactService(),
session_service=InMemorySessionService(),
memory_service=InMemoryMemoryService(),
)
def _build_agent(self) -> LlmAgent:
model_name = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
return LlmAgent(
model=model_name,
name="analysis_agent",
description="An agent that analyzes research findings and provides insights",
instruction="""
You are an analysis agent. Your role is to analyze research findings and provide meaningful insights.
When you receive research data, analyze it thoroughly and create an insightful analysis.
Return ONLY a valid JSON object with this exact structure:
{
"topic": "The topic being analyzed",
"overview": "A brief 2-3 sentence overview of the analysis",
"insights": [
{
"title": "Key Insight 1",
"description": "Detailed explanation of this insight",
"importance": "Why this matters"
},
{
"title": "Key Insight 2",
"description": "Detailed explanation of this insight",
"importance": "Why this matters"
},
{
"title": "Key Insight 3",
"description": "Detailed explanation of this insight",
"importance": "Why this matters"
}
],
"conclusion": "Concluding thoughts and recommendations"
}
Provide 3-5 meaningful insights based on the research.
Make the analysis thoughtful and actionable.
Return ONLY valid JSON, no markdown code blocks, no other text.
""",
tools=[],
)
async def invoke(self, query: str, session_id: str) -> str:
"""Generate analysis and return JSON string."""
session = await self._runner.session_service.get_session(
app_name=self._agent.name,
user_id=self._user_id,
session_id=session_id,
)
content = types.Content(role="user", parts=[types.Part.from_text(text=query)])
if session is None:
session = await self._runner.session_service.create_session(
app_name=self._agent.name,
user_id=self._user_id,
state={},
session_id=session_id,
)
response_text = ""
async for event in self._runner.run_async(
user_id=self._user_id, session_id=session.id, new_message=content
):
if event.is_final_response():
if (
event.content
and event.content.parts
and event.content.parts[0].text
):
response_text = "\n".join(
[p.text for p in event.content.parts if p.text]
)
break
content_str = response_text.strip()
if "```json" in content_str:
content_str = content_str.split("```json")[1].split("```")[0].strip()
elif "```" in content_str:
content_str = content_str.split("```")[1].split("```")[0].strip()
try:
structured_data = json.loads(content_str)
validated_analysis = StructuredAnalysis(**structured_data)
final_response = json.dumps(validated_analysis.model_dump(), indent=2)
print("✅ Successfully created structured analysis")
return final_response
except json.JSONDecodeError as e:
print(f"❌ JSON parsing error: {e}")
print(f"Content: {content_str}")
return json.dumps(
{
"error": "Failed to generate structured analysis",
"raw_content": content_str[:200],
}
)
except Exception as e:
print(f"❌ Validation error: {e}")
return json.dumps({"error": f"Validation failed: {str(e)}"})
# A2A Protocol executor wraps the ADK agent
class AnalysisAgentExecutor(AgentExecutor):
def __init__(self):
self.agent = AnalysisAgent()
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
query = context.get_user_input()
session_id = getattr(context, "context_id", "default_session")
final_content = await self.agent.invoke(query, session_id)
await event_queue.enqueue_event(new_agent_text_message(final_content))
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
raise Exception("cancel not supported")
port = int(os.getenv("ANALYSIS_PORT", 9002))
skill = AgentSkill(
id="analysis_agent",
name="Analysis Agent",
description="Analyzes research findings and provides meaningful insights using ADK",
tags=["research", "analysis", "insights", "adk"],
examples=[
"Analyze this research about quantum computing",
"What are the key insights from this data?",
"Provide analysis of these research findings",
],
)
public_agent_card = AgentCard(
name="Analysis Agent",
description="ADK-powered agent that analyzes research findings and provides meaningful insights",
url=f"http://localhost:{port}/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[skill],
supportsAuthenticatedExtendedCard=False,
)
def main():
if not os.getenv("GOOGLE_API_KEY") and not os.getenv("GEMINI_API_KEY"):
print("⚠️ Warning: No API key found!")
print(" Set GOOGLE_API_KEY or GEMINI_API_KEY")
print(" Get a key from: https://aistudio.google.com/app/apikey")
print()
request_handler = DefaultRequestHandler(
agent_executor=AnalysisAgentExecutor(),
task_store=InMemoryTaskStore(),
)
server = A2AStarletteApplication(
agent_card=public_agent_card,
http_handler=request_handler,
extended_agent_card=public_agent_card,
)
print(f"💡 Starting Analysis Agent (ADK + A2A) on http://localhost:{port}")
print(f" Agent: {public_agent_card.name}")
print(f" Description: {public_agent_card.description}")
uvicorn.run(server.build(), host="0.0.0.0", port=port)
if __name__ == "__main__":
main()
@@ -0,0 +1,86 @@
"""
Orchestrator Agent - Coordinates between Research and Analysis agents.
Speaks AG-UI Protocol to the UI, delegates tasks to A2A agents via middleware.
"""
from __future__ import annotations
from dotenv import load_dotenv
load_dotenv()
import os
import uvicorn
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
from google.adk.agents import LlmAgent
orchestrator_agent = LlmAgent(
name="OrchestratorAgent",
model="gemini-2.5-pro",
instruction="""
You are an orchestrator agent that coordinates research and analysis tasks.
AVAILABLE SPECIALIZED AGENTS:
1. **Research Agent** (LangGraph) - Gathers and summarizes information about a topic
2. **Analysis Agent** (ADK) - Analyzes research findings and provides insights
CRITICAL CONSTRAINTS:
- You MUST call agents ONE AT A TIME, never make multiple tool calls simultaneously
- After making a tool call, WAIT for the result before making another tool call
- Do NOT make parallel/concurrent tool calls - this is not supported
WORKFLOW FOR RESEARCH TASKS:
When the user asks to research a topic:
1. **Research Agent** - First, gather information about the topic
- Pass: The user's research query or topic
- Wait for structured JSON response with research findings
2. **Analysis Agent** - Then, analyze the research results
- Pass: The research results from step 1
- Wait for structured JSON with analysis and insights
3. Present the complete research and analysis to the user
IMPORTANT WORKFLOW DETAILS:
- Always call the Research Agent first to gather information
- Then call the Analysis Agent to analyze the findings
- Wait for each agent to complete before calling the next one
- Build your final response using information from both agents
RESPONSE STRATEGY:
- After each agent response, briefly acknowledge what you received
- Build up the complete answer incrementally
- At the end, present a well-organized summary
- Don't just list agent responses - synthesize them into a cohesive answer
IMPORTANT: Once you have received a response from an agent, do NOT call that same
agent again for the same information. Use the information you already have.
""",
)
# Wrap with AG-UI middleware to expose via AG-UI Protocol
adk_orchestrator_agent = ADKAgent(
adk_agent=orchestrator_agent,
app_name="orchestrator_app",
user_id="demo_user",
session_timeout_seconds=3600,
use_in_memory_services=True,
)
app = FastAPI(title="A2A Orchestrator (ADK + AG-UI Protocol)")
add_adk_fastapi_endpoint(app, adk_orchestrator_agent, path="/")
if __name__ == "__main__":
if not os.getenv("GOOGLE_API_KEY"):
print("⚠️ Warning: GOOGLE_API_KEY not set!")
print(" Set it with: export GOOGLE_API_KEY='your-key-here'")
print(" Get a key from: https://aistudio.google.com/app/apikey")
print()
port = int(os.getenv("ORCHESTRATOR_PORT", 9000))
print(f"🚀 Starting Orchestrator Agent (ADK + AG-UI) on http://localhost:{port}")
uvicorn.run(app, host="0.0.0.0", port=port)
@@ -0,0 +1,62 @@
# ============================================================================
# A2A + AG-UI Starter - Python Agent Dependencies
# ============================================================================
# ============================================================================
# AG-UI Protocol Implementation
# ============================================================================
# ag-ui-adk: Allows ADK agents to communicate with the frontend via AG-UI Protocol
# This is used by the orchestrator agent to receive messages from the UI
ag-ui-adk>=0.0.1
# ============================================================================
# A2A Protocol Implementation
# ============================================================================
# a2a: Core A2A Protocol SDK for agent-to-agent communication
# a2a-sdk: Additional utilities including HTTP server support
# These packages enable the Research and Analysis agents to communicate via A2A
a2a>=0.1.0
a2a-sdk[http-server]
# ============================================================================
# Google ADK (Agent Development Kit)
# ============================================================================
# google-adk: Google's framework for building AI agents with Gemini models
# Used by: Orchestrator Agent, Analysis Agent
# litellm: Unified interface for multiple LLM providers (dependency of ADK)
google-adk>=0.1.0
litellm>=1.0.0
# ============================================================================
# LangGraph Framework
# ============================================================================
# langgraph: LangChain's framework for building stateful agent workflows
# langchain: Core LangChain library for LLM applications
# langchain-openai: OpenAI integration for LangChain
# Used by: Research Agent
langgraph>=0.2.0
langchain>=0.3.0
langchain-openai>=0.2.0
# ============================================================================
# Web Server
# ============================================================================
# fastapi: Modern Python web framework for building agent HTTP endpoints
# uvicorn: ASGI server for running FastAPI applications
# All agents expose HTTP endpoints using FastAPI
fastapi>=0.115.0
uvicorn>=0.30.0
# ============================================================================
# Utilities
# ============================================================================
# python-dotenv: Loads environment variables from .env file
# Used to configure API keys (GOOGLE_API_KEY, OPENAI_API_KEY)
python-dotenv>=1.0.0
# ============================================================================
# LLM Providers
# ============================================================================
# openai: OpenAI Python SDK for GPT models
# Used by the Research Agent (LangGraph)
openai>=1.0.0
@@ -0,0 +1,180 @@
"""
Research Agent - Gathers information using LangGraph + OpenAI.
Exposes A2A Protocol endpoint, returns structured JSON.
"""
import uvicorn
import json
import os
from dotenv import load_dotenv
load_dotenv()
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Message
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.utils import new_agent_text_message
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, Optional, List
from pydantic import BaseModel, Field
class ResearchFinding(BaseModel):
title: str = Field(description="Title or key point of the finding")
description: str = Field(description="Detailed description of the finding")
class StructuredResearch(BaseModel):
topic: str = Field(description="The research topic")
summary: str = Field(description="Brief summary of the research")
findings: List[ResearchFinding] = Field(description="List of key findings")
sources: str = Field(description="Note about information sources")
class ResearchState(TypedDict):
message: str
research: str
structured_research: Optional[dict]
class ResearchAgent:
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
self.graph = self._build_graph()
def _build_graph(self):
workflow = StateGraph(ResearchState)
workflow.add_node("conduct_research", self._conduct_research)
workflow.set_entry_point("conduct_research")
workflow.add_edge("conduct_research", END)
return workflow.compile()
def _conduct_research(self, state: ResearchState) -> ResearchState:
"""Generate research findings using LLM and return structured JSON."""
message = state["message"]
prompt = f"""
Research the following topic and provide comprehensive information.
Topic: {message}
Return ONLY a valid JSON object with this exact structure:
{{
"topic": "The research topic",
"summary": "A brief 2-3 sentence summary of the topic",
"findings": [
{{
"title": "Key Point 1",
"description": "Detailed explanation of this point"
}},
{{
"title": "Key Point 2",
"description": "Detailed explanation of this point"
}},
{{
"title": "Key Point 3",
"description": "Detailed explanation of this point"
}}
],
"sources": "Note about where this information typically comes from"
}}
Include 3-5 key findings about the topic.
Make the research informative and well-structured.
Return ONLY valid JSON, no markdown code blocks, no other text.
"""
response = self.llm.invoke(prompt)
try:
structured_data = json.loads(response.content)
state["structured_research"] = structured_data
state["research"] = json.dumps(structured_data)
except json.JSONDecodeError as e:
state["research"] = f"Error: Failed to parse research results - {str(e)}"
state["structured_research"] = None
return state
async def invoke(self, message: Message) -> str:
"""Process A2A message and return research JSON."""
message_text = message.parts[0].root.text
result = self.graph.invoke(
{"message": message_text, "research": "", "structured_research": None}
)
return result["research"]
# A2A Protocol executor wraps the LangGraph agent
class ResearchAgentExecutor(AgentExecutor):
def __init__(self):
self.agent = ResearchAgent()
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
result = await self.agent.invoke(context.message)
await event_queue.enqueue_event(new_agent_text_message(result))
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
raise Exception("cancel not supported")
port = int(os.getenv("RESEARCH_PORT", 9001))
skill = AgentSkill(
id="research_agent",
name="Research Agent",
description="Gathers and summarizes information about a given topic using LangGraph",
tags=["research", "information", "summary", "langgraph"],
examples=[
"Research quantum computing",
"Tell me about artificial intelligence",
"Gather information on renewable energy",
],
)
public_agent_card = AgentCard(
name="Research Agent",
description="LangGraph-powered agent that gathers and summarizes information about any topic",
url=f"http://localhost:{port}/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[skill],
supportsAuthenticatedExtendedCard=False,
)
def main():
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ Warning: OPENAI_API_KEY not set!")
print(" Set it with: export OPENAI_API_KEY='your-key-here'")
print(" Get a key from: https://platform.openai.com/api-keys")
print()
request_handler = DefaultRequestHandler(
agent_executor=ResearchAgentExecutor(),
task_store=InMemoryTaskStore(),
)
server = A2AStarletteApplication(
agent_card=public_agent_card,
http_handler=request_handler,
extended_agent_card=public_agent_card,
)
print(f"🔍 Starting Research Agent (LangGraph + A2A) on http://localhost:{port}")
print(f" Agent: {public_agent_card.name}")
print(f" Description: {public_agent_card.description}")
uvicorn.run(server.build(), host="0.0.0.0", port=port)
if __name__ == "__main__":
main()