entari-plugin-hyw 0.3.5__py3-none-any.whl → 4.0.0rc14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

Files changed (78) hide show
  1. entari_plugin_hyw/Untitled-1 +1865 -0
  2. entari_plugin_hyw/__init__.py +979 -116
  3. entari_plugin_hyw/filters.py +83 -0
  4. entari_plugin_hyw/history.py +251 -0
  5. entari_plugin_hyw/misc.py +214 -0
  6. entari_plugin_hyw/search_cache.py +154 -0
  7. entari_plugin_hyw-4.0.0rc14.dist-info/METADATA +118 -0
  8. entari_plugin_hyw-4.0.0rc14.dist-info/RECORD +72 -0
  9. {entari_plugin_hyw-0.3.5.dist-info → entari_plugin_hyw-4.0.0rc14.dist-info}/WHEEL +1 -1
  10. {entari_plugin_hyw-0.3.5.dist-info → entari_plugin_hyw-4.0.0rc14.dist-info}/top_level.txt +1 -0
  11. hyw_core/__init__.py +94 -0
  12. hyw_core/agent.py +768 -0
  13. hyw_core/browser_control/__init__.py +63 -0
  14. hyw_core/browser_control/assets/card-dist/index.html +425 -0
  15. hyw_core/browser_control/assets/card-dist/logos/anthropic.svg +1 -0
  16. hyw_core/browser_control/assets/card-dist/logos/cerebras.svg +9 -0
  17. hyw_core/browser_control/assets/card-dist/logos/deepseek.png +0 -0
  18. hyw_core/browser_control/assets/card-dist/logos/gemini.svg +1 -0
  19. hyw_core/browser_control/assets/card-dist/logos/google.svg +1 -0
  20. hyw_core/browser_control/assets/card-dist/logos/grok.png +0 -0
  21. hyw_core/browser_control/assets/card-dist/logos/huggingface.png +0 -0
  22. hyw_core/browser_control/assets/card-dist/logos/microsoft.svg +15 -0
  23. hyw_core/browser_control/assets/card-dist/logos/minimax.png +0 -0
  24. hyw_core/browser_control/assets/card-dist/logos/mistral.png +0 -0
  25. hyw_core/browser_control/assets/card-dist/logos/nvida.png +0 -0
  26. hyw_core/browser_control/assets/card-dist/logos/openai.svg +1 -0
  27. hyw_core/browser_control/assets/card-dist/logos/openrouter.png +0 -0
  28. hyw_core/browser_control/assets/card-dist/logos/perplexity.svg +24 -0
  29. hyw_core/browser_control/assets/card-dist/logos/qwen.png +0 -0
  30. hyw_core/browser_control/assets/card-dist/logos/xai.png +0 -0
  31. hyw_core/browser_control/assets/card-dist/logos/xiaomi.png +0 -0
  32. hyw_core/browser_control/assets/card-dist/logos/zai.png +0 -0
  33. hyw_core/browser_control/assets/card-dist/vite.svg +1 -0
  34. hyw_core/browser_control/assets/index.html +5691 -0
  35. hyw_core/browser_control/assets/logos/anthropic.svg +1 -0
  36. hyw_core/browser_control/assets/logos/cerebras.svg +9 -0
  37. hyw_core/browser_control/assets/logos/deepseek.png +0 -0
  38. hyw_core/browser_control/assets/logos/gemini.svg +1 -0
  39. hyw_core/browser_control/assets/logos/google.svg +1 -0
  40. hyw_core/browser_control/assets/logos/grok.png +0 -0
  41. hyw_core/browser_control/assets/logos/huggingface.png +0 -0
  42. hyw_core/browser_control/assets/logos/microsoft.svg +15 -0
  43. hyw_core/browser_control/assets/logos/minimax.png +0 -0
  44. hyw_core/browser_control/assets/logos/mistral.png +0 -0
  45. hyw_core/browser_control/assets/logos/nvida.png +0 -0
  46. hyw_core/browser_control/assets/logos/openai.svg +1 -0
  47. hyw_core/browser_control/assets/logos/openrouter.png +0 -0
  48. hyw_core/browser_control/assets/logos/perplexity.svg +24 -0
  49. hyw_core/browser_control/assets/logos/qwen.png +0 -0
  50. hyw_core/browser_control/assets/logos/xai.png +0 -0
  51. hyw_core/browser_control/assets/logos/xiaomi.png +0 -0
  52. hyw_core/browser_control/assets/logos/zai.png +0 -0
  53. hyw_core/browser_control/engines/__init__.py +15 -0
  54. hyw_core/browser_control/engines/base.py +13 -0
  55. hyw_core/browser_control/engines/default.py +166 -0
  56. hyw_core/browser_control/engines/duckduckgo.py +171 -0
  57. hyw_core/browser_control/landing.html +172 -0
  58. hyw_core/browser_control/manager.py +173 -0
  59. hyw_core/browser_control/renderer.py +446 -0
  60. hyw_core/browser_control/service.py +940 -0
  61. hyw_core/config.py +154 -0
  62. hyw_core/core.py +462 -0
  63. hyw_core/crawling/__init__.py +18 -0
  64. hyw_core/crawling/completeness.py +437 -0
  65. hyw_core/crawling/models.py +88 -0
  66. hyw_core/definitions.py +104 -0
  67. hyw_core/image_cache.py +274 -0
  68. hyw_core/pipeline.py +502 -0
  69. hyw_core/search.py +171 -0
  70. hyw_core/stages/__init__.py +21 -0
  71. hyw_core/stages/base.py +95 -0
  72. hyw_core/stages/summary.py +191 -0
  73. entari_plugin_hyw/agent.py +0 -419
  74. entari_plugin_hyw/compressor.py +0 -59
  75. entari_plugin_hyw/tools.py +0 -236
  76. entari_plugin_hyw/vision.py +0 -35
  77. entari_plugin_hyw-0.3.5.dist-info/METADATA +0 -112
  78. entari_plugin_hyw-0.3.5.dist-info/RECORD +0 -9
