from Plugins.WPSAPI import * import httpx import re from datetime import datetime, timedelta from llama_index.core.tools import FunctionTool from llama_index.llms.ollama import Ollama from llama_index.core.agent.workflow import AgentWorkflow from llama_index.core.agent.workflow.react_agent import ReActAgent from bs4 import BeautifulSoup import requests logger: ProjectConfig = Architecture.Get(ProjectConfig) OLLAMA_URL = logger.FindItem("ollama_url", "http://ollama.liubai.site") OLLAMA_MODEL = logger.FindItem("ollama_model", "qwen2.5:7b") logger.SaveProperties() def get_target_web_page_url(year:str, month:str, day:str) -> str: return fr"http://mrxwlb.com/{year}/{month}/{day}/{year}年{month}月{day}日新闻联播文字版/" class NewsAIAgent: """新闻AI智能体 - 基于LlamaIndex和Ollama的工具调用智能体""" def __init__(self, ollama_url: str = OLLAMA_URL): self.ollama_url = ollama_url self.client: Optional[httpx.AsyncClient] = None self.llm = Ollama(model=OLLAMA_MODEL, base_url=ollama_url, request_timeout=600.0) self.workflow: Optional[AgentWorkflow] = None self._initialize_agent() def _initialize_agent(self): """初始化智能体和工具""" # 创建工具函数 tools = [ FunctionTool.from_defaults( fn=self._get_current_date, name="get_current_date", description="获取当前日期,返回格式为'年-月-日',例如'2025-11-19'" ), #FunctionTool.from_defaults( # fn=self._get_yesterday_date, # name="get_yesterday_date", # description="获取昨天的日期,返回格式为'年-月-日',例如'2025-11-18'" #), FunctionTool.from_defaults( fn=self._get_news_content, name="get_news_content", description="获取指定日期的新闻联播文字内容。参数date格式为'年-月-日',例如'2025-11-19'。返回该日期的新闻内容文本。" ), #FunctionTool.from_defaults( # fn=self._parse_date_from_text, # name="parse_date_from_text", # description="从文本中解析日期。支持格式:'2025年11月19日'、'2025-11-19'、'2025/11/19'、'今天'、'昨天'等。返回格式为'年-月-日'" #), ] # 系统提示词 - 更清晰明确的指令 system_prompt = """你是一个新闻分析助手,专门回答关于新闻联播的问题。 【重要】你必须按照以下步骤使用工具: 步骤1: 确定日期 - 如果用户问"今天"或"今日"或者是与今日日期相关的问题,调用 get_current_date 获取今日日期 步骤2: 获取新闻内容 - 拿到日期后,必须调用 get_news_content(date="YYYY-MM-DD") 获取新闻 - 注意:date参数格式必须是 "年-月-日",例如 "2025-11-19" 步骤3: 回答问题 - 仔细阅读获取到的新闻内容 - 基于新闻内容准确回答用户的问题 - 如果新闻中没有相关信息,明确告知用户 【禁止】直接回答问题而不调用工具!你必须先获取新闻内容才能回答。""" # 创建ReActAgent agent = ReActAgent( llm=self.llm, tools=tools, verbose=True, system_prompt=system_prompt, ) # 创建AgentWorkflow self.workflow = AgentWorkflow( agents=[agent], timeout=600.0, ) async def _get_client(self) -> httpx.AsyncClient: """获取HTTP客户端(懒加载)""" if self.client is None: self.client = httpx.AsyncClient(timeout=120.0) return self.client def _get_current_date(self) -> str: """获取当前日期 Returns: 当前日期字符串,格式:年-月-日 """ now = datetime.now() return f"{now.year}-{str(now.month).zfill(2)}-{str(now.day).zfill(2)}" def _get_yesterday_date(self) -> str: """获取昨天日期 Returns: 昨天日期字符串,格式:年-月-日 """ yesterday = datetime.now() - timedelta(days=1) return f"{yesterday.year}-{str(yesterday.month).zfill(2)}-{str(yesterday.day).zfill(2)}" def _parse_date_from_text(self, text: str) -> str: """从文本中解析日期 Args: text: 包含日期信息的文本 Returns: 日期字符串,格式:年-月-日 """ # 尝试匹配日期格式 date_patterns = [ r'(\d{4})年(\d{1,2})月(\d{1,2})日', r'(\d{4})-(\d{1,2})-(\d{1,2})', r'(\d{4})/(\d{1,2})/(\d{1,2})', ] for pattern in date_patterns: match = re.search(pattern, text) if match: year, month, day = match.groups() return f"{year}-{month.zfill(2)}-{day.zfill(2)}" # 检查相对日期 if "今天" in text or "今日" in text: return self._get_current_date() elif "昨天" in text or "昨日" in text: return self._get_yesterday_date() # 默认返回今天 return self._get_current_date() def _get_news_content(self, date: str) -> str: """获取指定日期的新闻内容(带缓存) Args: date: 日期字符串,格式:年-月-日,例如'2025-11-19' Returns: 新闻文字内容 """ try: # 解析日期 parts = date.split('-') if len(parts) != 3: return f"日期格式错误,请使用'年-月-日'格式,例如'2025-11-19'" year, month, day = parts # 去掉前导零(某些网站URL格式要求) month = str(int(month)) day = str(int(day)) # 检查缓存 cache_key = f"news_cache/{year}/{month}/{day}" cached_file = ProjectConfig().GetFile(cache_key, False) if cached_file.Exists(): cached_content = cached_file.LoadAsText() logger.Log("Info", f"从缓存加载新闻: {date}") return cached_content # 如果没有缓存,则抓取网页 logger.Log("Info", f"从网页抓取新闻: {date}") # url = get_target_web_page_url(year, month, day) url = ToolURL(get_target_web_page_url(year, month, day)) # 添加浏览器请求头,模拟真实浏览器访问 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', } logger.Log("Info", f"请求URL: {url}") response = requests.get(url, headers=headers, timeout=30, allow_redirects=True) response.raise_for_status() # 使用BeautifulSoup解析HTML soup = BeautifulSoup(response.text, 'html.parser') # 移除script和style标签 for script in soup(["script", "style"]): script.decompose() # 获取文本内容 content = soup.get_text() # 清理空白字符 lines = (line.strip() for line in content.splitlines()) chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) content = ' '.join(chunk for chunk in chunks if chunk) if not content or len(content) < 100: return f"未能获取到{year}年{month}月{day}日的新闻内容" # 保存到缓存 ProjectConfig().GetFile(cache_key, True).SaveAsText(content) logger.Log("Info", f"新闻内容已缓存: {date}") return content except Exception as e: logger.Log("Error", f"获取新闻失败: {e}") return f"获取新闻时出错: {str(e)}" async def answer_question(self, query: str) -> str: """根据问题回答新闻内容 Args: query: 用户问题 Returns: AI生成的回答 """ try: logger.Log("Info", f"="*50) logger.Log("Info", f"用户提问: {query}") logger.Log("Info", f"使用模型: {self.llm.model}") logger.Log("Info", f"="*50) # 使用workflow运行agent(注意参数是user_msg) result = await self.workflow.run(user_msg=query) logger.Log("Info", f"Agent执行完成,结果类型: {type(result)}") # 提取回答内容 if hasattr(result, 'response'): answer = str(result.response) logger.Log("Info", f"从result.response提取答案") elif hasattr(result, 'message'): answer = str(result.message) logger.Log("Info", f"从result.message提取答案") else: answer = str(result) logger.Log("Info", f"直接转换result为字符串") logger.Log("Info", f"最终答案长度: {len(answer)} 字符") return answer except Exception as e: logger.Log("Error", f"回答问题失败: {e}") import traceback error_trace = traceback.format_exc() logger.Log("Error", f"详细错误:\n{error_trace}") return f"处理问题时出错: {str(e)}" async def close(self): """关闭客户端""" if self.client: await self.client.aclose() self.client = None class NewsAIPlugin(WPSAPI): """新闻AI智能问答插件""" def __init__(self): super().__init__() self.ai_agent = NewsAIAgent(OLLAMA_URL) @override def dependencies(self) -> List[Type]: return [WPSAPI] @override def is_enable_plugin(self) -> bool: return True def get_guide_title(self) -> str: return "新闻AI智能问答" def get_guide_subtitle(self) -> str: return "基于AI的新闻联播智能问答系统" def get_guide_metadata(self) -> Dict[str, str]: return { "AI模型": "qwen3:0.6b", "数据源": "每日新闻联播", "功能": "智能问答", } def collect_command_entries(self) -> Sequence[GuideEntry]: return ( { "title": "新闻", "identifier": "ask_news", "description": "询问新闻内容,支持自动识别日期或查询今天/昨天的新闻。", "metadata": {"别名": "news"}, "icon": "🤖", "badge": "AI", "details": [ { "type": "list", "items": [ "支持日期格式:2024年11月19日、2024-11-19、2024/11/19", "支持相对日期:今天、昨天、今日、昨日", "自动使用AI分析新闻内容并回答问题", "示例:问 今天有什么重要新闻?", "示例:问 2024年11月19日 经济相关的新闻有哪些?", ] } ] }, #{ # "title": "新闻摘要", # "identifier": "news_summary", # "description": "获取指定日期新闻的AI摘要。", # "metadata": {"别名": "摘要"}, # "icon": "📝", # "badge": "AI", #}, ) def collect_guide_entries(self) -> Sequence[GuideEntry]: return ( { "title": "智能问答", "description": ( "使用AI理解用户问题,从新闻内容中提取相关信息进行回答。" "支持自然语言提问,可以询问特定主题、人物、事件等。" ), "icon": "💬", }, { "title": "日期识别", "description": ( "自动从问题中识别日期,支持多种格式。" "如果未指定日期,默认查询当天新闻。" ), "icon": "📅", }, { "title": "内容抓取", "description": ( "自动从新闻网站抓取指定日期的新闻联播文字版内容。" ), "icon": "🌐", }, ) @override def wake_up(self) -> None: logger.Log("Info", f"{ConsoleFrontColor.GREEN}NewsAIPlugin 新闻AI智能问答插件已加载{ConsoleFrontColor.RESET}") self.register_plugin("news") self.register_plugin("新闻") @override async def callback(self, message: str, chat_id: int, user_id: int) -> str|None: """处理用户问题""" try: if not message or message.strip() == "": help_text = """# 📰 新闻AI智能问答使用帮助 **直接提问即可,智能体会自动:** 1. 识别你想查询的日期 2. 获取对应日期的新闻内容 3. 基于新闻内容回答你的问题 **支持的日期格式:** - 今天、昨天、今日、昨日 - 2025年11月19日 - 2025-11-19 - 2025/11/19 **示例问题:** - `今天有什么重要新闻?` - `2025年11月17日有什么新闻?` - `昨天的新闻中有关于经济的内容吗?` - `今天习近平主席有什么活动?` - `请总结今天新闻联播的主要内容` **特性:** ✨ 智能理解问题意图 🤖 自动调用工具获取信息 💾 新闻内容自动缓存 🚀 基于LlamaIndex + Ollama""" return await self.send_markdown_message(help_text, chat_id, user_id) # 使用智能体回答问题 answer = await self.ai_agent.answer_question(message) # 格式化返回结果 formatted_answer = f"""📰 **新闻AI智能问答** {answer} --- *由 LlamaIndex + Ollama 驱动*""" return await self.send_markdown_message(formatted_answer, chat_id, user_id) except Exception as e: logger.Log("Error", f"新闻AI问答异常: {e}") import traceback error_detail = traceback.format_exc() logger.Log("Error", f"详细错误: {error_detail}") error_msg = f"""❌ **处理问题时出错** 错误信息:{str(e)} 请稍后重试或联系管理员。""" return await self.send_markdown_message(error_msg, chat_id, user_id)