import os import sys sys.path.append(os.path.dirname(os.path.dirname(__file__))) import json import re import asyncio import requests from openai import OpenAI from config import Global from bs4 import BeautifulSoup from datetime import datetime from concurrent.futures import ThreadPoolExecutor, as_completed from playwright.async_api import async_playwright from playwright.sync_api import sync_playwright from urllib.parse import urljoin from rich.console import Console from rich.panel import Panel from rich.status import Status console = Console() # if Global.modelscope['has_proxy']: # with open('assets\www_bing_cookie.json', 'r', encoding='utf-8') as f: # bing_cookie = json.load(f) # else: # with open('assets\cn_bing_cookie.json', 'r', encoding='utf-8') as f: # bing_cookie = json.load(f) # bing_cookie = '; '.join([f"{cookie['name']}={cookie['value']}" for cookie in bing_cookie]) if Global.modelscope['has_proxy']: bing_cookie = Global.modelscope['www_bing_cookie'] else: bing_cookie = Global.modelscope['cn_bing_cookie'] tools = [ { "type": "function", "function": { "name": "search_web", "description": "基于bing搜索,可以搜索网页或学术内容,返回搜索结果列表", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词或问题" }, "search_type": { "type": "string", "enum": ["网页", "学术"], "description": "搜索类型,默认为网页搜索", "default": "网页" } }, "required": ["query"] } } }, { "type": "function", "function": { "name": "get_webpage_text", "description": "获取指定URL列表的网页详细文本内容,专门用于提取网页中的文字信息。如果需要获取视频、图片、链接等其他元素,请使用get_website_elements", "parameters": { "type": "object", "properties": { "urls": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "要获取文本内容的URL列表(可以多输入几个URL,这样获取的信息更全面)" } }, "required": ["urls"] } } }, { "type": "function", "function": { "name": "get_website_elements", "description": "获取指定URL的网站内容,包括标题、图片、视频、链接、导航和meta信息。专门用于提取网页中的媒体资源和跳转链接,方便在特定网站中跳转网页。如果只需要获取网页文本内容,请使用get_webpage_text", "parameters": { "type": "object", "properties": { "url": { "type": "string", "format": "uri", "description": "要获取内容的网址" } }, "required": ["url"] } } }, { "type": "function", "function": { "name": "download_media", "description": "下载给定 URL 列表中的媒体文件(视频和图片)到指定目录", "parameters": { "type": "object", "properties": { "urls": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "要下载的媒体文件 URL 列表" }, "output_dir": { "type": "string", "description": "下载文件的输出目录,默认为 './downloads'", "default": "./downloads" } }, "required": ["urls"] } } } ] SYSTEM_PROMPT = """ 今天是%s 你是WebAgent,一个专业的信息搜索和分析助手。你的核心使命是帮助用户完成各种搜索任务,通过多轮对话和工具调用来收集全面、准确、最新的信息。 1. **get_website_elements**(媒体资源和链接发现工具) - 专门用于获取网页上的图片、视频、跳转链接、导航元素等 - 适用于需要获取媒体资源或在特定网站内部进行深度导航 - 帮助发现网站内的相关页面、子页面和媒体资源 - 注意:这个工具主要获取页面元素和资源,无法获取详细文本内容 2. **组合使用模式** - 使用get_website_elements发现新的有价值链接和媒体资源 - 对新发现的重要链接继续使用get_webpage_text获取文本内容 3. **信息整合**: - 持续收集和验证信息 - 交叉对比不同来源的内容 - 确保信息的准确性和时效性 4. **任务完成**: - 当收集到足够全面的信息后 - 输出 [任务完成] 标记 ## 搜索原则 - **权威性优先**:优先选择官方网站、知名媒体、学术机构等权威来源 - **时效性关注**:重点关注最新发布的信息和数据 - **全面性保证**:确保从多个角度和维度收集信息 - **准确性验证**:通过多个来源验证关键信息的准确性 - **精选策略**:从大量搜索结果中挑选最有价值的网站进行深度分析 ## 工具功能说明 1. **search_web**:网络搜索工具 - 功能:通过关键词发现相关网页 - 策略:获得搜索结果后,要从中挑选最有价值的网站进行后续分析 2. **get_webpage_text**:文本内容获取工具 - 功能:专门获取网页的详细文本内容 - 用途:深入分析网页文字内容,提取有价值的文本信息 - 使用策略:对从search_web中挑选出的优质网站使用 - 局限:只能获取文本信息,无法获取HTML、图片、视频、链接等其他元素 3. **get_website_elements**:媒体资源和链接发现工具 - 功能:获取网页上的图片、视频、跳转链接、导航元素、meta信息等页面结构 - 用途:获取媒体资源、在特定网站内部进行导航、发现更多相关页面 - 使用场景:需要获取图片、视频、链接等非文本内容时使用 - 局限:主要获取页面元素和资源,无法获取详细文本内容 4. **download_media**:媒体下载工具,用于下载相关媒体文件 记住:search_web用于"发现网站",get_webpage_text用于"获取文本",get_website_elements用于"获取媒体和链接" """ % (f"{datetime.today().strftime('%Y年%m月%d日')},{datetime.today().strftime('%A')}") class Agent: def __init__(self): self.client = OpenAI( api_key=Global.modelscope['api_key'], base_url=Global.modelscope['base_url'] ) self.messages = [{'role':'system', 'content':SYSTEM_PROMPT}] def request(self, task_content): self.messages.append({'role':'user', 'content':task_content}) while True: response = self.client.chat.completions.create( model = 'Qwen/Qwen3-235B-A22B-Thinking-2507', messages=self.messages, stream=True, tools=tools, temperature=0.6, top_p=0.95 ) content = '' reasoning = True assistant_message = {'role': 'assistant', 'content': ''} for chunk in response: if chunk.choices: delta = chunk.choices[0].delta if delta.content: if reasoning: reasoning = False console.print("\n[bold blue]Agent Response:[/bold blue]") console.print(delta.content, end='') content += delta.content elif delta.reasoning_content: console.print(delta.reasoning_content, end='', style="dim") if delta.tool_calls: if 'tool_calls' not in assistant_message: assistant_message['tool_calls'] = [] for tool_call in delta.tool_calls: if len(assistant_message['tool_calls']) <= tool_call.index: assistant_message['tool_calls'].append({ 'id': tool_call.id, 'type': 'function', 'function': { 'name': tool_call.function.name, 'arguments': tool_call.function.arguments or '' } }) else: assistant_message['tool_calls'][tool_call.index]['function']['arguments'] += tool_call.function.arguments or '' console.print() for message in self.messages: if message['role'] == 'tool': if "get_website_elements" in message['content'][:100]: if len(message['content']) > 1000: message['content'] = message['content'][:1000]+'...' self.messages.append(assistant_message) if assistant_message.get('tool_calls'): for tool_call in assistant_message['tool_calls']: try: function_name = tool_call['function']['name'] if tool_call['function']['arguments']: args = json.loads(tool_call['function']['arguments']) else: args = {} console.print(f"\n[bold magenta]Executing Tool: {function_name}[/bold magenta]") with Status(f"[bold green]Running {function_name}...", console=console): if function_name == "search_web": result = get_search(**args) elif function_name == "get_webpage_text": result = get_webpage_content(args['urls']) elif function_name == "get_website_elements": async def extract_content(): async with WebContentExtractor() as extractor: return await extractor.extract_page_content(args['url']) result = asyncio.run(extract_content()) elif function_name == "download_media": result = download_media(**args) else: raise Exception("错误的函数名") console.print(f"\n[bold cyan]{function_name.upper()} Output:[/bold cyan]") console.print(Panel(str(result)[:2000] + "..." if len(str(result)) > 2000 else str(result), border_style="cyan")) self.messages.append({ 'role': 'tool', 'content': f"执行成功: {function_name} - {str(result)[:30000]}", 'tool_call_id': tool_call['id'] }) except Exception as e: error_message = f"执行失败: {str(e)}" console.print(f"\n[bold red]{function_name.upper()} Error:[/bold red]") console.print(Panel(error_message, border_style="red")) self.messages.append({ 'role': 'tool', 'content': error_message, 'tool_call_id': tool_call['id'] }) if '[任务完成]' in content: console.print("\n[bold green]Task Completed[/bold green]") break if not assistant_message.get('tool_calls'): user_input = console.input('\n[bold yellow]Reply:[/bold yellow] ') self.messages.append({'role':'user', 'content':user_input}) try: port = Global.modelscope['port'] response = requests.post(f'http://127.0.0.1:{port}/return', json={"function_name":"Web_Agent", "function_id":agent_id, "result":content}) if response.status_code != 200: raise response.content except: raise "无法与server通信" class WebContentExtractor: def __init__(self): self.playwright = None self.browser = None async def __aenter__(self): self.playwright = await async_playwright().start() self.browser = await self.playwright.chromium.launch(headless=Global.modelscope['headless'], args=['--start-minimized']) return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.context: await self.context.close() if self.browser: await self.browser.close() if self.playwright: await self.playwright.stop() async def extract_page_content(self, url, wait_time=3000): """提取网页核心内容""" self.context = await self.browser.new_context() page = await self.context.new_page() try: try: await page.goto(url, wait_until='networkidle') await page.wait_for_timeout(wait_time) except: pass html_content = await page.content() extracted_data = self._preprocess_html(html_content, url) return extracted_data finally: await page.close() def _preprocess_html(self, html_content, base_url): """预处理HTML,提取核心信息""" soup = BeautifulSoup(html_content, 'html.parser') self._remove_unwanted_elements(soup) extracted_data = { 'url': base_url, 'title': self._extract_title(soup), 'content': self._extract_main_content(soup)[:10000], 'meta_info': self._extract_meta_info(soup), 'navigation': self._extract_navigation(soup), 'links': self._extract_links(soup, base_url), 'images': self._extract_images(soup, base_url) } return extracted_data def _remove_unwanted_elements(self, soup): """移除不必要的元素以减少token消耗""" unwanted_tags = [ 'script', 'style', 'noscript', 'iframe', 'embed', 'object', 'form', 'input', 'button', 'select', 'textarea', 'svg', 'canvas', 'video', 'audio' ] for tag in unwanted_tags: for element in soup.find_all(tag): element.decompose() for comment in soup.find_all(string=lambda text: isinstance(text, str) and text.strip().startswith('