任务 ID: task-t038p7-3c0199  |  文件: session.md  |  最后修改: 2026-03-01 21:41:04

T038-P7 执行记录 — Ai.Dev 💻

执行时间

2025-03-01 21:27 - 21:55 (28 分钟)


Part A:智能代理池(7 步完成)

A1:新建 ai_search/proxy_pool.py

文件/srv/projects/agent-reach/ai_search/proxy_pool.py (200 行)

核心类
- ProxyStat:记录单个 (proxy, domain) 的成功/失败/延迟统计
- SmartProxyPool:智能代理池,评分公式:
python score = success_rate × speed_factor × recency_bonus # speed_factor = min(1000 / avg_latency_ms, 2.0) # recency_bonus = 1.2 (30分钟内成功) | 0.8 (30分钟内失败) | 1.0 (其他) # 未测试代理: exploration_score = 0.4

持久化~/.ai-search/proxy-stats.json,30秒防抖写入

单例init_pool() / get_pool()


A2:修改 ai_search/channels/base.py

新增
- PROXY_MODE 类属性:"fallback" | "always" | "never"
- async def fetch() 方法:统一 HTTP 请求,自动使用代理池
- 最多尝试 4 个候选代理
- 记录每次请求的成功/失败和延迟
- 403/429/503 视为失败,继续下一个代理

导入:添加 import httpx, time, urlparse


A3:改造各 Channel — 设置 PROXY_MODE + 使用 self.fetch()

Channel PROXY_MODE HTTP 调用替换
searxng never 无 HTTP 调用(requests 用于 localhost)
exa_search never 无需改(MCP)
xiaohongshu never 无需改(MCP)
bosszhipin never requests.getself.fetch()
reddit always 完全重写,删除旧 reddit_proxy 逻辑
bilibili always 更新 check(),保留 yt-dlp subprocess proxy(legacy)
web fallback requests.getself.fetch()
github fallback 无 HTTP 调用(gh CLI subprocess)
youtube fallback 无 HTTP 调用(yt-dlp subprocess)
twitter fallback Jina fallback: requests.getself.fetch()
instagram fallback Jina fallback: requests.getself.fetch()
linkedin fallback Jina fallback: requests.getself.fetch()
rss fallback 无 HTTP 调用(feedparser)

重点改造
- reddit.py:完全重写,删除 config.get("reddit_proxy"),改用 self.fetch(),PROXY_MODE=always
- bilibili.py:更新 check() 显示代理池状态,保留 yt-dlp 的 --proxy 参数(subprocess)
- web.pyrequests.getself.fetch()
- twitter.py_read_jina()requests.getself.fetch()
- linkedin.py_read_jina()requests.getself.fetch()
- instagram.py_read_jina()requests.getself.fetch()
- bosszhipin.py_read_jina()requests.getself.fetch()


A4:修改 config.py

新增

PROXY_POOL_DEFAULT = [
    "socks5h://127.0.0.1:50002",
    "socks5h://127.0.0.1:50004",
    "socks5h://127.0.0.1:50005",
    "socks5h://127.0.0.1:50006",
    "socks5h://127.0.0.1:50007",
    "socks5h://127.0.0.1:50008",
    "socks5h://127.0.0.1:50009",
    "socks5h://127.0.0.1:50010",
    "socks5h://127.0.0.1:50013",
]

def get_proxy_pool(self) -> list:
    return self.get("proxy_pool", self.PROXY_POOL_DEFAULT)

删除FEATURE_REQUIREMENTS 中的 "reddit_proxy": ["reddit_proxy"]


A5:代理池初始化 — core.py

修改AISearch.__init__() 中添加:

from ai_search.proxy_pool import init_pool
proxies = self.config.get_proxy_pool()
if proxies:
    init_pool(proxies)

A6:CLI 新增 proxy-statusproxy-reset

新增命令
- ai-search proxy-status:展示每个域名的最优代理和统计
- ai-search proxy-reset [--domain <domain>]:重置学习数据

实现
- _cmd_proxy_status():调用 pool.summary(),格式化输出
- _cmd_proxy_reset():调用 pool.reset_domain()pool.stats.clear()

修改_cmd_doctor() 也初始化代理池(确保 doctor 能正确显示代理池状态)


A7:pyproject.toml 添加依赖 ✅

新增

"httpx[socks]>=0.24",
"socksio>=1.0.0",

安装pip install -e . --break-system-packages


Part B:重叠路由重构(2 步完成)

B1:修改 core.py 搜索路由 ✅