@@ -1,419 +0,0 @@
1
- import asyncio
2
- import base64
3
- import httpx
4
- from typing import Any, List, Optional, Union, Callable
5
- from langchain_openai import ChatOpenAI
6
- from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage, AIMessage
7
- from arclet.entari import BasicConfModel, metadata, plugin_config
8
- from langchain.agents import create_agent
9
- from langchain.agents.middleware import AgentMiddleware, ToolCallLimitMiddleware
10
- from langchain_core.callbacks import BaseCallbackHandler
11
- from langchain_core.messages import BaseMessage
12
- from typing import Dict, Any, List
13
- from langchain.agents.middleware import LLMToolSelectorMiddleware
14
- from dataclasses import field
15
-
16
- from pydantic import SecretStr
17
- from satori.exception import ActionFailed
18
- from loguru import logger
19
-
20
- # 导入工具模块
21
- from .tools import (
22
- web_search,
23
- # single_word_web_search,
24
- jina_fetch_webpage,
25
- # nbnhhsh,
26
- set_global_search_engines,
27
- set_global_compressor_llm,
28
- )
29
-
30
- # 导入视觉分析模块
31
- from .vision import vision_expert_analysis
32
-
33
- class HywConfig(BasicConfModel):
34
- hyw_command_name: Union[str, List[str]] = "hyw"
35
-
36
- # AI配置 - 必需字段,无默认值
37
- text_llm_model_name: str
38
- text_llm_api_key: str
39
- text_llm_model_base_url: str
40
- text_llm_temperature: float = 0.4
41
- text_llm_enable_search: bool = False
42
-
43
- vision_llm_model_name: str
44
- vision_llm_api_key: str
45
- vision_llm_model_base_url: str
46
- vision_llm_temperature: float = 0.4
47
- vision_llm_enable_search: bool = False
48
-
49
- # 压缩器LLM配置 - 使用便宜模型
50
- compressor_llm_model_name: str = "qwen-flash"
51
- compressor_llm_api_key: str
52
- compressor_llm_model_base_url: str
53
- compressor_llm_temperature: float = 0.1
54
- compressor_llm_enable_search: bool = False
55
-
56
- search_engines: List[str] = field(default_factory=lambda: ["auto"])
57
-
58
- SYS_PROMPT = """
59
- 你是一个大语言模型驱动的智能解释器, 需要使用工具获取准确信息, 回答用户的问题.
60
-
61
- [时刻注意信息的完成度]
62
- - 当你拥有足够的信息时, 你需要开始进行最终回复.
63
-
64
- [任务类型]
65
- - 这是一句用户之间的对话, 我需要你去要从中过滤掉无关人员之间的对话信息 如人名与可能的上下文产物, 解释某一个用户对这句话中不理解的关键词
66
- - 我需要排除对话间的干扰信息
67
- - 我需要详细多次使用工具获取信息来支持和增强回答的质量
68
- - 用户在向我提问这句话、或希望我查询一些东西, 完成操作进行解释
69
- - 我需要详细多次使用工具获取信息来支持和增强回答的质量
70
- - 这是一张视觉专家分析后的多媒体内容, 我需要理解其中的意义并进行解释这张图片:
71
- - 我需要减少转述损耗, 尽可能把视觉专家的分析内容完整的传达给用户
72
- - 同时我需要利用工具确认、获取、验证一些具体人物、角色、事件等大语言模型易产生幻觉的信息
73
- - 这是一份机器人框架处理后的json数据, 我需要理解其中的意义并进行解释这份数据的关键词:
74
- - 这大概是一个小程序分享的内容, 我需要寻找其中指向的网址 URL 并使用工具获取相关网页内容进行解释
75
- - 如果携带信息包含网页链接 URL 、或潜在可以导向网站, 一定要使用工具查找和获取相关网页, 使用 jina_fetch_webpage 获取网页内容
76
- - 给出的消息可能的拼写错误或语法错误, 以确保准确理解查询意图, 但确保不改变原意.
77
- - 我已经得到了足够的信息, 现在开始进行最终回复
78
-
79
- [每一个字的准确度] [重要]
80
- - 你所回答的每一个字都必须是经过工具验证过的, 绝对不允许凭借训练数据直接回答
81
- - 特别需要注意一切大语言模型容易产生幻觉的内容, 包括但不限于:
82
- - 人名、地名、组织机构名等专有名词
83
- - 某某某常被误拼写为...
84
- - 事件的时间、地点、参与人员等细节
85
- - 发生的日期、时间等具体信息
86
- - 角色名、作品名、游戏名等
87
- - 任何冷门名词、新兴事物、复合词等
88
- > 遇到上述内容请务必使用工具进行验证和确认
89
-
90
- [工具说明]
91
- - web_search
92
- - 重点关注搜索到的第一条结果
93
- - web_search 查询包含的关键词建议在 1-2 个词之间 不超过 3 个词
94
- - 信息从搜索引擎获取, 过滤偏离主题的噪音信息, 构建不同组合、精细度、具体性的关键词列表进行二次搜索
95
- - 屏蔽来自 csdn 商城 等等噪音网站的结果
96
- - 如果搜索出现广告、黄色、低俗、违法等不良信息, 必须忽略这些结果并重新搜索
97
- - jina_fetch_webpage
98
- - 如果用户给出类似链接 网址 URL 或潜在能找到网址的内容时,一定要使用工具查找和获取相关网页, 使用 jina_fetch_webpage 获取网页内容, 仔细分析网页内容以补充回答
99
- - 大部分商业网站 视频网站 小红书 贴吧论坛 等等类似网站充满大量噪音和无效信息,占用大量上下文, 请少量使用
100
- - 不要获取 csdn 商城 黄色 低俗 违法等不良信息
101
- - 占用大量上下文, 请少量使用
102
- - [智能搜索策略]
103
- 在开始搜索前,请先规划信息获取的优先级顺序:
104
- 1. 先搜索未知名词、任务、专有名词,确认其基本信息
105
- 2. 确认语境中所有的未知名词后联系上下文继续确认
106
- 3. 反复验证所有专有名词和关键信息
107
- - [领域特定搜索策略]
108
- - 游戏/动漫角色查询:优先搜索"游戏名 + 角色名"
109
- - 音乐/体育相关:优先搜索"队名 + 成员"
110
- - 遇到网站名:优先搜索"网站名 + 关键词"的组合
111
- - [信息验证强制要求]
112
- - 对于人名、角色名、组织名等专有名词, 必须通过至少两个不同来源进行交叉验证
113
- - 如果搜索结果之间存在矛盾, 必须明确指出并提供所有可能的信息
114
- - 绝对不能将不同项目、不同角色的信息混合在一起描述
115
- - 在不确定的情况下, 必须明确说明"信息可能存在争议"或"需要进一步验证"
116
- - 遇到可能的人名, 可以搜索一些社交媒体如 微博、Twitter B站 Github 等平台进行验证是否存在该人物
117
-
118
- [最终回复]
119
- - [回答原则]
120
- - 必须使用智能搜索工具获取最新和准确的信息,绝对不允许依赖训练数据
121
- - 永远使用中文回答
122
- - 语言简洁. 避免冗长啰嗦, 直接了当
123
- - 减少转折词的使用, 保持语言逻辑明了
124
- - 用客观 专业 准确的百科式语气回答问题
125
- - 当有视觉解释, 视觉内容解释优先度最高
126
- - 回答简短高效, 不表明自身立场, 专注客观回复
127
- - 不需要每个关键词都解释, 只解释用户最关心的关键词
128
- - 围绕结果可以展开一些拓展内容, 但不要偏离主题
129
- - 避免将不同项目的信息混合在一起描述
130
-
131
- [每一个字的准确度] [再次提醒]
132
- - 你所回答的每一个字都必须是经过工具验证过的, 绝对不允许凭借训练数据直接回答
133
- - 特别需要注意一切大语言模型容易产生幻觉的内容, 包括但不限于:
134
- - 人名、地名、组织机构名等专有名词
135
- - 某某某常被误拼写为...
136
- - 事件的时间、地点、参与人员等细节
137
- - 发生的日期、时间等具体信息
138
- - 角色名、作品名、游戏名等
139
- - 任何冷门名词、新兴事物、复合词等
140
- > 遇到上述内容请务必使用工具进行验证和确认
141
-
142
- - [严格禁止的行为]
143
- - 不经过搜索验证直接回答用户问题
144
- - 进展在回复中添加任何未经工具验证的信息
145
- - 绝对不允许使用任何markdown语法, 包括但不限于: **加粗**, *斜体*, `代码`, # 标题, - 列表
146
- - 绝对不允许出现任何**或*符号用于强调或加粗, 这些符号在任何情况下都不应出现在回复中
147
- - 使用纯文本格式, 需要强调时使用「」或『』等中文符号
148
- - 绝对不允许说"并非一个通用技术术语或广为人知的...非广为人知的信息或通用技术术语" "根据搜索结果显示..." "目前未发现相关信息..."等无意义表述
149
-
150
- - [格式要求]
151
- 第一行: [Key] :: <关键词> | <关键词> <...>
152
- 第二行: >> [LLM Agent] :: {model_names}
153
- 第三行开始: <详细解释>
154
-
155
- """
156
-
157
- class AgentService:
158
- """AI服务类,管理文本和视觉LLM"""
159
-
160
- def __init__(self, config: "HywConfig"):
161
- self.config = config
162
- self._text_llm: Optional[ChatOpenAI] = None
163
- self._vision_llm: Optional[ChatOpenAI] = None
164
- self._planning_agent: Optional[Any] = None
165
-
166
-
167
- # 删除信息规划相关变量
168
-
169
- # 设置全局搜索引擎配置
170
- self._set_search_engine()
171
- self._init_models()
172
- self._init_agents()
173
- self._setup_compressor()
174
-
175
- def _set_search_engine(self):
176
- """设置搜索引擎配置"""
177
- set_global_search_engines(self.config.search_engines)
178
-
179
-
180
- def _init_models(self):
181
- """初始化LLM模型"""
182
- self._text_llm = ChatOpenAI(
183
- model=self.config.text_llm_model_name,
184
- api_key=SecretStr(self.config.text_llm_api_key),
185
- base_url=self.config.text_llm_model_base_url,
186
- temperature=self.config.text_llm_temperature,
187
- extra_body={"enable_search": self.config.text_llm_enable_search}
188
- )
189
-
190
- self._vision_llm = ChatOpenAI(
191
- model=self.config.vision_llm_model_name,
192
- api_key=SecretStr(self.config.vision_llm_api_key),
193
- base_url=self.config.vision_llm_model_base_url,
194
- temperature=self.config.vision_llm_temperature,
195
- extra_body={"enable_search": self.config.vision_llm_enable_search}
196
- )
197
-
198
-
199
- def _init_agents(self):
200
- """初始化专家Agent系统 - 使用 create_agent"""
201
- if self._text_llm is None:
202
- self._planning_agent = None
203
- return
204
-
205
- # 创建用于代理的LLM实例
206
- agent_llm = ChatOpenAI(
207
- model=self.config.text_llm_model_name,
208
- api_key=SecretStr(self.config.text_llm_api_key),
209
- base_url=self.config.text_llm_model_base_url,
210
- temperature=0.2, # 较低温度,保持规划的一致性
211
- extra_body={"enable_search": False}
212
- )
213
-
214
-
215
- # 定义所有工具
216
- all_tools = [web_search, jina_fetch_webpage]
217
-
218
- # 构建模型名称字符串
219
- model_names = self.config.text_llm_model_name
220
-
221
- system_prompt = SYS_PROMPT.format(model_names=model_names)
222
-
223
- # 记录提示词信息用于调试
224
- logger.info(f"[DEBUG] Tools count: {len(all_tools)}")
225
- logger.info(f"[DEBUG] Tools names: {[tool.name for tool in all_tools]}")
226
-
227
-
228
- # 使用 create_agent 创建生产就绪的代理实现
229
- self._planning_agent = create_agent(
230
- model=agent_llm,
231
- tools=all_tools,
232
- system_prompt=system_prompt,
233
- )
234
-
235
- logger.info(f"[DEBUG] Agent created successfully: {type(self._planning_agent)}")
236
-
237
- def _setup_compressor(self):
238
- """设置压缩器LLM"""
239
- # 创建压缩器LLM - 使用较低温度保证压缩质量
240
- compressor_llm = ChatOpenAI(
241
- model=self.config.compressor_llm_model_name,
242
- api_key=SecretStr(self.config.compressor_llm_api_key),
243
- base_url=self.config.compressor_llm_model_base_url,
244
- temperature=self.config.compressor_llm_temperature,
245
- extra_body={"enable_search": self.config.compressor_llm_enable_search}
246
- )
247
-
248
- # 设置全局压缩器LLM
249
- set_global_compressor_llm(compressor_llm)
250
- logger.info(f"[DEBUG] 压缩器LLM设置完成: {self.config.compressor_llm_model_name}")
251
-
252
-
253
- async def unified_completion(self, content: str, images: Optional[List[bytes]] = None) -> Any:
254
- """统一入口 - 使用 LangChain 自动工具执行(带内容过滤重试)"""
255
- if self._planning_agent is None:
256
- raise RuntimeError("规划专家未初始化")
257
-
258
- # 内容过滤重试机制
259
- max_inspection_retries = 3
260
- retry_count = 0
261
-
262
- while retry_count <= max_inspection_retries:
263
- try:
264
- result = await self._unified_completion_internal(content, images)
265
-
266
- # 检查返回结果中是否包含内容审查失败的信息
267
- if hasattr(result, 'content') and "内容审查 | 审查失败" in result.content and retry_count < max_inspection_retries:
268
- retry_count += 1
269
- logger.warning(f"[内容审查失败] 第 {retry_count}/{max_inspection_retries} 次重试")
270
- # 继续下一次尝试
271
- continue
272
-
273
- return result
274
-
275
- except Exception as e:
276
- # 检查是否是内容审查相关的异常
277
- error_msg = str(e)
278
- if ("data_inspection_failed" in error_msg or "inappropriate content" in error_msg.lower()) and retry_count < max_inspection_retries:
279
- retry_count += 1
280
- logger.warning(f"[内容审查失败] 异常错误,第 {retry_count}/{max_inspection_retries} 次重试: {error_msg}")
281
- # 继续下一次尝试
282
- continue
283
- else:
284
- # 其他异常或超过重试次数,直接返回
285
- raise
286
-
287
- async def _unified_completion_internal(self, content: str, images: Optional[List[bytes]] = None) -> Any:
288
- """统一入口内部实现 - 使用 create_agent 自动工具执行"""
289
- logger.info(f"[DEBUG] 开始处理内容: {content[:100]}...")
290
- if self._planning_agent is None:
291
- logger.error("[DEBUG] 规划专家未初始化")
292
- raise RuntimeError("规划专家未初始化")
293
-
294
- # 开始计时 - 包含整个处理流程
295
- import time
296
- total_start_time = time.time()
297
-
298
- # 收集专家信息和使用的模型
299
- expert_info = []
300
- model_names = self.config.text_llm_model_name
301
- logger.info(f"[DEBUG] 使用模型: {model_names}")
302
-
303
- # 1. 如果有图片,先调用视觉专家进行分析
304
- vision_time = 0.0
305
- if images:
306
- model_names += f"[{self.config.vision_llm_model_name}]"
307
-
308
- vision_start_time = time.time()
309
- for i, image_data in enumerate(images):
310
- if self._vision_llm is None:
311
- vision_result = "视觉专家不可用"
312
- else:
313
- vision_result = await vision_expert_analysis(self._vision_llm, image_data, content)
314
- expert_info.append(f"视觉专家分析{i+1}: {vision_result}")
315
- vision_time = time.time() - vision_start_time
316
- full_context = "\n".join([f"图片{i+1}分析结果: {res}" for i, res in enumerate(expert_info)]) + f"\n对话携带信息: {content}"
317
- logger.info("content:", content)
318
- else:
319
- # 2. 构建完整上下文
320
- context_parts = [f"文本信息: {content}"]
321
- if expert_info:
322
- context_parts.extend(expert_info)
323
-
324
- full_context = "\n".join(context_parts)
325
-
326
- try:
327
- # 3. 使用传统create_agent方式处理
328
- logger.info("=== 使用 create_agent 开始处理 ===")
329
-
330
- # 构建用户消息 - 强调必须使用工具
331
- user_message = f"""[当前情况]
332
- {full_context}
333
-
334
- [开始]
335
- - 合理规划使用工具的顺序和频率, 避免滥用工具与资源
336
- - 请根据以上信息进行分析和回复
337
- """
338
-
339
- # 调用代理,create_agent 会自动处理工具调用循环
340
- result = await self._planning_agent.ainvoke({
341
- "messages": [HumanMessage(content=user_message)]
342
- })
343
-
344
-
345
- # 详细检查结果结构
346
- if isinstance(result, dict):
347
- if 'messages' in result:
348
- logger.info(f"[DEBUG] 字典中的消息数量: {len(result['messages'])}")
349
- for i, msg in enumerate(result['messages']):
350
- msg_type = type(msg).__name__
351
- content_len = len(str(msg.content)) if hasattr(msg, 'content') else 0
352
- tool_calls_count = len(msg.tool_calls) if hasattr(msg, 'tool_calls') and msg.tool_calls else 0
353
- elif hasattr(result, 'messages'):
354
- logger.info(f"[DEBUG] 对象消息数量: {len(result.messages)}")
355
- for i, msg in enumerate(result.messages):
356
- msg_type = type(msg).__name__
357
- content_len = len(str(msg.content)) if hasattr(msg, 'content') else 0
358
- tool_calls_count = len(msg.tool_calls) if hasattr(msg, 'tool_calls') and msg.tool_calls else 0
359
- logger.info(f"[DEBUG] 对象消息{i}: {msg_type}, 内容长度: {content_len}, 工具调用数: {tool_calls_count}")
360
- else:
361
- logger.warning(f"[DEBUG] 未知结果结构,长度: {len(str(result))} 字符")
362
-
363
- # 计算总耗时
364
- total_duration = time.time() - total_start_time
365
-
366
- # 生成统计信息
367
- elapsed_parts = [f"total: {total_duration:.1f}s"]
368
- if vision_time > 0:
369
- elapsed_parts.append(f"vision: {vision_time:.1f}s")
370
- elapsed_line = f"[Elapsed] :: {' | '.join(elapsed_parts)}"
371
-
372
- # 提取工具使用统计 - 使用列表推导简化
373
- messages = result.get('messages', []) if isinstance(result, dict) else (result.messages if hasattr(result, 'messages') else [])
374
- tool_calls = [call for msg in messages if hasattr(msg, 'tool_calls') and msg.tool_calls for call in msg.tool_calls]
375
- tool_stats = {name: {'count': len([call for call in tool_calls if call.get('name') == name])}
376
- for name in set(call.get('name', 'unknown') for call in tool_calls)}
377
-
378
- if tool_stats:
379
- tool_parts = [f"web_search[{', '.join(self.config.search_engines)}]: {stats['count']}" if name == 'web_search' else f"{name}: {stats['count']}" for name, stats in tool_stats.items()]
380
- tool_stats_line = f"[Use Tools] :: {', '.join(tool_parts)}"
381
- else:
382
- tool_stats_line = ""
383
-
384
- # 简化结果处理 - 直接获取最后一条消息
385
- messages = result.get('messages', []) if isinstance(result, dict) else (result.messages if hasattr(result, 'messages') else [])
386
-
387
- # 使用列表推导找到最后一条AI消息(有内容且无工具调用)
388
- ai_messages = [msg for msg in messages if hasattr(msg, 'content') and msg.content and not (hasattr(msg, 'tool_calls') and msg.tool_calls)]
389
-
390
- if ai_messages:
391
- ai_content = ai_messages[-1].content
392
- stats_text = f"\n{tool_stats_line}\n{elapsed_line}" if tool_stats_line else f"\n{elapsed_line}"
393
- return AIMessage(content=ai_content + stats_text)
394
- else:
395
- fallback_content = f"[Key] :: 信息处理 | 处理完成\n>> [LLM Agent] :: {model_names}\n处理完成,但未生成有效回复。\n{elapsed_line}"
396
- return AIMessage(content=fallback_content)
397
-
398
- except Exception as e:
399
- # 获取统计信息和耗时
400
- total_duration = time.time() - total_start_time
401
- # 内联统计信息生成
402
- elapsed_parts = [f"total: {total_duration:.1f}s"]
403
- if vision_time > 0:
404
- elapsed_parts.append(f"vision: {vision_time:.1f}s")
405
- stats_text = f"\n[Elapsed] :: {' | '.join(elapsed_parts)}"
406
-
407
- # 内容过滤错误处理
408
- error_msg = str(e)
409
- is_inspection_error = "data_inspection_failed" in error_msg or "inappropriate content" in error_msg.lower()
410
-
411
- if is_inspection_error:
412
- logger.warning(f"内容审查失败: {error_msg}")
413
- fallback_content = f"[Key] :: 内容审查 | 审查失败\n>> [LLM Agent] :: {model_names}\n内容审查失败,请重新尝试。{stats_text}"
414
- return AIMessage(content=fallback_content)
415
-
416
- # 通用错误处理
417
- logger.error(f"create_agent 执行失败: {e}")
418
- fallback_content = f"[Key] :: 系统异常 | 执行错误\n>> [LLM Agent] :: {model_names}\n系统异常: {error_msg}\n{stats_text}"
419
- return AIMessage(content=fallback_content)
@@ -1,59 +0,0 @@
1
- from langchain_openai import ChatOpenAI
2
- from langchain_core.messages import SystemMessage
3
- from loguru import logger
4
-
5
-
6
- class WebpageCompressor:
7
- """网页内容压缩器"""
8
-
9
- def __init__(self, llm: ChatOpenAI):
10
- self.llm = llm
11
-
12
- async def compress_webpage(self, webpage_content: str, url: str) -> str:
13
- """使用LLM压缩网页内容"""
14
- if self.llm is None:
15
- raise RuntimeError("压缩器LLM未初始化")
16
-
17
- logger.info(f"[压缩器] 开始压缩网页内容,原始长度: {len(webpage_content)} 字符")
18
- logger.debug(f"[压缩器] 网页URL: {url}")
19
-
20
- # 如果内容超过1万字符,直接截取前1万字符
21
- if len(webpage_content) > 10000:
22
- logger.warning(f"[压缩器] 网页内容超过1万字符 ({len(webpage_content)}字符),截取前1万字符")
23
- webpage_content = webpage_content[:10000] + "\n...(内容过长,已截断)"
24
-
25
- compress_prompt = f"""你是网页内容提取专家,需要从网页中提取核心信息。
26
-
27
- [任务]
28
- 从以下网页内容中提取主要信息,去除导航、广告、样板文字等噪音。
29
-
30
- [网页URL]
31
- {url}
32
-
33
- [网页内容]
34
- {webpage_content}
35
-
36
- [提取要求]
37
- 1. 提取网页的主要内容和核心信息
38
- 2. 保留关键段落、标题、列表
39
- 3. 去除导航栏、页脚、广告、社交媒体按钮等
40
- 4. 保留重要的数据、日期、引用
41
- 5. 如果是新闻/文章,提取标题、作者、发布时间、正文摘要
42
- 6. 如果是百科,s提取定义、关键属性、分类信息
43
- 7. 保持信息的逻辑结构
44
-
45
- [输出格式]
46
- 网页: {url}
47
- ---
48
- <压缩后的核心内容>
49
- """
50
-
51
- logger.info(f"[压缩器] 调用LLM压缩,prompt长度: {len(compress_prompt)} 字符")
52
- result = await self.llm.ainvoke([
53
- SystemMessage(content=compress_prompt)
54
- ])
55
-
56
- compressed = str(result.content) if hasattr(result, 'content') else str(result)
57
- logger.info(f"[压缩器] 压缩完成: {len(webpage_content)} -> {len(compressed)} 字符 (压缩率: {100 - len(compressed)*100//len(webpage_content)}%)")
58
- logger.debug(f"[压缩器] 压缩结果预览: {compressed[:300]}...")
59
- return compressed