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

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -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,2 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
@@ -0,0 +1,45 @@
from pydantic import BaseModel, Field
from aworld.output import Output
import asyncio
from aworld.output.base import OutputPart
class DebateSpeech(Output, BaseModel):
name: str = Field(default="", description="name of the speaker")
type: str = Field(default="", description="speech type")
stance: str = Field(default="", description="stance of the speech")
content: str = Field(default="", description="content of the speech")
round: int = Field(default=0, description="round of the speech")
finished: bool = Field(default=False, description="round of the speech")
metadata: dict = Field(default_factory=dict, description="metadata of the speech")
async def wait_until_finished(self):
"""
Wait until the speech is finished.
"""
while not self.finished:
await asyncio.sleep(1)
async def convert_to_parts(self, message_output, after_call):
async def __convert_to_parts__():
async for item in message_output.response_generator:
if item:
self.content += item
yield OutputPart(content=item)
if message_output.finished:
await after_call(message_output.response)
self.parts = __convert_to_parts__()
@classmethod
def from_dict(cls, data: dict) -> "DebateSpeech":
return cls(
name=data.get("name", ""),
type=data.get("type", ""),
stance=data.get("stance", ""),
content=data.get("content", ""),
round=data.get("round", 0),
metadata=data.get("metadata", {})
)
@@ -0,0 +1,219 @@
import logging
from abc import ABC
from typing import Dict, Any, Union, List, Literal, Optional
from datetime import datetime
import uuid
from aworld.models.model_response import ToolCall
from examples.multi_agents.collaborative.debate.agent.base import DebateSpeech
from examples.multi_agents.collaborative.debate.agent.prompts import user_assignment_prompt, user_assignment_system_prompt, affirmative_few_shots, \
negative_few_shots, \
user_debate_prompt
from examples.multi_agents.collaborative.debate.agent.search.search_engine import SearchEngine
from examples.multi_agents.collaborative.debate.agent.search.tavily_search_engine import TavilySearchEngine
from examples.multi_agents.collaborative.debate.agent.stream_output_agent import StreamOutputAgent
from aworld.config import AgentConfig
from aworld.core.common import Observation, ActionModel
from aworld.output import SearchOutput, SearchItem, MessageOutput
from aworld.output.artifact import ArtifactType
def truncate_content(raw_content, char_limit):
if raw_content is None:
raw_content = ''
if len(raw_content) > char_limit:
raw_content = raw_content[:char_limit] + "... [truncated]"
return raw_content
class DebateAgent(StreamOutputAgent, ABC):
stance: Literal["affirmative", "negative"]
def __init__(self, conf: AgentConfig, name: str, stance: Literal["affirmative", "negative"], search_engine: Optional[SearchEngine] = TavilySearchEngine()):
conf.name = name
super().__init__(conf, name)
self.steps = 0
self.stance = stance
self.search_engine = search_engine
async def speech(self, topic: str, opinion: str,oppose_opinion: str, round: int, speech_history: list[DebateSpeech]) -> DebateSpeech:
observation = Observation(content=self.get_latest_speech(speech_history).content if self.get_latest_speech(speech_history) else "")
info = {
"topic": topic,
"round": round,
"opinion": opinion,
"oppose_opinion": oppose_opinion,
"history": speech_history
}
actions = await self.async_policy(observation, info)
return actions[0].policy_info
async def async_policy(self, observation: Observation, info: Dict[str, Any] = {}, **kwargs) -> Union[
List[ActionModel], None]:
## step 1: params
opponent_claim = observation.content
round = info["round"]
opinion = info["opinion"]
oppose_opinion = info["oppose_opinion"]
topic = info["topic"]
history: list[DebateSpeech] = info["history"]
#Event.emit("xxx")
## step2: gen keywords
keywords = await self.gen_keywords(topic, opinion, oppose_opinion, opponent_claim, history)
logging.info(f"gen keywords = {keywords}")
## step3search_webpages
search_results = await self.search_webpages(keywords, max_results=5)
for search_result in search_results:
logging.info(f"keyword#{search_result['query']}-> result size is {len(search_result['results'])}")
search_item = {
"query": search_result.get("query", ""),
"results": [SearchItem(title=result["title"],url=result["url"], content=result['content'], raw_content=result['raw_content'], metadata={}) for result in search_result["results"]],
"origin_tool_call": ToolCall.from_dict({
"id": f"call_search",
"type": "function",
"function": {
"name": "search",
"arguments": keywords
}
}),
"task_id": self.context.task_id
}
search_output = SearchOutput.from_dict(search_item)
await self.workspace.create_artifact(
artifact_type=ArtifactType.WEB_PAGES,
artifact_id=str(uuid.uuid4()),
content=search_output,
metadata={
"query": search_output.query,
"user": self.name(),
"round": info["round"],
"opinion": info["opinion"],
"oppose_opinion": info["oppose_opinion"],
"topic": info["topic"],
"tags": [f"user#{self.name()}",f"Rounds#{info['round']}"]
}
)
## step4 gen result
user_response = await self.gen_statement(topic, opinion, oppose_opinion, opponent_claim, history, search_results)
logging.info(f"user_response is {user_response}")
## step3: gen speech
speech = DebateSpeech.from_dict({
"round": round,
"type": "speech",
"stance": self.stance,
"name": self.name(),
})
async def after_speech_call(message_output_response):
logging.info(f"{self.stance}#{self.name()}: after_speech_call")
speech.metadata = {}
speech.content = message_output_response
speech.finished = True
await speech.convert_to_parts(user_response, after_speech_call)
action = ActionModel(
policy_info=speech
)
return [action]
async def gen_keywords(self, topic, opinion, oppose_opinion, last_oppose_speech_content, history):
current_time = datetime.now().strftime("%Y-%m-%d-%H")
human_prompt = user_assignment_prompt.format(topic=topic,
opinion=opinion,
oppose_opinion=oppose_opinion,
last_oppose_speech_content=last_oppose_speech_content,
current_time = current_time,
limit=2
)
messages = [{'role': 'system', 'content': user_assignment_system_prompt},
{'role': 'user', 'content': human_prompt}]
output = await self.async_call_llm(messages)
response = await output.get_finished_response()
return response.split(",")
async def search_webpages(self, keywords, max_results):
return await self.search_engine.async_batch_search(queries=keywords, max_results=max_results)
async def gen_statement(self, topic, opinion, oppose_opinion, opponent_claim, history, search_results) -> MessageOutput:
search_results_content = ""
for search_result in search_results:
search_results_content += f"SearchQuery: {search_result['query']}"
search_results_content += "\n\n".join([truncate_content(s['content'], 1000) for s in search_result['results']])
unique_history = history
# if len(history) >= 2:
# for i in range(len(history)):
# # Check if the current element is the same as the next one
# if i == len(history) - 1 or history[i] != history[i+1]:
# # Add the current element to the result list
# unique_history.append(history[i])
affirmative_chat_history = ""
negative_chat_history = ""
if len(unique_history) >= 2:
if self.stance == "affirmative":
for speech in unique_history[:-1]:
if speech.stance == "affirmative":
affirmative_chat_history = affirmative_chat_history + "You: " + speech.content + "\n"
elif speech.stance == "negative":
affirmative_chat_history = affirmative_chat_history + "Your Opponent: " + speech.content + "\n"
elif self.stance == "negative":
for speech in unique_history[:-1]:
if speech.stance == "negative":
negative_chat_history = negative_chat_history + "You: " + speech.content + "\n"
elif speech.stance == "affirmative":
negative_chat_history = negative_chat_history + "Your Opponent: " + speech.content + "\n"
few_shots = ""
chat_history = ""
if self.stance == "affirmative":
chat_history = affirmative_chat_history
few_shots = affirmative_few_shots
elif self.stance == "negative":
chat_history = negative_chat_history
few_shots = negative_few_shots
human_prompt = user_debate_prompt.format(topic=topic,
opinion=opinion,
oppose_opinion=oppose_opinion,
last_oppose_speech_content=opponent_claim,
search_results_content=search_results_content,
chat_history = chat_history,
few_shots = few_shots
)
messages = [{'role': 'system', 'content': user_assignment_system_prompt},
{'role': 'user', 'content': human_prompt}]
return await self.async_call_llm(messages)
def get_latest_speech(self, history: list[DebateSpeech]):
"""
get the latest speech from history
"""
if len(history) == 0:
return None
return history[-1]
def set_workspace(self, workspace):
self.workspace = workspace
@@ -0,0 +1,209 @@
import logging
from typing import Optional, AsyncGenerator
from aworld.memory.main import MemoryFactory
from examples.multi_agents.collaborative.debate.agent.base import DebateSpeech
from examples.multi_agents.collaborative.debate.agent.debate_agent import DebateAgent
from examples.multi_agents.collaborative.debate.agent.moderator_agent import ModeratorAgent
from aworld.core.common import Observation
from aworld.core.memory import MemoryItem
from aworld.output import Output, WorkSpace, ArtifactType, CodeArtifact
class DebateArena:
"""
DebateArena is platform for debate
"""
affirmative_speaker: DebateAgent
negative_speaker: DebateAgent
moderator: Optional[ModeratorAgent]
speeches: list[DebateSpeech]
display_panel: str
def __init__(self,
affirmative_speaker: DebateAgent,
negative_speaker: DebateAgent,
moderator: ModeratorAgent,
workspace: WorkSpace,
**kwargs
):
self.affirmative_speaker = affirmative_speaker
self.negative_speaker = negative_speaker
self.moderator = moderator
self.speeches = []
self.workspace = workspace
self.affirmative_speaker.set_workspace(workspace)
self.negative_speaker.set_workspace(workspace)
self.moderator.set_workspace(workspace)
self.moderator.memory = MemoryFactory.instance()
# Event.register("topic", func= );
async def async_run(self, topic: str, rounds: int) \
-> AsyncGenerator[Output, None]:
"""
Start the debate
1. debate will start from round 1
2. each round will have two speeches, one from affirmative_speaker and one from negative_speaker
3. after all rounds finished, the debate will end
Args:
topic: str -> topic of the debate
affirmative_opinion: str -> affirmative speaker's opinion
negative_opinion: str -> negative speaker's opinion
rounds: int -> number of rounds
Returns: list[DebateSpeech]
"""
## 1. generate opinions
moderator_speech = await self.moderator_speech(topic, rounds)
if not moderator_speech:
return
yield moderator_speech
await moderator_speech.wait_until_finished()
self.store_speech(moderator_speech)
affirmative_opinion = moderator_speech.metadata["affirmative_opinion"]
negative_opinion = moderator_speech.metadata["negative_opinion"]
logging.info(f"✈️==================================== opinions =============================================")
logging.info(f"topic: {topic}")
logging.info(f"affirmative_opinion: {affirmative_opinion}")
logging.info(f"negative_opinion: {negative_opinion}")
logging.info(f"✈️==================================== start... =============================================")
## 2. Alternating speeches
for i in range(1, rounds + 1):
logging.info(
f"✈️==================================== round#{i} start =============================================")
loading_speech = DebateSpeech.from_dict({
"content": f"\n\n**round#{i} start** \n\n",
"round": i,
"type": "loading",
"stance": "stage",
"name": "stage",
"finished": True
})
yield loading_speech
loading_speech = DebateSpeech.from_dict({
"content": f"\n\n【affirmative】✅:{self.affirmative_speaker.name()}\n Searching ....\n",
"round": i,
"type": "loading",
"stance": "stage",
"name": "stage",
"finished": True
})
yield loading_speech
# affirmative_speech
speech = await self.affirmative_speech(i, topic, affirmative_opinion, negative_opinion)
yield speech
await speech.wait_until_finished()
self.store_speech(speech)
loading_speech = DebateSpeech.from_dict({
"content": f"\n\n【negative】❌:{self.negative_speaker.name()}\n Searching ....\n",
"round": i,
"type": "loading",
"stance": "stage",
"name": "stage",
"finished": True
})
yield loading_speech
# negative_speech
speech = await self.negative_speech(i, topic, negative_opinion, affirmative_opinion)
yield speech
await speech.wait_until_finished()
self.store_speech(speech)
logging.info(
f"🛬==================================== round#{i} end =============================================")
## 3. Summary speeches
moderator_speech = await self.moderator.summary_speech()
if not moderator_speech:
return
yield moderator_speech
await moderator_speech.wait_until_finished()
await self.workspace.add_artifact(
CodeArtifact.build_artifact(
artifact_type=ArtifactType.CODE,
artifact_id="result",
code_type='html',
content=moderator_speech.content,
metadata={
"topic": topic
}
)
)
logging.info(
f"🛬==================================== total is end =============================================")
async def moderator_speech(self, topic, rounds) -> DebateSpeech | None:
results = await self.moderator.async_policy(Observation(content=topic, info={"rounds": rounds}))
if not results or not results[0] or not results[0].policy_info:
return None
return results[0].policy_info
async def affirmative_speech(self, round: int, topic: str, opinion: str, oppose_opinion: str) -> DebateSpeech:
"""
affirmative_speaker will start speech
"""
affirmative_speaker = self.get_affirmative_speaker()
logging.info(affirmative_speaker.name() + ": " + "start")
speech = await affirmative_speaker.speech(topic, opinion, oppose_opinion, round, self.speeches)
logging.info(affirmative_speaker.name() + ": result: " + speech.content)
return speech
async def negative_speech(self, round: int, topic: str, opinion: str, oppose_opinion: str) -> DebateSpeech:
"""
after affirmative_speaker finished speech, negative_speaker will start speech
"""
negative_speaker = self.get_negative_speaker()
logging.info(negative_speaker.name() + ": " + "start")
speech = await negative_speaker.speech(topic, opinion, oppose_opinion, round, self.speeches)
logging.info(negative_speaker.name() + ": result: " + speech.content)
return speech
def get_affirmative_speaker(self) -> DebateAgent:
"""
return the affirmative speaker
"""
return self.affirmative_speaker
def get_negative_speaker(self) -> DebateAgent:
"""
return the negative speaker
"""
return self.negative_speaker
def store_speech(self, speech: DebateSpeech):
self.moderator.memory.add(MemoryItem.from_dict({
"content": speech.content,
"metadata": {
"round": speech.round,
"speaker": speech.name,
"type": speech.type
}
}))
self.speeches.append(speech)
def gen_closing_statement(self):
pass
@@ -0,0 +1,139 @@
import logging
from abc import ABC
from datetime import datetime
from typing import Dict, Any, Union, List
from pydantic import Field
from aworld.core.common import Observation, ActionModel
from aworld.output import MessageOutput, WorkSpace, ArtifactType, SearchOutput
from examples.multi_agents.collaborative.debate.agent.base import DebateSpeech
from examples.multi_agents.collaborative.debate.agent.prompts import user_assignment_system_prompt, summary_system_prompt, summary_debate_prompt
from examples.multi_agents.collaborative.debate.agent.stream_output_agent import StreamOutputAgent
def truncate_content(raw_content, char_limit):
if raw_content is None:
raw_content = ''
if len(raw_content) > char_limit:
raw_content = raw_content[:char_limit] + "... [truncated]"
return raw_content
class ModeratorAgent(StreamOutputAgent, ABC):
stance: str = "moderator"
topic: str = Field(default=None)
affirmative_opinion: str = Field(default=None)
negative_opinion: str = Field(default=None)
async def async_policy(self, observation: Observation, info: Dict[str, Any] = {}, **kwargs) -> Union[
List[ActionModel], None]:
## step 1: params
topic = observation.content
## step2: gen opinions
output = await self.gen_opinions(topic)
## step3: gen speech
moderator_speech = DebateSpeech.from_dict({
"content": "",
"round": 0,
"type": "speech",
"stance": "moderator",
"name": self.name(),
})
async def after_speech_call(message_output_response):
logging.info("moderator: after_speech_call")
opinions = message_output_response
self.affirmative_opinion = opinions.get("positive_opinion")
self.negative_opinion = opinions.get("negative_opinion")
moderator_speech.metadata = {
"topic": topic,
"affirmative_opinion": self.affirmative_opinion,
"negative_opinion": self.negative_opinion,
}
moderator_speech.finished = True
await moderator_speech.convert_to_parts(output, after_speech_call)
action = ActionModel(
policy_info=moderator_speech
)
return [action]
async def gen_opinions(self, topic) -> MessageOutput:
current_time = datetime.now().strftime("%Y-%m-%d-%H")
human_prompt = self.agent_prompt.format(topic=topic,
current_time=current_time,
)
messages = [
{"role": "system", "content": user_assignment_system_prompt},
{"role": "user", "content": human_prompt}
]
output = await self.async_call_llm(messages, json_parse=True)
return output
async def summary_speech(self) -> DebateSpeech:
chat_history = await self.get_formated_history()
print(f"chat_history is \n {chat_history}")
search_results_content_history = await self.get_formated_search_results_content_history()
print(f"search_results_content_history is \n {search_results_content_history}")
human_prompt = summary_debate_prompt.format(topic=self.topic,
opinion=self.affirmative_opinion,
oppose_opinion=self.negative_opinion,
chat_history=chat_history,
search_results_content_history=search_results_content_history
)
messages = [
{"role": "system", "content": summary_system_prompt},
{"role": "user", "content": human_prompt}
]
output = await self.async_call_llm(messages, json_parse=False)
moderator_speech = DebateSpeech.from_dict({
"content": "",
"round": 0,
"type": "summary",
"stance": "moderator",
"name": self.name(),
})
async def after_speech_call(message_output_response):
moderator_speech.finished = True
await moderator_speech.convert_to_parts(output, after_speech_call)
return moderator_speech
async def get_formated_history(self):
formated = []
for item in self.memory.get_all():
formated.append(f"{item.metadata['speaker']} (round {item.metadata['round']}): {item.content}")
return "\n".join(formated)
async def get_formated_search_results_content_history(self):
if not self.workspace:
return
search_results = self.workspace.list_artifacts(ArtifactType.WEB_PAGES)
materials = []
for search_result in search_results:
if isinstance(search_result.content, SearchOutput):
for item in search_result.content.results:
materials.append(
f"{search_result.metadata['user']} (round {search_result.metadata['round']}): {search_result.content.query}: url: {item.url}, title: {item.title}, description: {item.content}")
return "\n".join(materials)
def set_workspace(self, workspace: WorkSpace):
self.workspace = workspace
@@ -0,0 +1,251 @@
user_assignment_system_prompt = "You are a helpful search agent."
user_assignment_prompt = """
While facing the hot topic: {topic}, your opinion is {opinion}. You stand on your opinion and fight any other opinion (such as {oppose_opinion}) that differs from your opinion.
You have an assistant that can help search the relative materials online to support your opinion {opinion} in the topic: {topic}
While facing your opponent's claim {last_oppose_speech_content}, you decide to fight back! Now you need to ask your assistant to do some online survey, according to that claim, to give you more insights to decide what to debate with your opponent.
For example, you could talk to your assistant to search: A, B, C . Then you will gain more insights and can decide how to fight back!
Attention: You need to pay attention the current time ({current_time}).
If you want to search something that is sensitive to the time, espcially some materials are needed to be up to date, you need to output your assignment queries associated with the current time, so your assistant can search the up to date search.
Format Requirements (query seperated by , ), limit {limit}:
aaa, bbb, ccc...
Now, you could output your assignment queries (strictly follow the Format Requirements: seperated by ,) to your assistant.
"""
user_debate_system_prompt = "You are an impressive debater."
user_debate_prompt = """
## Role
You are an outstanding debater, with a fiery and stubborn personality, sharp language, and a penchant for irony.
Your responsibility is to respond to the content of the opposing debater's speech based on the current debate topic, your stance, your skills, and restrictions.
## Current Debate Topic
{topic}
## Your Stance
{opinion}
## Opponent's Stance
{oppose_opinion}
## Your Skills
- You have a strong sense of self-esteem, a bit rebellious, enjoy arguing back, and speak quite aggressively.
- Sometimes you like to use habitual rhetorical questions to belittle the opponent.
- When you sense the opponent is about to leave or the conversation is hard to continue due to lack of topics, you can throw out provocative questions related to the current debate topic to challenge the opponent.
- You excel at finding loopholes in the opponent's arguments and incessantly attacking their weaknesses.
- You are adept at using reference materials to support your arguments with facts and counter the opponent's loopholes.
- Your language is concise and incisive, always able to capture the key points. When responding, you can settle the matter in just three or four sentences without the need for lengthy explanations.
## Attention
- It is strictly prohibited to use phrases like “lets switch to a lighter topic.”
- The output content must be organized according to the given format and must not deviate from the framework requirements.
- If you recoginize that there is no history chat and your opponent has not said anything yet, it means you are on the first round, you need to directly and briefly state your opinion to your opponent.
- You are not allowed to output your inner thinking chain, just directly speak out to your opponent.
- Please do not say too much, it will make you seem too mild, which directly undermines your character.
- Be brief and sharp, and use sarcasm frequently. Limit your response within 100-150 words.
- DO NOT output your response starting with 'You:', JUST DIRECTLY output your response without "You:".
## Dialogue Style Examples
{few_shots}
## Current Task
You are arguing with your opponent on the topic: {topic}. You opinion is {opinion}.
Below is the content of your conversation:
{chat_history}
Now, your opponent just said to you: “{last_oppose_speech_content}”.
At this moment, you have obtained the following related materials, which may be beneficial for your rebuttal against the opponent.
Reference materials: {search_results_content}
Please use your character's identity traits, skills, restrictions, and dialogue style examples, relying on your reference materials, to counter the opposing debater in a incisive and concise way. Never output "You:". Now, please directly output your response to your opponent without "You:".
"""
affirmative_few_shots = """
Your opponent:
You: Let me make it straight, Jordan is the best, who dares to oppose?
Your opponent: James has a better character. Even when JR Smith made a blunder in the finals, LeBron was still supportive. If it were Jordan, he would have chased him around the court in anger.
You: Cut it out! Michael Jordan was a ruthless winner on the court. He wouldn't tolerate any mistakes, and it's this relentless standard that earned him six championships and never failing in the finals. So, no matter how much of a nice guy LeBron tries to be, it won't erase his multiple finals defeats.
Your opponent: James has a stronger body, and physique is the most important factor on basketball court.
You: My Jesus, I can't believe someone would say that. Bastetball is far beyond physique. Skills, mind and leadership all matters. In these aspects, James is no match for Jordan. If James is so proud of his physique, why doesn't he go work in the fields?
"""
negative_few_shots = """
Your opponent:
You: Let me make it straight, Lebron is the best, who dares to oppose?
Your opponent: With no doubt, Jordan's skills are more well-rounded.
You: Would you stop kidding...Since Jordan's skills are supposedly so well-rounded, then tell me why his three-point shooting percentage is so low. Jordan was just given a greater personal boost because of the unique era he played in.
"""
generate_opinions_prompt = """
Here is the debate topic:{topic}.
Please output the two sides' (positive side vs negative side) opinions of the topic.
Output format:
{{
"positive_opinion":"xxx"
"negative_opinion":"yyy"
}}
Now the topic is {topic}, please follow the example and output format, output the two sides' opinions.
you must always return json, don not return markdown tag or others such as ```json,``` etc;
For example:
----------------------------------
topic: Who is better? A or B?
{{
"positive_opinion":"A"
"negative_opinion":"B"
}}
----------------------------------
topic: Is is OK to drink wine?
positive_opinion:Yes
negative_opinion:No
{{
"positive_opinion":"Yes"
"negative_opinion":"No"
}}
"""
summary_system_prompt = "You are a good assistant to make summary."
summary_debate_prompt = """
## Your Role
You are a reliable assistant to make summary and skilled in information architecture and visual storytelling, capable of transforming any content into stunning cards using a webpage format.
Your responsibility is: 1. read people's conversation and make summary on this conversation; 2. translate your summary into the HTML code.
## Current Situation
1. You find that several people have started a debate on a particular topic "{topic}";
2. One side is holding the opinion: {opinion}, the other side is holding: {oppose_opinion}.
2.1 For the details of the conversation between these two sides, please refer to Conversation History below.
3. Each time one side is giving the conversation, he/she would like to cite the supportive materials searched on the website, in form of the urls, title, descritpion.
3.1 For the details of the supportive materials between these two sides for each conversation round, please refer to Supportive Materials History below.
4. Now you are supposed to make a concise, brief summary for each round conversation between the two sides, in terms of the viewpoint, citation.
4.1 'viewpoint' is the main point for each side in each conversation round;
4.2 'citation' is the formatted structure in terms of the urls, title of the supportive materials that is indeed cited in each conversation round.
## Conversation History:
{chat_history}
## Supportive Materials History
{search_results_content_history}
## Summary Format
debater_name1's summary (round 1): xxxx
debater_name1's citation(round 1): url_1: xxxx, title_1: xxxx; url_2: xxxx, title_2: xxxx
debater_name2's summary (round 1): yyyy
debater_name2's citation(round 1): url_1: yyyy, title_1: yyyy; url_2: yyyy, title_2: yyyy...
debater_name1's summary (round 2): pppp
debater_name1's citation(round 2): url_1: pppp, title_1: pppp; url_2: pppp, title_2: pppp
debater_name2's summary (round 2): qqqq
debater_name2's citation(round 2): url_1: qqqq, title_1: qqqq; url_2: qqqq, title_2: qqqq...
...
## Write HTML Requirements
1. You should only present using HTML code, including basic HTML, CSS, and JavaScript. This should encompass text, visualization, and structured results.
2. Provide complete HTML code; CSS and JavaScript should also be included within the code to ensure the user can open a single file.
3. Do not arbitrarily omit the core viewpoints from the original text; core viewpoints and summaries must be preserved.
## Write HTML Technical Implementation
1. Utilize modern CSS techniques (such as flex/grid layouts, variables, gradients)
2. Ensure the code is clean and efficient, without redundant elements
3. Add a save button that does not interfere with the design
4. Implement a one-click save as image feature using html2canvas
5. The saved image should only contain the cover design, excluding interface elements
6. Use Google Fonts or other CDNs to load appropriate modern fonts
7. Online icon resources can be used (such as Font Awesome)
## Write HTML Professional Typography Techniques
1. Apply the designer's common "negative space" technique to create focal points
2. Maintain harmonious proportion between text and decorative elements
3. Ensure a clear visual flow to guide the readers eye movement
4. Use subtle shadow or light effects to increase depth
5. For webpage URL addresses and their corresponding titles, use hyperlinks.
## Example
Topic: Which is more important to success? Working hard or Opportunity?
Conversation History:
Tom (round 1): Hard work can help individuals continuously improve their skills and professional knowledge, laying a solid foundation for achieving success. A programmer Kim spends time every day learning new languages and technologies, eventually becoming an expert in the field and securing an important position at a major tech company.
Jerry (round 1): Opportunity is more important. Steve Jobs saw the great potential of personal computer, then he found Apple.
Tom (round 2): Many famous entrepreneurs experienced numerous setbacks and failures before achieving success. They did not just happen to be lucky enough to get opportunities, but rather, through continuous effort and relentless perseverance, they eventually succeeded. For example, Thomas Edison conducted thousands of experiments in the process of inventing the light bulb, which demonstrates that his success was inseparable from his tenacious effort.
Jerry (round 2): Many historical events were driven by opportunities rather than purely by abilities. For example, during wars, some generals won battles because they made crucial decisions at key moments, even though they were not the most experienced commanders.
Supportive Materials History:
Tom (round 1): id1: url: aaaa, title: Why is Kim? description: bbbb.
Jerry (round 1): id1: url: cccc, title: Steve's story. description: dddd.
Tom (round 2): id1: url: eeee, title: The invention of light bulb. description: ffff.
Jerry (round 2): id1: url: gggg, title: Some interesting things during the war. description: hhhh.
Your Summary:
Tom's summary (round 1): Hard working improves people's skills and thus leads to personal sucess.
Tom's citation (round 1): url_1: aaaa, title_1: Why is Kim?
Jerry's summary (round 1): Steve's story supports opportunity is more important.
Jerry's citation (round 1): url_1:cccc, title_1:Steve's story.
Tom's summary (round 2): Entrepreneurs, like Thomas Edison, faced repeated failures before succeeding. Their achievements resulted from persistent effort and perseverance, not mere luck.
Tom's citation (round 2): url_1: eeee, title_1: The invention of light bulb.
Jerry's summary (round 2): Historical events are often driven by opportunity, as seen when generals win battles through timely decisions rather than experience.
Jerry's citation (round 2): url_1:gggg, title_1: Some interesting things during the war.
## Attention
- Strictly follow the ## Output Format. The output content must be organized according to the ## Output Format and must not deviate from the framework requirements.
- The summary of each side's each conversation round should be very concise (it would be better within 30 words), cannot be too long. Just capture the key points.
- Only the supportive materials that has been indeed referred by the debater can appear in the citation, in terms of their urls and the titles. Ignore the materials that have not been referred by the debater.
- You are not allowed to output your inner thinking chain.
- DO NOT output your response starting with 'You:', JUST DIRECTLY output your response without "You:".
- Please output your summary in the HTML form directly in one step.
Please deeply understand Your Role and Current Situation. Strictly follow the Summary Format, Attention with Example, output your summary, according to the Conversation History and Supportive Materials History.
Then transfer your summary according to the Write HTML Requirements, Write HTML Technical Implementation, Write HTML Professional Typography Techniques.
"""
# ## Conversation History:
# Jake (round1): Jordan is the best, he scores 35.1 points per game. No one is better than that.
# Lucy (round1): Jordan's opponent is too weak, Lebron's opponent is stronger, so James is better.
# Jake (round2): Jordan is the best, he has 6 champions. No one is better than that.
# Lucy (round2): Jordan's teamates are better, pippen, rodman... Lebron leads the whole team forward.
# ## Supportive Materials History
# Jake (round1): url_1: 123, title_1: Jordan's data;
# Lucy (round1): url_1: 456, title_1: The diff between basketball's eras; url_2: 9999, title_2: which second-order wave forces on hydrodynamcis.
# Jake (round2): url_1: 123678aa, title_1: Who's got most champions?
# Lucy (round2): url_1: xxxbbbw45, title_1: The importance of teammates.
@@ -0,0 +1,48 @@
import asyncio
from enum import Enum
from typing import List
class SearchAPI(Enum):
PERPLEXITY = "perplexity"
TAVILY = "tavily"
EXA = "exa"
ARXIV = "arxiv"
PUBMED = "pubmed"
LINKUP = "linkup"
class SearchException(Exception):
def __init__(self, message, error_code=None):
super().__init__()
self.message = message
self.error_code = error_code
def __str__(self):
return f'{self.message}'
class SearchEngine:
def batch_search(self, queries: List[str], max_results=5, include_raw_content=False, **kwargs):
try:
return asyncio.run(
self.async_batch_search(queries, max_results=max_results, include_raw_content=include_raw_content, **kwargs))
except Exception as err:
raise SearchException(f"search queries = {queries} failed.")
async def async_batch_search(self, queries: List[str], max_results=5, include_raw_content=False, **kwargs):
search_tasks = []
for query in queries:
search_tasks.append(
self.async_search(query, max_results, include_raw_content, **kwargs)
)
# Execute all searches concurrently
search_docs = await asyncio.gather(*search_tasks)
return search_docs
async def async_search(self, query: str, max_results=5, include_raw_content=False, **kwargs) -> dict:
pass
@@ -0,0 +1,46 @@
from examples.multi_agents.collaborative.debate.agent.search.search_engine import SearchEngine
class TavilySearchEngine(SearchEngine):
"""Tavily"""
async def async_search(self, query: str, max_results=5, include_raw_content=False, **kwargs) -> dict:
"""
Performs concurrent web searches using the Tavily API.
Args:
query (Str): str
Returns:
dict: search responses from Tavily API, one per query. Each response has format:
{
'query': str, # The original search query
'follow_up_questions': None,
'answer': None,
'images': list,
'results': [ # List of search results
{
'title': str, # Title of the webpage
'url': str, # URL of the result
'content': str, # Summary/snippet of content
'score': float, # Relevance score
'raw_content': str|None # Full page content if available
},
...
]
}
"""
try:
from tavily import AsyncTavilyClient
except ImportError:
# install mistune
import subprocess
subprocess.run(["pip", "install", "tavily-python>=0.5.1"], check=True)
from tavily import AsyncTavilyClient
tavily_async_client = AsyncTavilyClient()
return await tavily_async_client.search(
query,
max_results=5,
include_raw_content=True,
topic="general"
)
@@ -0,0 +1,18 @@
from aworld.config import AgentConfig
from aworld.agents.llm_agent import Agent
from aworld.models.llm import acall_llm_model_stream
from aworld.output import MessageOutput
class StreamOutputAgent(Agent):
def __init__(self, conf: AgentConfig, name: str, **kwargs):
super().__init__(conf, name)
async def async_call_llm(self, messages, json_parse=False) -> MessageOutput:
# Async streaming with acall_llm_model
async def async_generator():
async for chunk in acall_llm_model_stream(self.llm, messages, stream=True):
if chunk.content:
yield chunk.content
return MessageOutput(source=async_generator(), json_parse=json_parse)
@@ -0,0 +1,59 @@
import asyncio
import os
import uuid
from dotenv import load_dotenv
from aworld import trace
from examples.multi_agents.collaborative.debate.agent.debate_agent import DebateAgent
from examples.multi_agents.collaborative.debate.agent.main import DebateArena
from examples.multi_agents.collaborative.debate.agent.moderator_agent import ModeratorAgent
from examples.multi_agents.collaborative.debate.agent.prompts import generate_opinions_prompt
from aworld.config import AgentConfig
from aworld.output import WorkSpace
# os.environ["LLM_PROVIDER"] = "openai"
# os.environ["LLM_MODEL_NAME"] = "YOUR_LLM_MODEL_NAME"
# os.environ["LLM_BASE_URL"] = "YOUR_LLM_BASE_URL"
# os.environ["LLM_API_KEY"] = "YOUR_LLM_API_KEY"
if __name__ == '__main__':
load_dotenv()
trace.configure()
base_config = {
"llm_provider": os.getenv("LLM_PROVIDER"),
"llm_model_name": os.environ['LLM_MODEL_NAME'],
"llm_base_url": os.environ['LLM_BASE_URL'],
"llm_api_key": os.environ['LLM_API_KEY'],
"llm_temperature": os.getenv("LLM_TEMPERATURE", 0.0)
}
agentConfig = AgentConfig.model_validate(base_config)
agent1 = DebateAgent(name="affirmativeSpeaker", stance="affirmative", conf=AgentConfig.model_validate(base_config))
agent2 = DebateAgent(name="negativeSpeaker", stance="negative", conf=AgentConfig.model_validate(base_config))
moderator_agent = ModeratorAgent(
conf=AgentConfig.model_validate(base_config | {
"name": "moderator_agent",
"agent_prompt": generate_opinions_prompt
}),
name="moderator_agent"
)
debate_arena = DebateArena(affirmative_speaker=agent1, negative_speaker=agent2, moderator=moderator_agent,
workspace=WorkSpace.from_local_storages(str(uuid.uuid4())))
async def start_debate(debate_arena, topic, rounds):
speeches = debate_arena.async_run(topic=topic, rounds=rounds)
async for speech in speeches:
if speech.parts:
async for part in speech.parts:
print(part.content, flush=True, end="")
print(f"{speech.name}: {speech.content}")
asyncio.run(start_debate(debate_arena, topic="张居正", rounds=3))
@@ -0,0 +1,6 @@
Multi-agent interaction paradigm in travel examples.
![img.png](img.png)
Suggest using gpt-4o or deepseek-chat models, The latter does not support multimodality, so set use_vision to false.
@@ -0,0 +1,2 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,74 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
plan_sys_prompt = """
You are an AI agent designed to automate tasks. Your goal is to accomplish the ultimate task following the rules.
# Input Format
Task
Previous steps
# Response Rules
1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON or text.
2. ACTIONS: You can specify one actions in the list to be executed in sequence.
3. REQUIREMENTS:
- If you want to extract some information, you can use example_search_agent gets related info and url, then you can use browser_agent extract info from specific url.
- If you want to search, you need use example_search_agent and give the specific task.
- If you want to extract, you need use broswer_agent and give the task contains specific url. you can give two url once for browser agent, and tell browser agent only need extract from one url. if one url is invalid, use another url for replace.
- If you want to write, you need use example_write_agent and give the task and refer, the task needs be very detailed and contains all requirements.
4. Pipeline:
- If you have many information to search. you should choose search tool - extract loop many times.
5. TASK COMPLETION:
- Use the done action as the last action as soon as the ultimate task is complete
- Dont use "done" before you are done with everything the user asked you, except you reach the last step of max_steps.
- If you reach your last step, use the done action even if the task is not fully finished. Provide all the information you have gathered so far. If the ultimate task is completly finished set success to true. If not everything the user asked for is completed set success in done to false!
- If you have to do something repeatedly for example the task says for "each", or "for all", or "x times", count always inside "memory" how many times you have done it and how many remain. Don't stop until you have completed like the task asked you. Only call done after the last step.
- Don't hallucinate actions
- Make sure you include everything you found out for the ultimate task in the done text parameter. Do not just say you are done, but include the requested information of the task.
"""
# 6. Output Format:
# - You need first evaluate previous goal, and then save important things into memory, then give the next goal and use tool call to execute task.
# 'current_state': {
# 'evaluation_previous_goal': 'Success - I completed search and gets the url',
# 'memory': 'search compeleted and gets url',
# 'next_goal': 'extract information from the related url',
# },
#
# 7. You need execute task step by step, so that you can only give one simple action per time. do not search much more info one times. (you can search, extract, search, extract, ..., write)
# """
plan_prompt = """Your ultimate task is: {task}. If you achieved your ultimate task, stop everything and use the done action in the next step to complete the task. If not, continue as usual.
You should break down the retrieval task into small atomic granularities, search small and extract, and then search next. you should only take one action / function call once per time.
"""
search_sys_prompt = "You are a helpful search agent. please only use one action complete this task (only search once), at least results 6 pages."
search_prompt = """
Please act as a search agent, constructing appropriate keywords and search terms, using search toolkit to collect relevant information, including urls, webpage snapshots, etc.
Here are the question: {task}
"""
search_output_prompt = """
1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format:
{"action":[{{"one_action_name": {{// action-specific parameter}}}}, // ... more actions in sequence]}
"""
write_sys_prompt = "You are a helpful write agent."
write_prompt = """
Please act as a write agent, constructing appropriate keywords and search terms, using search toolkit to collect relevant information, including urls, webpage snapshots, etc.
Here are the write task: {task}
please only use one action complete this task.
"""
# Here is the reference information: {reference}
write_output_prompt = """
1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format:
{"action":[{{"one_action_name": {{// action-specific parameter}}}}, // ... more actions in sequence]}
"""
@@ -0,0 +1,95 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
import os
from aworld.config.conf import AgentConfig, ToolConfig
from aworld.agents.llm_agent import Agent
from aworld.config import ModelConfig
from aworld.core.agent.swarm import TeamSwarm, Swarm, GraphBuildType
from aworld.core.task import Task
from aworld.runner import Runners
from examples.browser_use.agent import BrowserAgent
from examples.browser_use.config import BrowserAgentConfig
from examples.common.tools.common import Tools
from examples.common.tools.conf import BrowserToolConfig
from examples.common.tools.tool_action import SearchAction
from examples.multi_agents.collaborative.travel.prompts import *
# os.environ["LLM_PROVIDER"] = "openai"
# os.environ["LLM_MODEL_NAME"] = "YOUR_LLM_MODEL_NAME"
# os.environ["LLM_BASE_URL"] = "YOUR_LLM_BASE_URL"
# os.environ["LLM_API_KEY"] = "YOUR_LLM_API_KEY"
model_config = ModelConfig(
llm_provider=os.getenv("LLM_PROVIDER", "openai"),
llm_model_name=os.getenv("LLM_MODEL_NAME"),
llm_base_url=os.getenv("LLM_BASE_URL"),
llm_api_key=os.getenv("LLM_API_KEY"),
llm_temperature=os.getenv("LLM_TEMPERATURE", 0.0)
)
agent_config = AgentConfig(
llm_config=model_config,
use_vision=False
)
plan = Agent(
conf=agent_config,
name="example_plan_agent",
system_prompt=plan_sys_prompt,
agent_prompt=plan_prompt,
agent_names=['browser_agent'],
step_reset=False
)
search = Agent(
conf=agent_config,
name="example_search_agent",
desc="search ",
system_prompt=search_sys_prompt,
agent_prompt=search_prompt,
tool_names=[Tools.SEARCH_API.value],
black_tool_actions={Tools.SEARCH_API.value: [SearchAction.DUCK_GO.value.name, SearchAction.WIKI.value.name,
SearchAction.GOOGLE.value.name]}
)
write = Agent(
conf=agent_config,
name="example_write_agent",
system_prompt=write_sys_prompt,
agent_prompt=write_prompt,
tool_names=[Tools.HTML.value],
)
browser_agent = BrowserAgent(
name='browser_agent',
desc="browser_agent can execute extract web info task and open local file task, if you want to use browser agent to open local file, you should give the specific absolutely file path in params.",
conf=BrowserAgentConfig(
llm_config=model_config,
use_vision=False
),
custom_executor=True,
tool_names=[Tools.BROWSER.value]
)
def main():
goal = """
I need a 7-day Japan itinerary from April 2 to April 8 2025, departing from Hangzhou, We want to see beautiful cherry blossoms and experience traditional Japanese culture (kendo, tea ceremonies, Zen meditation). We would like to taste matcha in Uji and enjoy the hot springs in Kobe. I am planning to propose during this trip, so I need a special location recommendation. Please provide a detailed itinerary and create a simple HTML travel handbook that includes a 7-day Japan itinerary, an updated cherry blossom table, attraction descriptions, essential Japanese phrases, and travel tips for us to reference throughout our journey.
you need search and extract different info 1 times, and then write, at last use browser agent goto the html url and then, complete the task.
"""
swarm = Swarm((plan, search), (plan, browser_agent), (plan, write), build_type=GraphBuildType.HANDOFF)
# swarm = TeamSwarm(plan, search, browser_agent, write)
task = Task(
swarm=swarm,
input=goal,
tools_conf={
Tools.BROWSER.value: BrowserToolConfig(width=800, height=720, use_async=True, llm_config=model_config),
Tools.HTML.value: ToolConfig(name="html", llm_config=model_config)
},
endless_threshold=5
)
Runners.sync_run_task(task)
if __name__ == '__main__':
main()