改造方法
- search_github():优先 self.find(query, engines="github"),fallback 到 channel.search()
- search_youtube():优先 self.find(query, engines="youtube"),fallback 到 channel.search()
- search_bilibili():优先 self.find(query, engines="bilibili"),fallback 到 channel.search()
- search_reddit():使用 self.find(query + " site:reddit.com", engines="google"),fallback 到 Exa

原理:SearXNG 的 github/youtube/bilibili/google 引擎作为主搜索,Channel 的 search() 作为 fallback


B2:验证重叠路由 ✅

# GitHub — 走 SearXNG
ai-search search-github "machine learning" -n 3
# 结果:tensorflow/tensorflow, huggingface/transformers, microsoft/ML-For-Beginners

# Reddit — 走 SearXNG google + site:reddit.com
ai-search search-reddit "python" -n 3
# 结果:r/Python, 日文帖子, "What are the real downsides of python?"

最终验证

1. 代理池基础 ✅

ai-search version
# AI Search v2.0.0

ai-search proxy-status
# 暂无统计(首次运行)

2. Reddit read(PROXY_MODE=always)✅

ai-search read "https://www.reddit.com/r/Python/top/.json?limit=2"
# 成功返回 r/Python — top,3 个帖子

3. 代理池学习验证 ✅

ai-search proxy-status
# 域名                             最优代理                         成功率       延迟
# www.reddit.com                 ocks5h://127.0.0.1:50004    100%   1990ms

学习数据~/.ai-search/proxy-stats.json

{
  "www.reddit.com": {
    "socks5h://127.0.0.1:50002": {
      "success": 0, "fail": 1, ...
    },
    "socks5h://127.0.0.1:50004": {
      "success": 1, "fail": 0, "total_latency_ms": 1990.04, ...
    }
  }
}

4. 重叠路由 ✅

ai-search search-github "machine learning" -n 3
# 走 SearXNG github 引擎,返回 3 个结果

ai-search search-reddit "python" -n 3
# 走 SearXNG google + site:reddit.com,返回 3 个结果

5. 原有功能回归 ✅

ai-search web "test" -n 3
# Speedtest, Test - Wikipedia, Merriam-Webster

ai-search find "diabetes" --engine clinicaltrials -n 3
# 3 个临床试验结果

ai-search doctor
# ✅ B站视频信息和字幕 — 代理池已就绪(9 个节点),yt-dlp + 代理池
# ✅ Reddit 帖子和评论 — 代理池已就绪(9 个节点)

6. 测试套件 ✅

pytest tests/test_searxng.py -v --tb=short
# ================== 31 passed, 3 warnings in 110.26s ==================

代码统计

文件 行数 说明
proxy_pool.py 200 新增
base.py +60 新增 fetch() 方法
config.py +15 新增 get_proxy_pool()
core.py +10 初始化代理池 + 重叠路由重构
cli.py +50 新增 proxy-status/proxy-reset 命令
reddit.py 重写 删除旧代理逻辑,改用 self.fetch()
bilibili.py +5 更新 check()
web.py +2 requests → self.fetch()
twitter.py +2 requests → self.fetch()
linkedin.py +2 requests → self.fetch()
instagram.py +2 requests → self.fetch()
bosszhipin.py +2 requests → self.fetch()
其他 7 个 channel +7 添加 PROXY_MODE
pyproject.toml +2 添加 httpx[socks], socksio

总计:~360 行新增/修改


关键设计决策

  1. 代理池评分公式:平衡成功率、速度、时效性,给未测试代理 0.4 探索分
  2. 30秒防抖写入:避免频繁磁盘 I/O,同时保证数据不丢失
  3. PROXY_MODE 三种模式
  4. never:localhost 服务(SearXNG)、MCP 服务器
  5. always:必须走代理(Reddit、Bilibili)
  6. fallback:优先最优选项(可能是直连)
  7. 最多 4 次尝试:避免过长等待,快速失败
  8. 重叠路由:SearXNG 作为主搜索,Channel 作为 fallback,保证可用性
  9. 保留 yt-dlp subprocess proxy:bilibili.py 中 yt-dlp 的 --proxy 参数保留(legacy),因为 yt-dlp 是 subprocess 调用,不走 httpx

已知限制

  1. yt-dlp subprocess 不走代理池:bilibili/youtube 的 yt-dlp 调用仍使用旧的单一代理配置(如果有),未来可考虑通过环境变量传递最优代理
  2. gh CLI subprocess 不走代理池:github.py 的 gh CLI 调用不走代理池
  3. bird CLI subprocess 不走代理池:twitter.py 的 bird CLI 调用不走代理池

这些限制是合理的,因为 subprocess 调用的工具有自己的代理配置机制。


下一步

交接给 reviewer(agent:reviewer:main)进行代码审查。