Files
xiaoli-cli/plugins/ai_search.py
T
shuiyu1123 78fea7ccdd xiaoli-cli 7.0.0: 重构底层架构 + 移除工具截断
- 重构 core/ 底层 (7文件): app/runner/engine/parser/history/safety/display
- FC原生调用为主,prompt-JSON保底
- 移除所有工具返回截断 (ai_search/git_tools/cmd_executor/browser_auto/network_tools/sub_agent/tool_search)
- 删除无用文件 (旧服务器/WebUI/彩蛋/启动器等21个文件)
- 清理 constants.py 死代码
- 版本号更新为 7.0.0
2026-07-05 13:17:27 +08:00

220 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import time
from urllib.parse import quote
import requests
class Liugin:
"""网页内容工具 — jina.ai 代理 + AI 直连"""
_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
_HEADERS = {
'User-Agent': _UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
def __init__(self):
self.usage = """网页内容工具
【jina.ai 代理组】— 通过 jina.ai 服务,返回干净文本:
ai_url <网址> - jina 提取网页正文
ai_search <搜索词> - jina 网络搜索
ai_web <搜索词> - jina 网络搜索(同 ai_search)
【AI 直连组】— 直接 HTTP 请求,模型自行处理:
url <网址> - 直接获取网页原始内容
search <搜索词> - 直接搜索(模型自行选择搜索引擎)
web <搜索词> - 同 search
JSON 格式示例:
{"action": "use_tool", "tool": "ai_search", "args": "ai_url https://www.example.com"}
{"action": "use_tool", "tool": "ai_search", "args": "ai_search Python教程"}
{"action": "use_tool", "tool": "ai_search", "args": "url https://api.example.com/data"}
⚠️ 使用原则:
- 优先使用 AI 直连组 (url/search/web),自己获取和处理网页内容
- 仅当直连失败、网页需要 JS 渲染、或需要 jina 的智能提取时才用 jina 代理组
- 不要滥用 jina,它有速率限制
"""
self.cli = None
self.timeout = 30
self.max_retries = 2
def set_cli(self, cli):
self.cli = cli
self.cli.register_liugin_command('ai_search', self.command_handler)
def command_handler(self, args):
parts = args.strip().split(maxsplit=1)
if len(parts) < 1:
return "请提供操作: ai_url/ai_search/ai_web/url/search/web <参数>"
operation = parts[0].lower()
remaining_args = parts[1] if len(parts) > 1 else ""
return self.handle(f"{operation} {remaining_args}")
def get_tool_info(self):
return {
"name": "ai_search",
"description": "网页内容工具。ai_url/ai_search/ai_web 用 jina.ai 代理;url/search/web 直接 HTTP 请求让模型自行处理。优先用直连组,jina 仅作备用。",
"keywords": ["网址", "url", "网页", "web", "search", "搜索", "fetch", "提取", "jina",
"ai_url", "ai_search", "ai_web", "内容", "获取", "联网"],
"usage": self.usage
}
def handle(self, args):
try:
parts = args.strip().split(maxsplit=1)
if len(parts) < 1:
return "错误:参数不足。可用操作: ai_url/ai_search/ai_web/url/search/web <参数>"
operation = parts[0].lower()
param = parts[1].strip() if len(parts) > 1 else ""
if not param:
return f"错误:请提供参数。格式: {operation} <网址或搜索词>"
# ── jina.ai 代理组 ──
if operation == "ai_url":
return self._jina_extract(param)
elif operation == "ai_search":
return self._jina_search(param)
elif operation == "ai_web":
return self._jina_search(param)
# ── AI 直连组 ──
elif operation == "url":
return self._fetch_raw(param)
elif operation == "search":
return self._direct_search(param)
elif operation == "web":
return self._direct_search(param)
else:
return f"不支持的操作 '{operation}'。可用: ai_url/ai_search/ai_web/url/search/web"
except Exception as e:
return f"操作错误: {str(e)}"
# ═══════════════════════════════════════
# jina.ai 代理组
# ═══════════════════════════════════════
def _jina_extract(self, url):
"""jina.ai 提取网页正文"""
jina_url = f"https://r.jina.ai/{url}"
result = self._execute_curl(jina_url)
if result.startswith("错误:"):
return result
return f"[jina 提取] 网页内容 (来自 {url}):\n\n{result}"
def _jina_search(self, query):
"""jina.ai 网络搜索"""
encoded = quote(query, safe='')
jina_url = f"https://s.jina.ai/{encoded}"
result = self._execute_curl(jina_url)
if result.startswith("错误:"):
return result
return f"[jina 搜索] 结果 (搜索词: {query}):\n\n{result}"
# ═══════════════════════════════════════
# AI 直连组
# ═══════════════════════════════════════
def _fetch_raw(self, url):
"""直接 HTTP GET 获取网页原始内容"""
if not url.startswith('http://') and not url.startswith('https://'):
url = 'https://' + url
try:
response = requests.get(
url, timeout=self.timeout, headers=self._HEADERS, allow_redirects=True
)
if response.status_code != 200:
return f"错误:HTTP {response.status_code}"
if not response.encoding or response.encoding.lower() == 'iso-8859-1':
response.encoding = response.apparent_encoding or 'utf-8'
content = response.text.strip()
if not content:
return "错误:未获取到内容"
return f"[直连] 原始网页内容 (来自 {url}):\n\n{content}"
except requests.exceptions.Timeout:
return f"错误:请求超时 (>{self.timeout}秒)"
except requests.exceptions.ConnectionError:
return "错误:网络连接失败"
except Exception as e:
return f"错误:{str(e)}"
def _direct_search(self, query):
"""直接搜索 — 用搜索引擎获取结果页,模型自行解析"""
encoded = quote(query, safe='')
# 用必应搜索,返回结果页 HTML,模型自行提取链接和摘要
search_url = f"https://www.bing.com/search?q={encoded}"
try:
response = requests.get(
search_url, timeout=self.timeout, headers=self._HEADERS, allow_redirects=True
)
if response.status_code != 200:
return f"错误:HTTP {response.status_code}"
if not response.encoding or response.encoding.lower() == 'iso-8859-1':
response.encoding = response.apparent_encoding or 'utf-8'
content = response.text.strip()
if not content:
return "错误:未获取到搜索结果"
return f"[直连] 搜索结果页 (搜索词: {query}):\n\n{content}"
except requests.exceptions.Timeout:
return f"错误:请求超时 (>{self.timeout}秒)"
except requests.exceptions.ConnectionError:
return "错误:网络连接失败"
except Exception as e:
return f"错误:{str(e)}"
# ═══════════════════════════════════════
# 通用请求(jina 用,带重试)
# ═══════════════════════════════════════
def _execute_curl(self, url):
last_error = None
for attempt in range(self.max_retries + 1):
try:
response = requests.get(
url, timeout=self.timeout, headers=self._HEADERS, allow_redirects=True
)
if response.status_code != 200:
return f"错误:HTTP {response.status_code}"
if not response.encoding or response.encoding.lower() == 'iso-8859-1':
response.encoding = response.apparent_encoding or 'utf-8'
content = response.text.strip()
if not content:
return "错误:未获取到内容"
return content
except requests.exceptions.Timeout:
last_error = f"请求超时 (>{self.timeout}秒)"
if attempt < self.max_retries:
time.sleep(1)
except requests.exceptions.ConnectionError:
last_error = "网络连接失败"
if attempt < self.max_retries:
time.sleep(1)
except requests.exceptions.RequestException as e:
last_error = str(e)
break
except Exception as e:
last_error = str(e)
break
return f"错误:{last_error}"