Skip to content

Commit 390a17d

Browse files
Merge remote-tracking branch 'origin/main'
2 parents e74cc53 + 580d068 commit 390a17d

File tree

3 files changed

+1518
-0
lines changed

3 files changed

+1518
-0
lines changed

examples/agents/browser_demo.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
"""Demo for using OxyGent with browser tools."""
2+
3+
import asyncio
4+
import os
5+
import logging
6+
from typing import Dict, Any
7+
8+
from oxygent import MAS, Config, oxy
9+
from oxygent.prompts import SYSTEM_PROMPT
10+
11+
# 配置日志
12+
logging.basicConfig(
13+
level=logging.INFO,
14+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
15+
)
16+
logger = logging.getLogger(__name__)
17+
18+
# 从环境变量加载配置
19+
def load_config() -> Dict[str, Any]:
20+
"""Load configuration from environment variables."""
21+
required_vars = [
22+
"DEFAULT_LLM_API_KEY",
23+
"DEFAULT_LLM_BASE_URL",
24+
"DEFAULT_LLM_MODEL_NAME"
25+
]
26+
27+
config = {}
28+
missing_vars = []
29+
30+
for var in required_vars:
31+
value = os.getenv(var)
32+
if value is None:
33+
missing_vars.append(var)
34+
config[var] = value
35+
36+
if missing_vars:
37+
raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
38+
39+
return config
40+
41+
# Browser-specific system prompt
42+
BROWSER_SYSTEM_PROMPT = """
43+
You are a helpful assistant that can use these tools:
44+
${tools_description}
45+
46+
Choose the appropriate tool based on the user's question.
47+
If no tool is needed, respond directly.
48+
If answering the user's question requires multiple tool calls, call only one tool at a time. After the user receives the tool result, they will provide you with feedback on the tool call result.
49+
50+
Important instructions for browser operations:
51+
1. When performing web operations:
52+
- Always verify URLs before visiting
53+
- Handle page loading states appropriately
54+
- Extract relevant information efficiently
55+
- Save important data to files when requested
56+
- Follow proper browser automation practices
57+
58+
2. When saving web content:
59+
- Format data appropriately before saving
60+
- Use clear file naming conventions
61+
- Include relevant metadata
62+
- Verify file save operations
63+
64+
3. When you need to use a tool, you must only respond with the exact JSON object format below, nothing else:
65+
```json
66+
{
67+
"think": "Your thinking (if analysis is needed)",
68+
"tool_name": "Tool name",
69+
"arguments": {
70+
"parameter_name": "parameter_value"
71+
}
72+
}
73+
```
74+
75+
4. When a tool is still executing, you must wait for its result before calling another tool.
76+
77+
5. When calling multiple tools in sequence, you MUST correctly pass context and information from previous tool results to subsequent tool calls:
78+
- Include relevant data from previous tool results in the arguments of your next tool call
79+
- Maintain state and context across multiple tool calls
80+
- If a tool returns data that will be needed by a future tool, you must store that data
81+
82+
After receiving the tool's response:
83+
1. Transform the raw data into a natural conversational response
84+
2. The answer should be concise but rich in content
85+
3. Focus on the most relevant information
86+
4. Use appropriate context from the user's question
87+
5. Avoid simply repeating the raw data
88+
89+
Please only use the tools explicitly defined above.
90+
"""
91+
92+
class BrowserDemo:
93+
"""Browser demo implementation class."""
94+
95+
def __init__(self):
96+
"""Initialize the browser demo with configuration."""
97+
try:
98+
self.config = load_config()
99+
Config.set_agent_llm_model("default_llm")
100+
self.oxy_space = self._create_oxy_space()
101+
except Exception as e:
102+
logger.error(f"Failed to initialize BrowserDemo: {str(e)}")
103+
raise
104+
105+
def _create_oxy_space(self) -> list:
106+
"""Create and configure the oxy space with all required components."""
107+
try:
108+
return [
109+
self._create_http_llm(),
110+
self._create_browser_tools(),
111+
self._create_filesystem_tools(),
112+
self._create_browser_agent(),
113+
self._create_file_agent(),
114+
self._create_master_agent()
115+
]
116+
except Exception as e:
117+
logger.error(f"Failed to create oxy space: {str(e)}")
118+
raise
119+
120+
def _create_http_llm(self) -> oxy.HttpLLM:
121+
"""Create and configure the HTTP LLM component."""
122+
return oxy.HttpLLM(
123+
name="default_llm",
124+
api_key=self.config["DEFAULT_LLM_API_KEY"],
125+
base_url=self.config["DEFAULT_LLM_BASE_URL"],
126+
model_name=self.config["DEFAULT_LLM_MODEL_NAME"],
127+
llm_params={"temperature": 0.01},
128+
semaphore=4,
129+
category="llm",
130+
class_name="HttpLLM",
131+
desc="Default language model",
132+
desc_for_llm="Default language model for text generation",
133+
is_entrance=False,
134+
is_permission_required=False,
135+
is_save_data=True,
136+
timeout=60,
137+
retries=3,
138+
delay=1,
139+
is_multimodal_supported=False,
140+
)
141+
142+
def _create_browser_tools(self) -> oxy.StdioMCPClient:
143+
"""Create and configure the browser tools component."""
144+
return oxy.StdioMCPClient(
145+
name="browser_tools",
146+
params={
147+
"command": "uv",
148+
"args": ["--directory", "./mcp_servers", "run", "browser_tools.py"],
149+
},
150+
category="tool",
151+
class_name="StdioMCPClient",
152+
desc="Browser tools for web operations",
153+
desc_for_llm="Tools for browser automation and web scraping",
154+
is_entrance=False,
155+
is_permission_required=False,
156+
is_save_data=True,
157+
timeout=30,
158+
retries=3,
159+
delay=1,
160+
friendly_error_text="Browser operation failed",
161+
semaphore=2
162+
)
163+
164+
def _create_filesystem_tools(self) -> oxy.StdioMCPClient:
165+
"""Create and configure the filesystem tools component."""
166+
return oxy.StdioMCPClient(
167+
name="filesystem",
168+
params={
169+
"command": "npx",
170+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./local_file"],
171+
},
172+
category="tool",
173+
class_name="StdioMCPClient",
174+
desc="File system operations",
175+
desc_for_llm="Tools for file system operations",
176+
is_entrance=False,
177+
is_permission_required=False,
178+
is_save_data=True,
179+
timeout=30,
180+
retries=3,
181+
delay=1,
182+
friendly_error_text="File system operation failed",
183+
semaphore=2
184+
)
185+
186+
def _create_browser_agent(self) -> oxy.ReActAgent:
187+
"""Create and configure the browser agent component."""
188+
return oxy.ReActAgent(
189+
name="browser_agent",
190+
desc="A tool for browser operations like visiting URLs, getting page content, and analyzing web pages.",
191+
desc_for_llm="Agent for browser automation and web scraping",
192+
category="agent",
193+
class_name="ReActAgent",
194+
tools=["browser_tools"],
195+
llm_model="default_llm",
196+
prompt=BROWSER_SYSTEM_PROMPT,
197+
is_entrance=False,
198+
is_permission_required=False,
199+
is_save_data=True,
200+
timeout=30,
201+
retries=3,
202+
delay=1,
203+
is_multimodal_supported=False,
204+
semaphore=2
205+
)
206+
207+
def _create_file_agent(self) -> oxy.ReActAgent:
208+
"""Create and configure the file agent component."""
209+
return oxy.ReActAgent(
210+
name="file_agent",
211+
desc="A tool for file operation.",
212+
desc_for_llm="Agent for file system operations",
213+
category="agent",
214+
class_name="ReActAgent",
215+
tools=["filesystem"],
216+
llm_model="default_llm",
217+
prompt=SYSTEM_PROMPT,
218+
is_entrance=False,
219+
is_permission_required=False,
220+
is_save_data=True,
221+
timeout=30,
222+
retries=3,
223+
delay=1,
224+
is_multimodal_supported=False,
225+
semaphore=2
226+
)
227+
228+
def _create_master_agent(self) -> oxy.ReActAgent:
229+
"""Create and configure the master agent component."""
230+
return oxy.ReActAgent(
231+
name="master_agent",
232+
desc="Master agent for coordinating browser and file operations",
233+
desc_for_llm="Master agent that coordinates browser automation and file operations",
234+
category="agent",
235+
class_name="ReActAgent",
236+
sub_agents=["browser_agent", "file_agent"],
237+
is_master=True,
238+
llm_model="default_llm",
239+
prompt=SYSTEM_PROMPT,
240+
is_entrance=False,
241+
is_permission_required=False,
242+
is_save_data=True,
243+
timeout=100,
244+
retries=3,
245+
delay=1,
246+
is_multimodal_supported=False,
247+
semaphore=2
248+
)
249+
250+
async def run_demo(self, query: str = "搜索'武汉市天气',提取搜索结果的天气概览数据保存到`./local_file/weather.txt`"):
251+
"""Run the browser demo with the specified query."""
252+
try:
253+
async with MAS(oxy_space=self.oxy_space) as mas:
254+
logger.info(f"Starting web service with query: {query}")
255+
await mas.start_web_service(first_query=query)
256+
logger.info("Web service completed successfully")
257+
except Exception as e:
258+
logger.error(f"Error running browser demo: {str(e)}")
259+
raise
260+
261+
async def main():
262+
"""Main entry point for the browser demo."""
263+
try:
264+
demo = BrowserDemo()
265+
await demo.run_demo()
266+
except Exception as e:
267+
logger.error(f"Fatal error in main: {str(e)}")
268+
raise
269+
270+
if __name__ == "__main__":
271+
asyncio.run(main())

0 commit comments

Comments
 (0)