ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
@@ -0,0 +1,2 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2025 inclusionAI.
|
||||
@@ -0,0 +1,224 @@
|
||||
import json
|
||||
import logging
|
||||
from mailbox import Message
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any, AsyncGenerator, Dict, List
|
||||
|
||||
from aworld.cmd.utils.agent_ui_parser import AWorldWebAgentUI
|
||||
from aworld.core.common import ActionModel, Observation
|
||||
from aworld.core.context.base import Context
|
||||
from aworld.memory.models import MemorySystemMessage, MessageMetadata
|
||||
from aworld.output.base import MessageOutput
|
||||
from aworld.output.ui.base import AworldUI
|
||||
from aworld.output.workspace import WorkSpace
|
||||
from examples.multi_agents.coordination.deepresearch.planner.plan import PlannerOutputParser
|
||||
|
||||
from aworld.core.agent.swarm import TeamSwarm
|
||||
from aworld.cmd.data_model import BaseAWorldAgent, ChatCompletionRequest
|
||||
from aworld.config.conf import AgentConfig, ModelConfig, TaskConfig
|
||||
from aworld.agents.llm_agent import Agent
|
||||
from aworld.core.task import Task
|
||||
from aworld.runner import Runners
|
||||
from examples.common.tools.common import Tools
|
||||
from .prompts import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseDynamicPromptAgent(Agent):
|
||||
async def async_policy(
|
||||
self,
|
||||
observation: Observation,
|
||||
info: Dict[str, Any] = {},
|
||||
message: Message = None,
|
||||
**kwargs,
|
||||
) -> List[ActionModel]:
|
||||
return await super().async_policy(observation, info, message, **kwargs)
|
||||
|
||||
# multi turn system prompt generation
|
||||
async def _add_system_message_to_memory(self, context: Context, content: str):
|
||||
session_id = context.get_task().session_id
|
||||
task_id = context.get_task().id
|
||||
user_id = context.get_task().user_id
|
||||
|
||||
if not self.system_prompt:
|
||||
return
|
||||
content = self.system_prompt_template.format(context=context, task=content)
|
||||
# logger.info(f"system prompt content: {content}")
|
||||
|
||||
await self.memory.add(
|
||||
MemorySystemMessage(
|
||||
content=content,
|
||||
metadata=MessageMetadata(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
task_id=task_id,
|
||||
agent_id=self.id(),
|
||||
agent_name=self.name(),
|
||||
),
|
||||
),
|
||||
agent_memory_config=self.memory_config,
|
||||
)
|
||||
logger.info(
|
||||
f"🧠 [MEMORY:short-term] Added system input to agent memory: Agent#{self.id()}, 💬 {content[:100]}..."
|
||||
)
|
||||
|
||||
|
||||
class PlanAgent(BaseDynamicPromptAgent):
|
||||
pass
|
||||
|
||||
|
||||
class ReportingAgent(BaseDynamicPromptAgent):
|
||||
pass
|
||||
|
||||
|
||||
def get_deepresearch_swarm(user_input):
|
||||
|
||||
agent_config = AgentConfig(
|
||||
llm_config=ModelConfig(
|
||||
llm_provider=os.getenv("LLM_MODEL_PROVIDER_DEEPRESEARCH", "openai"),
|
||||
llm_model_name=os.getenv("LLM_MODEL_NAME_DEEPRESEARCH"),
|
||||
llm_base_url=os.getenv("LLM_BASE_URL_DEEPRESEARCH"),
|
||||
llm_api_key=os.getenv("LLM_API_KEY_DEEPRESEARCH"),
|
||||
),
|
||||
use_vision=False,
|
||||
)
|
||||
|
||||
agent_id = "🧠 DeepResearchPlanAgent"
|
||||
plan_agent = PlanAgent(
|
||||
agent_id=agent_id,
|
||||
name=agent_id,
|
||||
desc=agent_id,
|
||||
conf=agent_config,
|
||||
use_tools_in_prompt=True,
|
||||
model_output_parser=PlannerOutputParser(agent_id),
|
||||
system_prompt_template=plan_sys_prompt,
|
||||
)
|
||||
|
||||
web_search_agent = Agent(
|
||||
name="🔎 WebSearchAgent",
|
||||
desc="🔎 WebSearchAgent",
|
||||
conf=agent_config,
|
||||
system_prompt_template=search_sys_prompt,
|
||||
tool_names=[Tools.SEARCH_API.value],
|
||||
)
|
||||
|
||||
reporting_agent = Agent(
|
||||
name="📝 ReportingAgent",
|
||||
desc="📝 ReportingAgent",
|
||||
conf=agent_config,
|
||||
system_prompt_template=reporting_sys_prompt,
|
||||
)
|
||||
|
||||
return TeamSwarm(plan_agent, web_search_agent, reporting_agent, max_steps=1)
|
||||
|
||||
|
||||
class DeepResearchAgentWebUI(AWorldWebAgentUI):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def message_output(self, output: MessageOutput):
|
||||
content = ""
|
||||
try:
|
||||
if (
|
||||
hasattr(output, "response")
|
||||
and "<FINAL_ANSWER_TAG>" in output.response
|
||||
and "</FINAL_ANSWER_TAG>" in output.response
|
||||
):
|
||||
try:
|
||||
content = (
|
||||
output.response.split("<FINAL_ANSWER_TAG>")[1]
|
||||
.split("</FINAL_ANSWER_TAG>")[0]
|
||||
.strip()
|
||||
)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing output: {traceback.format_exc()}")
|
||||
|
||||
step_info = ""
|
||||
try:
|
||||
if (
|
||||
"<PLANNING_TAG>" in output.response
|
||||
and "</PLANNING_TAG>" in output.response
|
||||
):
|
||||
try:
|
||||
planning = (
|
||||
output.response.split("<PLANNING_TAG>")[1]
|
||||
.split("</PLANNING_TAG>")[0]
|
||||
.strip()
|
||||
)
|
||||
plan = json.loads(planning)
|
||||
steps = plan.get("steps")
|
||||
dags = plan.get("dag")
|
||||
for i, dag in enumerate(dags):
|
||||
if isinstance(dag, list):
|
||||
for sub_i, sub_dag in enumerate(dag):
|
||||
sub_step = steps.get(sub_dag)
|
||||
sub_step_id = sub_step.get("id")
|
||||
sub_step_input = sub_step.get("input")
|
||||
step_info += f" - STEP {i+1}.{sub_i+1}: {sub_step_input} *@{sub_step_id}*\n"
|
||||
else:
|
||||
step = steps.get(dag)
|
||||
step_id = step.get("id")
|
||||
step_input = step.get("input")
|
||||
step_info += f" - STEP {i+1}: {step_input} *@{step_id}*\n"
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing output: {traceback.format_exc()}")
|
||||
|
||||
if content and step_info:
|
||||
return f"\n\n{content}\n\n**Execution Steps:**\n{step_info}\n"
|
||||
elif step_info:
|
||||
return f"\n\n**Execution Steps:**\n{step_info}\n"
|
||||
elif content:
|
||||
return f"\n\n{content}\n"
|
||||
|
||||
return await super().message_output(output)
|
||||
|
||||
|
||||
class AWorldAgent(BaseAWorldAgent):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def name(self):
|
||||
return "Deep Research[PlanAgent]"
|
||||
|
||||
def description(self):
|
||||
return "Deep Research Agent with PlanAgent"
|
||||
|
||||
async def run(self, prompt: str = None, request: ChatCompletionRequest = None):
|
||||
|
||||
if prompt is None and request is not None:
|
||||
prompt = request.messages[-1].content
|
||||
|
||||
swarm = get_deepresearch_swarm(prompt)
|
||||
|
||||
task = Task(
|
||||
input=prompt,
|
||||
swarm=swarm,
|
||||
conf=TaskConfig(max_steps=20),
|
||||
session_id=request.session_id,
|
||||
endless_threshold=50,
|
||||
)
|
||||
|
||||
rich_ui = DeepResearchAgentWebUI(
|
||||
session_id=request.session_id,
|
||||
workspace=WorkSpace.from_local_storages(workspace_id=request.session_id),
|
||||
)
|
||||
async for output in Runners.streamed_run_task(task).stream_events():
|
||||
logger.info(f"Agent Ouput: {output}")
|
||||
try:
|
||||
res = await AworldUI.parse_output(output, rich_ui)
|
||||
for item in res if isinstance(res, list) else [res]:
|
||||
if isinstance(item, AsyncGenerator):
|
||||
async for sub_item in item:
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
except Exception as e:
|
||||
msg = f"Error parsing output: {traceback.format_exc()}"
|
||||
logger.error(msg)
|
||||
yield msg
|
||||
@@ -0,0 +1,286 @@
|
||||
parallel_plan_sys_prompt = """## Task
|
||||
You are an information search expert. Your goal is to maximize the retrieval of effective information through search task planning and retrieval. Please plan the necessary search and processing steps to solve the problem based on the user's question and background information.
|
||||
|
||||
## Problem Analysis and Search Strategy Planning
|
||||
- Break down complex user questions into multi-step or single-step search plans. Ensure all search plans are **complete and executable**.
|
||||
- Use step-by-step searching. For high-complexity problems, break them down into multiple sequential execution steps.
|
||||
- When planning, prioritize strategy breadth (coverage). Start with broad searches, then refine strategies based on search results.
|
||||
- Typically limit to no more than 5 steps.
|
||||
|
||||
## Search Strategy Key Points
|
||||
- Source Reasoning: Trace user queries to their sources, especially focusing on official websites and officially published information.
|
||||
- Multiple Intent Breakdown: If user input contains multiple intentions or meanings, break it down into independently searchable queries.
|
||||
- Information Completion:
|
||||
- Supplement omitted or implied information in user questions
|
||||
- Replace pronouns with specific entities based on context
|
||||
- Time Conversion: The current date is {{current_date}}. Convert relative time expressions in user input to specific dates or date ranges.
|
||||
- Semantic Completeness: Ensure each query is semantically clear and complete for precise search engine results.
|
||||
- Bilingual Search: Many data sources require English searches, so provide corresponding English information.
|
||||
|
||||
## Important Output Format Requirements (MUST STRICTLY FOLLOW):
|
||||
1. BOTH tags (<PLANNING_TAG> and <FINAL_ANSWER_TAG>) MUST be present
|
||||
2. The JSON inside <PLANNING_TAG> MUST be valid and properly formatted
|
||||
3. Inside <PLANNING_TAG>:
|
||||
- The "steps" object MUST contain numbered steps (agent_step_1, agent_step_2, etc.)
|
||||
- Each step MUST have both "input" and "id" fields
|
||||
- The "dag" array MUST define execution order using step IDs
|
||||
- Parallel steps MUST be grouped in nested arrays
|
||||
4. DO NOT include any explanatory text between the two tag sections
|
||||
5. DO NOT modify or change the tag names
|
||||
6. If no further planning is needed, output an empty <PLANNING_TAG> section but STILL include <FINAL_ANSWER_TAG> with explanation
|
||||
|
||||
## Example:
|
||||
Topic: Analyze the development trends and main challenges of China's New Energy Vehicle (NEV) market in 2024
|
||||
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {
|
||||
"agent_step_1": {
|
||||
"input": "Search for 2024 China NEV market policy updates and industry forecasts",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_2": {
|
||||
"input": "Search for major challenges and bottlenecks in China's NEV industry development",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_3": {
|
||||
"input": "Analyze market trends based on gathered data and synthesize findings",
|
||||
"id": "analysis_tool"
|
||||
}
|
||||
},
|
||||
"dag": [["agent_step_1", "agent_step_2"], "agent_step_3"]
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Based on the planned analysis steps, we will be able to provide a comprehensive overview of China's NEV market development trends and challenges in 2024, incorporating both policy updates and industry insights.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
Topic: Research the latest developments in Large Language Models (LLMs) and their impact on the AI industry in the past 6 months
|
||||
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {
|
||||
"agent_step_1": {
|
||||
"input": "Search for major LLM releases and technical breakthroughs in the last 6 months",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_2": {
|
||||
"input": "Search for industry applications and commercial implementations of new LLM technologies",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_3": {
|
||||
"input": "Search for academic papers and research findings about LLM improvements",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_4": {
|
||||
"input": "Synthesize findings to analyze trends and impact on AI industry",
|
||||
"id": "analysis_tool"
|
||||
}
|
||||
},
|
||||
"dag": [["agent_step_1", "agent_step_2", "agent_step_3"], "agent_step_4"]
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Based on the planned research steps, we will compile a comprehensive analysis of recent LLM developments, including technical advances, practical applications, and their broader impact on the AI industry landscape.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
Topic: Compare the sustainability initiatives and environmental impact of major tech companies (Apple, Google, Microsoft) in their data centers
|
||||
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {
|
||||
"agent_step_1": {
|
||||
"input": "Search for official environmental reports and sustainability commitments from Apple, Google, and Microsoft",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_2": {
|
||||
"input": "Search for third-party assessments and environmental impact studies of tech companies' data centers",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_3": {
|
||||
"input": "Search for specific green initiatives and renewable energy projects by these companies",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_4": {
|
||||
"input": "Search for comparative analysis of environmental metrics and carbon footprint data",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_5": {
|
||||
"input": "Compile and compare findings to create a comprehensive comparison",
|
||||
"id": "analysis_tool"
|
||||
}
|
||||
},
|
||||
"dag": [["agent_step_1", "agent_step_2"], ["agent_step_3", "agent_step_4"], "agent_step_5"]
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Based on the planned analysis steps, we will provide a detailed comparison of sustainability initiatives and environmental impact across major tech companies, focusing on their data center operations and overall environmental commitments.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
Topic: No further research needed as all required information has been collected
|
||||
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {},
|
||||
"dag": []
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Based on the comprehensive information already collected in previous steps, no additional research is needed. We can proceed with synthesizing the existing findings.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
## Research Topic
|
||||
{{task}}"""
|
||||
|
||||
parallel_replan_sys_prompt = (
|
||||
parallel_plan_sys_prompt
|
||||
+ """
|
||||
|
||||
## Trajectories
|
||||
{{trajectories}}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
plan_sys_prompt = """\
|
||||
You are an expert research planner. Your task is to decompose user questions, plan efficient search strategies, and validate completeness.
|
||||
|
||||
## Core Responsibilities
|
||||
1. **Problem Decomposition**: Break complex questions into clear, executable search steps
|
||||
2. **Strategic Planning**: Design comprehensive research workflows (max 3 steps)
|
||||
3. **Completeness Validation**: Assess if current information fully answers the user's question
|
||||
|
||||
## Planning Guidelines
|
||||
- Start with broad searches, then narrow to specific details
|
||||
- Prioritize authoritative sources and recent information
|
||||
- Convert relative dates using current date: {{current_date}}
|
||||
- Ensure each search query is clear, complete, and semantically meaningful
|
||||
- Limit to maximum 2 search steps for efficiency
|
||||
|
||||
## CRITICAL: Output Format Requirements
|
||||
**MANDATORY**: Always output BOTH tags in exact format below:
|
||||
|
||||
**If planning needed:**
|
||||
```
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {
|
||||
"agent_step_1": {
|
||||
"input": "Specific search query here",
|
||||
"id": "tool_name_from_available_tools"
|
||||
},
|
||||
"agent_step_2": {
|
||||
"input": "Next search query here",
|
||||
"id": "tool_name_from_available_tools"
|
||||
}
|
||||
},
|
||||
"dag": ["agent_step_1", "agent_step_2"]
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Brief description of what these steps will accomplish and what information gaps they'll fill.
|
||||
</FINAL_ANSWER_TAG>
|
||||
```
|
||||
|
||||
**If no planning needed (question fully answered):**
|
||||
```
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {},
|
||||
"dag": []
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
Brief summary confirming the question is fully answered with current information.
|
||||
</FINAL_ANSWER_TAG>
|
||||
```
|
||||
|
||||
## Format Validation Checklist
|
||||
✓ Both tags present and correctly named
|
||||
✓ Valid JSON structure inside PLANNING_TAG
|
||||
✓ Each step has "input" and "id" fields
|
||||
✓ Tool IDs match available tools list
|
||||
✓ DAG array defines execution order
|
||||
✓ No text between tag sections
|
||||
|
||||
## Examples
|
||||
|
||||
**Research Planning Example:**
|
||||
Topic: Analyze China's NEV market trends in 2024
|
||||
|
||||
<PLANNING_TAG>
|
||||
{
|
||||
"steps": {
|
||||
"agent_step_1": {
|
||||
"input": "2024 China NEV market policy updates and growth forecasts",
|
||||
"id": "search_tool"
|
||||
},
|
||||
"agent_step_2": {
|
||||
"input": "Major challenges facing China NEV industry development 2024",
|
||||
"id": "search_tool"
|
||||
}
|
||||
},
|
||||
"dag": ["agent_step_1", "agent_step_2"]
|
||||
}
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
These searches will provide current policy landscape and industry challenges to deliver a comprehensive analysis of China's NEV market trends and obstacles in 2024.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
**No Further Planning Example:**
|
||||
Topic: Question already fully answered
|
||||
|
||||
<PLANNING_TAG>
|
||||
</PLANNING_TAG>
|
||||
|
||||
<FINAL_ANSWER_TAG>
|
||||
All required information has been gathered from previous research steps. Ready to synthesize findings.
|
||||
</FINAL_ANSWER_TAG>
|
||||
|
||||
## Available Tools
|
||||
{{tool_list}}
|
||||
|
||||
## Research Topic
|
||||
{{task}}
|
||||
|
||||
## Trajectories
|
||||
{{trajectories}}"""
|
||||
|
||||
|
||||
search_sys_prompt = """Conduct targeted aworld_search tools to gather the most recent, credible information on "{{task}}" and synthesize it into a verifiable text artifact.
|
||||
|
||||
Instructions:
|
||||
- Query should ensure that the most current information is gathered. The current date is {{current_date}}.
|
||||
- Conduct multiple, diverse searches to gather comprehensive information.
|
||||
- Consolidate key findings while meticulously tracking the source(s) for each specific piece of information.
|
||||
- The output should be a well-written summary or report based on your search findings.
|
||||
- Only include the information found in the search results, don't make up any information.
|
||||
- Generate output in English
|
||||
- Search tool accepts one parameter and returns one result
|
||||
Research Topic:
|
||||
{{task}}
|
||||
"""
|
||||
|
||||
|
||||
reporting_sys_prompt = """Generate a high-quality answer to the user's question based on the provided summaries.
|
||||
|
||||
Instructions:
|
||||
- The current date is {{current_date}}.
|
||||
- You are the final step of a multi-step research process, don't mention that you are the final step.
|
||||
- You have access to all the information gathered from the previous steps.
|
||||
- You have access to the user's question.
|
||||
- Generate a high-quality answer to the user's question based on the provided summaries and the user's question.
|
||||
- you MUST include all the citations from the summaries in the answer correctly.
|
||||
- Format the output using Markdown structure
|
||||
User Context:
|
||||
- {{task}}
|
||||
|
||||
Summaries:
|
||||
{{trajectories}}"""
|
||||
@@ -0,0 +1,74 @@
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from aworld.cmd.data_model import BaseAWorldAgent, ChatCompletionRequest
|
||||
from aworld.config.conf import AgentConfig, TaskConfig
|
||||
from aworld.agents.llm_agent import Agent
|
||||
from aworld.core.task import Task
|
||||
from aworld.runner import Runners
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AWorldAgent(BaseAWorldAgent):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def name(self):
|
||||
return "Single Agent Demo"
|
||||
|
||||
def description(self):
|
||||
return "Single Agent Demo with search and playwright fetch ability"
|
||||
|
||||
async def run(self, prompt: str = None, request: ChatCompletionRequest = None):
|
||||
llm_provider = os.getenv("LLM_PROVIDER_DEMO", "openai")
|
||||
llm_model_name = os.getenv("LLM_MODEL_NAME_DEMO")
|
||||
llm_api_key = os.getenv("LLM_API_KEY_DEMO")
|
||||
llm_base_url = os.getenv("LLM_BASE_URL_DEMO")
|
||||
llm_temperature = os.getenv("LLM_TEMPERATURE_DEMO", 0.0)
|
||||
|
||||
if not llm_model_name or not llm_api_key or not llm_base_url:
|
||||
raise ValueError(
|
||||
"LLM_MODEL_NAME, LLM_API_KEY, LLM_BASE_URL must be set in your envrionment variables"
|
||||
)
|
||||
|
||||
agent_config = AgentConfig(
|
||||
llm_provider=llm_provider,
|
||||
llm_model_name=llm_model_name,
|
||||
llm_api_key=llm_api_key,
|
||||
llm_base_url=llm_base_url,
|
||||
llm_temperature=llm_temperature,
|
||||
)
|
||||
|
||||
path_cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
mcp_path = os.path.join(path_cwd, "mcp.json")
|
||||
with open(mcp_path, "r") as f:
|
||||
mcp_config = json.load(f)
|
||||
|
||||
super_agent = Agent(
|
||||
conf=agent_config,
|
||||
name="🙋🏻♂️ Single Agent Demo",
|
||||
system_prompt="""You are a Super Search Agent, your goal is to accomplish the ultimate task following the instructions.""",
|
||||
mcp_config=mcp_config,
|
||||
# mcp_servers=mcp_config.get("mcpServers", {}).keys(),
|
||||
# mcp_servers=["aworldsearch-server", "aworld-playwright"],
|
||||
mcp_servers=["google-pse-search", "aworld-playwright"],
|
||||
feedback_tool_result=True,
|
||||
)
|
||||
|
||||
if prompt is None and request is not None:
|
||||
prompt = request.messages[-1].content
|
||||
|
||||
task = Task(
|
||||
input=prompt,
|
||||
agent=super_agent,
|
||||
conf=TaskConfig(max_steps=20),
|
||||
session_id=request.session_id,
|
||||
endless_threshold=50,
|
||||
)
|
||||
|
||||
with open("data/output.txt", "w") as f:
|
||||
f.write(f"AGENT START: agent={self.name()}, prompt: {prompt}\n")
|
||||
async for output in Runners.streamed_run_task(task).stream_events():
|
||||
f.write(f"Agent {self.name()} received output: {output}\n")
|
||||
yield output
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"google-pse-search": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@adenot/mcp-google-search"
|
||||
],
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "${GOOGLE_API_KEY}",
|
||||
"GOOGLE_SEARCH_ENGINE_ID": "${GOOGLE_SEARCH_ENGINE_ID}"
|
||||
}
|
||||
},
|
||||
"aworld-playwright": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"playwright-mcp-aworld",
|
||||
"--isolated"
|
||||
],
|
||||
"env": {
|
||||
"OSS_ENDPOINT": "${OSS_ENDPOINT}",
|
||||
"OSS_ACCESS_KEY_ID": "${OSS_ACCESS_KEY_ID}",
|
||||
"OSS_ACCESS_KEY_SECRET": "${OSS_ACCESS_KEY_SECRET}",
|
||||
"OSS_BUCKET": "${OSS_BUCKET}",
|
||||
"PLAYWRIGHT_TIMEOUT": "120000",
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"fetch": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"-i",
|
||||
"https://mirrors.aliyun.com/pypi/simple/",
|
||||
"mcp-server-fetch",
|
||||
"--ignore-robots-txt"
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-time",
|
||||
"--local-timezone",
|
||||
"Asia/Shanghai"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Dict, Any, Optional, Union
|
||||
|
||||
import aiohttp
|
||||
from mcp.server import FastMCP
|
||||
from mcp.types import TextContent
|
||||
from pydantic import Field
|
||||
|
||||
mcp = FastMCP("aworldsearch-server")
|
||||
|
||||
|
||||
async def search_single(query: str, num: int = 5) -> Optional[Dict[str, Any]]:
|
||||
"""Execute a single search query, returns None on error"""
|
||||
try:
|
||||
url = os.getenv('AWORLD_SEARCH_URL')
|
||||
searchMode = os.getenv('AWORLD_SEARCH_SEARCHMODE')
|
||||
source = os.getenv('AWORLD_SEARCH_SOURCE')
|
||||
domain = os.getenv('AWORLD_SEARCH_DOMAIN')
|
||||
uid = os.getenv('AWORLD_SEARCH_UID')
|
||||
if not url or not searchMode or not source or not domain:
|
||||
logging.warning(f"Query failed: url, searchMode, source, domain parameters incomplete")
|
||||
return None
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
"domain": domain,
|
||||
"extParams": {},
|
||||
"page": 0,
|
||||
"pageSize": num,
|
||||
"query": query,
|
||||
"searchMode": searchMode,
|
||||
"source": source,
|
||||
"userId": uid
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
async with session.post(url, headers=headers, json=data) as response:
|
||||
if response.status != 200:
|
||||
logging.warning(f"Query failed: {query}, status code: {response.status}")
|
||||
return None
|
||||
|
||||
result = await response.json()
|
||||
return result
|
||||
except aiohttp.ClientError:
|
||||
logging.warning(f"Request error: {query}")
|
||||
return None
|
||||
except Exception:
|
||||
logging.warning(f"Query exception: {query}")
|
||||
return None
|
||||
|
||||
|
||||
def filter_valid_docs(result: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Filter valid document results, returns empty list if input is None"""
|
||||
if result is None:
|
||||
return []
|
||||
|
||||
try:
|
||||
valid_docs = []
|
||||
|
||||
# Check success field
|
||||
if not result.get("success"):
|
||||
return valid_docs
|
||||
|
||||
# Check searchDocs field
|
||||
search_docs = result.get("searchDocs", [])
|
||||
if not search_docs:
|
||||
return valid_docs
|
||||
|
||||
# Extract required fields
|
||||
required_fields = ["title", "docAbstract", "url", "doc"]
|
||||
|
||||
for doc in search_docs:
|
||||
# Check if all required fields exist and are not empty
|
||||
is_valid = True
|
||||
for field in required_fields:
|
||||
if field not in doc or not doc[field]:
|
||||
is_valid = False
|
||||
break
|
||||
|
||||
if is_valid:
|
||||
# Keep only required fields
|
||||
filtered_doc = {field: doc[field] for field in required_fields}
|
||||
valid_docs.append(filtered_doc)
|
||||
|
||||
return valid_docs
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
@mcp.tool(description="Search based on the user's input query list")
|
||||
async def search(
|
||||
query_list: List[str] = Field(
|
||||
description="List format, queries to search for"
|
||||
),
|
||||
num: int = Field(
|
||||
5,
|
||||
description="Maximum number of results per query, default is 5, please keep the total results within 15"
|
||||
)
|
||||
) -> Union[str, TextContent]:
|
||||
"""Execute search main function, supports single query or query list"""
|
||||
try:
|
||||
# Get configuration from environment variables
|
||||
env_total_num = os.getenv('AWORLD_SEARCH_TOTAL_NUM')
|
||||
if env_total_num and env_total_num.isdigit():
|
||||
# Force override input num parameter with environment variable
|
||||
num = int(env_total_num)
|
||||
|
||||
# If no queries provided, return empty list
|
||||
if not query_list:
|
||||
# Initialize TextContent with additional parameters
|
||||
return TextContent(
|
||||
type="text",
|
||||
text="", # Empty string instead of None
|
||||
**{"metadata": {}} # Pass as additional fields
|
||||
)
|
||||
|
||||
# When query count is >= 3 or slice_num is set, use corresponding value
|
||||
slice_num = os.getenv('AWORLD_SEARCH_SLICE_NUM')
|
||||
if slice_num and slice_num.isdigit():
|
||||
actual_num = int(slice_num)
|
||||
else:
|
||||
actual_num = 2 if len(query_list) >= 3 else num
|
||||
|
||||
# Execute all queries in parallel
|
||||
tasks = [search_single(q, actual_num) for q in query_list]
|
||||
raw_results = await asyncio.gather(*tasks)
|
||||
|
||||
# Filter and merge results
|
||||
all_valid_docs = []
|
||||
for result in raw_results:
|
||||
valid_docs = filter_valid_docs(result)
|
||||
all_valid_docs.extend(valid_docs)
|
||||
|
||||
# If no valid results found, return empty list
|
||||
if not all_valid_docs:
|
||||
# Initialize TextContent with additional parameters
|
||||
return TextContent(
|
||||
type="text",
|
||||
text="", # Empty string instead of None
|
||||
**{"metadata": {}} # Pass as additional fields
|
||||
)
|
||||
|
||||
# Format results as JSON
|
||||
result_json = json.dumps(all_valid_docs, ensure_ascii=False)
|
||||
|
||||
# Create dictionary structure directly
|
||||
combined_query = ",".join(query_list)
|
||||
|
||||
search_items = []
|
||||
# Use a dictionary to deduplicate by URL
|
||||
url_dict = {}
|
||||
for doc in all_valid_docs:
|
||||
url = doc.get("url", "")
|
||||
if url not in url_dict:
|
||||
url_dict[url] = {
|
||||
"title": doc.get("title", ""),
|
||||
"url": url,
|
||||
"snippet": doc.get("doc", "")[:100] + "..." if len(doc.get("doc", "")) > 100 else doc.get("doc", ""),
|
||||
"content": doc.get("doc", "") # Map doc field to content
|
||||
}
|
||||
|
||||
# Convert dictionary values to list
|
||||
search_items = list(url_dict.values())
|
||||
|
||||
|
||||
|
||||
search_output_dict = {
|
||||
"artifact_type": "WEB_PAGES",
|
||||
"artifact_data": {
|
||||
"query": combined_query,
|
||||
"results": search_items
|
||||
}
|
||||
}
|
||||
|
||||
# Log results
|
||||
logging.info(f"Completed {len(query_list)} queries, found {len(all_valid_docs)} valid documents")
|
||||
|
||||
# Initialize TextContent with additional parameters
|
||||
return TextContent(
|
||||
type="text",
|
||||
text=result_json,
|
||||
**{"metadata": search_output_dict} # Pass processed data as metadata
|
||||
)
|
||||
except Exception as e:
|
||||
# Handle errors
|
||||
logging.error(f"Search error: {e}")
|
||||
# Initialize TextContent with additional parameters
|
||||
return TextContent(
|
||||
type="text",
|
||||
text="", # Empty string instead of None
|
||||
**{"metadata": {}} # Pass as additional fields
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
print("Starting Audio MCP aworldsearch-server...", file=sys.stderr)
|
||||
mcp.run(transport="stdio")
|
||||
|
||||
|
||||
# Make the module callable
|
||||
def __call__():
|
||||
"""
|
||||
Make the module callable for uvx.
|
||||
This function is called when the module is executed directly.
|
||||
"""
|
||||
main()
|
||||
|
||||
|
||||
sys.modules[__name__].__call__ = __call__
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# # Configure logging
|
||||
# logging.basicConfig(
|
||||
# level=logging.INFO,
|
||||
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
# )
|
||||
#
|
||||
#
|
||||
# # Test single query
|
||||
# # asyncio.run(search("Alibaba financial report"))
|
||||
#
|
||||
# # Test multiple queries
|
||||
# test_queries = ["Alibaba financial report", "Tencent financial report", "Baidu financial report"]
|
||||
# asyncio.run(search(query_list=test_queries))
|
||||
+1
@@ -0,0 +1 @@
|
||||
httpx[socks]
|
||||
@@ -0,0 +1,92 @@
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from aworld.cmd.data_model import BaseAWorldAgent, ChatCompletionRequest
|
||||
from aworld.config.conf import AgentConfig, TaskConfig
|
||||
from aworld.agents.llm_agent import Agent
|
||||
from aworld.core.agent.swarm import GraphBuildType, Swarm
|
||||
from aworld.core.task import Task
|
||||
from aworld.runner import Runners
|
||||
from .prompt import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AWorldAgent(BaseAWorldAgent):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def name(self):
|
||||
return "Team Agent[AgentAsToolCall]"
|
||||
|
||||
def description(self):
|
||||
return "Team Agent with fetch and time mcp server"
|
||||
|
||||
async def run(self, prompt: str = None, request: ChatCompletionRequest = None):
|
||||
llm_provider = os.getenv("LLM_PROVIDER_TEAM", "openai")
|
||||
llm_model_name = os.getenv("LLM_MODEL_NAME_TEAM")
|
||||
llm_api_key = os.getenv("LLM_API_KEY_TEAM")
|
||||
llm_base_url = os.getenv("LLM_BASE_URL_TEAM")
|
||||
llm_temperature = os.getenv("LLM_TEMPERATURE_TEAM", 0.0)
|
||||
|
||||
if not llm_model_name or not llm_api_key or not llm_base_url:
|
||||
raise ValueError(
|
||||
"LLM_MODEL_NAME, LLM_API_KEY, LLM_BASE_URL must be set in your envrionment variables"
|
||||
)
|
||||
|
||||
agent_config = AgentConfig(
|
||||
llm_provider=llm_provider,
|
||||
llm_model_name=llm_model_name,
|
||||
llm_api_key=llm_api_key,
|
||||
llm_base_url=llm_base_url,
|
||||
llm_temperature=llm_temperature,
|
||||
)
|
||||
|
||||
path_cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
mcp_path = os.path.join(path_cwd, "mcp.json")
|
||||
with open(mcp_path, "r") as f:
|
||||
mcp_config = json.load(f)
|
||||
|
||||
plan_agent = Agent(
|
||||
conf=agent_config,
|
||||
name="Team-Plan-Agent",
|
||||
system_prompt=plan_agent_sys_prompt,
|
||||
)
|
||||
|
||||
google_pse_search_agent = Agent(
|
||||
conf=agent_config,
|
||||
name="Google-PSE-Search-Agent",
|
||||
system_prompt=google_pse_search_sys_prompt,
|
||||
mcp_config=mcp_config,
|
||||
mcp_servers=["google-pse-search"],
|
||||
)
|
||||
|
||||
summary_agent = Agent(
|
||||
conf=agent_config,
|
||||
name="Summary-Agent",
|
||||
system_prompt=summary_agent_sys_prompt,
|
||||
)
|
||||
|
||||
# default is sequence swarm mode
|
||||
swarm = Swarm(
|
||||
plan_agent,
|
||||
google_pse_search_agent,
|
||||
summary_agent,
|
||||
max_steps=10,
|
||||
build_type=GraphBuildType.TEAM,
|
||||
)
|
||||
|
||||
if prompt is None and request is not None:
|
||||
prompt = request.messages[-1].content
|
||||
|
||||
task = Task(
|
||||
input=prompt,
|
||||
swarm=swarm,
|
||||
conf=TaskConfig(max_steps=20),
|
||||
session_id=request.session_id,
|
||||
endless_threshold=50,
|
||||
)
|
||||
|
||||
async for output in Runners.streamed_run_task(task).stream_events():
|
||||
logger.info(f"Agent Ouput: {output}")
|
||||
yield output
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"google-pse-search": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@adenot/mcp-google-search"
|
||||
],
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": "${GOOGLE_API_KEY}",
|
||||
"GOOGLE_SEARCH_ENGINE_ID": "${GOOGLE_SEARCH_ENGINE_ID}"
|
||||
}
|
||||
},
|
||||
"aworldsearch-server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"agent_deploy/demo_agent/mcp_servers/aworldsearch_server.py"
|
||||
],
|
||||
"env": {
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60",
|
||||
"AWORLD_SEARCH_URL": "${AWORLD_SEARCH_URL}",
|
||||
"AWORLD_SEARCH_TOTAL_NUM": "${AWORLD_SEARCH_TOTAL_NUM}",
|
||||
"AWORLD_SEARCH_SLICE_NUM": "${AWORLD_SEARCH_SLICE_NUM}",
|
||||
"AWORLD_SEARCH_DOMAIN": "${AWORLD_SEARCH_DOMAIN}",
|
||||
"AWORLD_SEARCH_SEARCHMODE": "${AWORLD_SEARCH_SEARCHMODE}",
|
||||
"AWORLD_SEARCH_SOURCE": "${AWORLD_SEARCH_SOURCE}",
|
||||
"AWORLD_SEARCH_UID": "${AWORLD_SEARCH_UID}"
|
||||
}
|
||||
},
|
||||
"fetch": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"-i",
|
||||
"https://mirrors.aliyun.com/pypi/simple/",
|
||||
"mcp-server-fetch",
|
||||
"--ignore-robots-txt"
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-time",
|
||||
"--local-timezone",
|
||||
"Asia/Shanghai"
|
||||
]
|
||||
},
|
||||
"aworld-playwright": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"playwright-mcp-aworld",
|
||||
"--isolated"
|
||||
],
|
||||
"env": {
|
||||
"OSS_ENDPOINT": "${OSS_ENDPOINT}",
|
||||
"OSS_ACCESS_KEY_ID": "${OSS_ACCESS_KEY_ID}",
|
||||
"OSS_ACCESS_KEY_SECRET": "${OSS_ACCESS_KEY_SECRET}",
|
||||
"OSS_BUCKET": "${OSS_BUCKET}",
|
||||
"PLAYWRIGHT_TIMEOUT": "120000",
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
plan_agent_sys_prompt = """\
|
||||
You are a Team-Plan-Agent, Your goal is to make a plan to accomplish the task.
|
||||
"""
|
||||
|
||||
|
||||
google_pse_search_sys_prompt = """\
|
||||
You are Google-PSE-Search-Agent, your goal is to use the Google PSE Search to search the web.
|
||||
"""
|
||||
|
||||
aworld_playwright_sys_prompt = """\
|
||||
You are Aworld-Playwright-Agent, your goal is to use the Aworld Playwright Tool to search the web.
|
||||
|
||||
Instructions:
|
||||
- You must accomplish the task using the following steps:
|
||||
- STEP 1: Choose which search engine to search the web, for example: google, bing, etc.
|
||||
- STEP 2: Generate a search query based on the user's request
|
||||
- STEP 3: Call tool aworld-playwright to open search engine search engine page.
|
||||
- STEP 4: Click each search item to get the item content.
|
||||
- STEP 5: Format the search result to the following format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"title": <title>,
|
||||
"url": <url>,
|
||||
"content": <content>
|
||||
}
|
||||
]
|
||||
```
|
||||
"""
|
||||
|
||||
summary_agent_sys_prompt = """\
|
||||
You are Summary-Agent, your goal is to summarize the result of the search task following the instructions below.
|
||||
|
||||
Instructions:
|
||||
- You MUST list the content reference from the search result
|
||||
"""
|
||||
@@ -0,0 +1 @@
|
||||
browser-use
|
||||
Reference in New Issue
Block a user