yaping-aether 0.9.0__tar.gz

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.
Files changed (40) hide show
  1. yaping_aether-0.9.0/.env.example +39 -0
  2. yaping_aether-0.9.0/.github/workflows/test.yml +30 -0
  3. yaping_aether-0.9.0/.gitignore +33 -0
  4. yaping_aether-0.9.0/LICENSE +59 -0
  5. yaping_aether-0.9.0/PKG-INFO +120 -0
  6. yaping_aether-0.9.0/README.md +92 -0
  7. yaping_aether-0.9.0/pyproject.toml +49 -0
  8. yaping_aether-0.9.0/run.py +14 -0
  9. yaping_aether-0.9.0/src/aether/__init__.py +3 -0
  10. yaping_aether-0.9.0/src/aether/agents/__init__.py +0 -0
  11. yaping_aether-0.9.0/src/aether/agents/create_agent.py +28 -0
  12. yaping_aether-0.9.0/src/aether/agents/file_agent.py +27 -0
  13. yaping_aether-0.9.0/src/aether/agents/search_agent.py +26 -0
  14. yaping_aether-0.9.0/src/aether/cache/__init__.py +0 -0
  15. yaping_aether-0.9.0/src/aether/cache/semantic_cache.py +140 -0
  16. yaping_aether-0.9.0/src/aether/checkpoint.py +103 -0
  17. yaping_aether-0.9.0/src/aether/compact.py +112 -0
  18. yaping_aether-0.9.0/src/aether/config.py +244 -0
  19. yaping_aether-0.9.0/src/aether/controller.py +100 -0
  20. yaping_aether-0.9.0/src/aether/learn.py +69 -0
  21. yaping_aether-0.9.0/src/aether/main.py +700 -0
  22. yaping_aether-0.9.0/src/aether/mcp.py +76 -0
  23. yaping_aether-0.9.0/src/aether/memory.py +128 -0
  24. yaping_aether-0.9.0/src/aether/modes.py +33 -0
  25. yaping_aether-0.9.0/src/aether/sandbox.py +91 -0
  26. yaping_aether-0.9.0/src/aether/session.py +84 -0
  27. yaping_aether-0.9.0/src/aether/skill.py +76 -0
  28. yaping_aether-0.9.0/src/aether/supervisor.py +113 -0
  29. yaping_aether-0.9.0/src/aether/toolkit.py +78 -0
  30. yaping_aether-0.9.0/src/aether/tools/__init__.py +0 -0
  31. yaping_aether-0.9.0/src/aether/tools/create_tools.py +90 -0
  32. yaping_aether-0.9.0/src/aether/tools/file_tools.py +120 -0
  33. yaping_aether-0.9.0/src/aether/tools/git_tools.py +101 -0
  34. yaping_aether-0.9.0/src/aether/tools/project_tools.py +161 -0
  35. yaping_aether-0.9.0/src/aether/tools/search_tools.py +90 -0
  36. yaping_aether-0.9.0/src/aether/usage.py +93 -0
  37. yaping_aether-0.9.0/src/aether/ux.py +63 -0
  38. yaping_aether-0.9.0/src/aether/vscode.py +34 -0
  39. yaping_aether-0.9.0/tests/__init__.py +0 -0
  40. yaping_aether-0.9.0/tests/test_agents.py +92 -0
@@ -0,0 +1,39 @@
1
+ # Aether 配置
2
+ # 复制此文件为 .env 并填入你的 API Key
3
+
4
+ # ===== 模型配置 =====
5
+ # 主管模型(用于任务拆解和调度,建议用最强的)
6
+ SUPERVISOR_MODEL=dashscope/qwen-max
7
+ SUPERVISOR_API_KEY=your_qwen_api_key_here
8
+
9
+ # 专家默认模型(可以每个专家用不同的,这里是默认值)
10
+ WORKER_MODEL=deepseek/deepseek-chat
11
+ WORKER_API_KEY=your_deepseek_api_key_here
12
+
13
+ # 智能路由:每个专家可配独立模型(读文件用便宜的,写代码用强的)
14
+ # FILE_MODEL=dashscope/qwen-turbo ← 文件操作走便宜模型
15
+ # SEARCH_MODEL=deepseek/deepseek-chat
16
+ # CREATE_MODEL=dashscope/qwen-max ← 创作走强模型
17
+
18
+ # 如果所有模型共用同一个 API 提供商,可以用这个简化配置
19
+ # DEFAULT_MODEL=dashscope/qwen-max
20
+ # DEFAULT_API_KEY=your_api_key_here
21
+
22
+ # ===== 可选配置 =====
23
+ # 计划模型(可选,不设则共用主管模型。可设为更便宜的模型做计划,省 Token)
24
+ # PLAN_MODEL=dashscope/qwen-turbo
25
+
26
+ # 默认执行模式: auto(直接执行), plan(先出计划), step(逐步确认)
27
+ DEFAULT_MODE=auto
28
+
29
+ # 最大步数(主管 Agent 最多执行多少步)
30
+ MAX_STEPS=20
31
+
32
+ # 日志级别: DEBUG, INFO, WARNING, ERROR
33
+ LOG_LEVEL=INFO
34
+
35
+ # 缓存开关(MVP 阶段默认关闭)
36
+ CACHE_ENABLED=false
37
+
38
+ # 记忆系统(默认开启)
39
+ MEMORY_ENABLED=true
@@ -0,0 +1,30 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [master, main]
6
+ pull_request:
7
+ branches: [master, main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Setup Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ pip install -e .
27
+ pip install pytest
28
+
29
+ - name: Run tests
30
+ run: python -m pytest tests/ -v --tb=short
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+
9
+ # Virtualenv
10
+ .venv/
11
+ venv/
12
+
13
+ # Environment
14
+ .env
15
+
16
+ # IDE
17
+ .vscode/
18
+ .idea/
19
+ *.swp
20
+ *.swo
21
+
22
+ # OS
23
+ .DS_Store
24
+ Thumbs.db
25
+
26
+ # Cache
27
+ *.cache/
28
+ .cache/
29
+
30
+ # Test
31
+ .pytest_cache/
32
+ htmlcov/
33
+ .coverage
@@ -0,0 +1,59 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "You" (or "Your") shall mean an individual or Legal Entity
16
+ exercising permissions granted by this License.
17
+
18
+ "Source" form shall mean the preferred form for making modifications,
19
+ including but not limited to software source code, documentation
20
+ source, and configuration files.
21
+
22
+ "Object" form shall mean any form resulting from mechanical
23
+ transformation or translation of a Source form, including but
24
+ not limited to compiled object code, generated documentation,
25
+ and conversions to other media types.
26
+
27
+ "Work" shall mean the work of authorship, whether in Source or
28
+ Object form, made available under the License, as indicated by a
29
+ copyright notice that is included in or attached to the work.
30
+
31
+ 2. Grant of Copyright License. Subject to the terms and conditions of
32
+ this License, each Contributor hereby grants to You a perpetual,
33
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
34
+ copyright license to reproduce, prepare Derivative Works of,
35
+ publicly display, publicly perform, sublicense, and distribute the
36
+ Work and such Derivative Works in Source or Object form.
37
+
38
+ 3. Grant of Patent License. Subject to the terms and conditions of
39
+ this License, each Contributor hereby grants to You a perpetual,
40
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
41
+ (except as stated in this section) patent license to make, have made,
42
+ use, offer to sell, sell, import, and otherwise transfer the Work.
43
+
44
+ 4. Redistribution. You may reproduce and distribute copies of the
45
+ Work or Derivative Works thereof in any medium, with or without
46
+ modifications, and in Source or Object form, provided that You
47
+ meet the following conditions:
48
+
49
+ (a) You must give any other recipients of the Work or
50
+ Derivative Works a copy of this License; and
51
+
52
+ (b) You must cause any modified files to carry prominent notices
53
+ stating that You changed the files; and
54
+
55
+ (c) You must retain, in the Source form of any Derivative Works
56
+ that You distribute, all copyright, patent, trademark, and
57
+ attribution notices from the Source form of the Work.
58
+
59
+ See the full license at: http://www.apache.org/licenses/LICENSE-2.0
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: yaping-aether
3
+ Version: 0.9.0
4
+ Summary: 1+N 多模态 Agent 框架 — 面向国产大模型的开源 AI 助手
5
+ Project-URL: Homepage, https://github.com/AQ-july/aether
6
+ Project-URL: Repository, https://github.com/AQ-july/aether.git
7
+ Project-URL: Issues, https://github.com/AQ-july/aether/issues
8
+ Author: Aether Contributors
9
+ License: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: agent,ai-assistant,chinese-llm,cli,llm,multi-agent,smolagents
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: litellm>=1.60.0
22
+ Requires-Dist: python-dotenv>=1.0.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Requires-Dist: smolagents>=1.20.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # 🌌 Aether v0.9.0
30
+
31
+ **1+N 多模态 Agent 框架** — 面向国产大模型的开源 AI 助手。
32
+
33
+ > 一个主管调度多个专家,自动拆解和执行复杂任务。终端里跑,VS Code 里用,支持 100+ 模型。
34
+
35
+ ## 🚀 安装
36
+
37
+ ```bash
38
+ pip install aether
39
+ aether # 首次运行自动配置
40
+ ```
41
+
42
+ ## ✨ 特性
43
+
44
+ - 🧠 **1+3 主管-专家架构** — 主管拆解任务,File/Search/Create 三个专家各司其职
45
+ - 🇨🇳 **国产模型全覆盖** — DeepSeek V4 / 通义千问 / 智谱 GLM / 豆包 / Moonshot / Ollama
46
+ - 🔧 **实时切换模型** — `/model deepseek` 三秒切,支持任意 LiteLLM 兼容的 model_id
47
+ - 📋 **三种执行模式** — Auto(直接执行)/ Plan(先出计划)/ Step(逐步确认)
48
+ - 💾 **语义缓存 + 上下文压缩** — 相似任务命中缓存,长对话自动压缩,省 Token
49
+ - 🎯 **人格学习** — 自动学习你的交互风格,越聊越像你
50
+ - 🔒 **Docker 沙箱** — `/sandbox on` 容器隔离,代码碰不到真实文件
51
+ - ⏪ **Checkpoint 快照** — 写文件自动备份,`/rewind` 秒级回滚
52
+ - 🔌 **MCP 插件** — 接入社区工具,`.env` 里配 `MCP_SERVERS`
53
+ - 📦 **Skills 能力包** — `.aether/skills/*.md` 即插即用
54
+ - 🧠 **跨会话记忆** — 自动记住偏好和项目信息
55
+
56
+ ## ⚡ 快速开始
57
+
58
+ ```bash
59
+ pip install aether
60
+ aether
61
+ ```
62
+
63
+ 首次运行自动弹出终端配置向导 → 选模型 → 输 Key → 开始对话。
64
+
65
+ ## 💻 VS Code 中使用
66
+
67
+ 打开 VS Code 终端(Ctrl+`),输入:
68
+
69
+ ```bash
70
+ aether
71
+ ```
72
+
73
+ 或添加任务(`.vscode/tasks.json`):
74
+
75
+ ```json
76
+ {
77
+ "version": "2.0.0",
78
+ "tasks": [{
79
+ "label": "Aether",
80
+ "type": "shell",
81
+ "command": "aether",
82
+ "presentation": {"reveal": "always", "panel": "new"}
83
+ }]
84
+ }
85
+ ```
86
+
87
+ 然后 `Ctrl+Shift+P` → `Tasks: Run Task` → `Aether`。
88
+
89
+ ## ⌨️ 常用命令
90
+
91
+ | 命令 | 别名 | 作用 |
92
+ |------|------|------|
93
+ | `/mode plan` | `/m p` | 先出计划再执行 |
94
+ | `/model qwen` | `/md` | 实时切换模型 |
95
+ | `/usage` | `/u` | 查看 Token 消耗 |
96
+ | `/status` | `/s` | 会话状态 |
97
+ | `/memory` | — | 查看记忆 |
98
+ | `/git` | — | Git 状态 |
99
+ | `/checkpoint` | `/cp` | 快照列表 |
100
+ | `/rewind` | `/w` | 回滚到快照 |
101
+ | `/export` | `/ex` | 导出对话 |
102
+ | `/sandbox on` | `/sb on` | 开启 Docker 沙箱 |
103
+ | `/skill` | `/sk` | Skills 管理 |
104
+ | `/model` | `/md` | 切换模型 |
105
+ | `/quit` | `/q` | 退出 |
106
+
107
+ ## 🏗️ 架构
108
+
109
+ ```
110
+ 用户输入 → Supervisor(任务拆解+调度)
111
+
112
+ ┌─────────┼─────────┐
113
+ ▼ ▼ ▼
114
+ FileAgent SearchAgent CreateAgent
115
+ (文件) (搜索) (创作)
116
+ ```
117
+
118
+ ## 📄 许可
119
+
120
+ Apache 2.0
@@ -0,0 +1,92 @@
1
+ # 🌌 Aether v0.9.0
2
+
3
+ **1+N 多模态 Agent 框架** — 面向国产大模型的开源 AI 助手。
4
+
5
+ > 一个主管调度多个专家,自动拆解和执行复杂任务。终端里跑,VS Code 里用,支持 100+ 模型。
6
+
7
+ ## 🚀 安装
8
+
9
+ ```bash
10
+ pip install aether
11
+ aether # 首次运行自动配置
12
+ ```
13
+
14
+ ## ✨ 特性
15
+
16
+ - 🧠 **1+3 主管-专家架构** — 主管拆解任务,File/Search/Create 三个专家各司其职
17
+ - 🇨🇳 **国产模型全覆盖** — DeepSeek V4 / 通义千问 / 智谱 GLM / 豆包 / Moonshot / Ollama
18
+ - 🔧 **实时切换模型** — `/model deepseek` 三秒切,支持任意 LiteLLM 兼容的 model_id
19
+ - 📋 **三种执行模式** — Auto(直接执行)/ Plan(先出计划)/ Step(逐步确认)
20
+ - 💾 **语义缓存 + 上下文压缩** — 相似任务命中缓存,长对话自动压缩,省 Token
21
+ - 🎯 **人格学习** — 自动学习你的交互风格,越聊越像你
22
+ - 🔒 **Docker 沙箱** — `/sandbox on` 容器隔离,代码碰不到真实文件
23
+ - ⏪ **Checkpoint 快照** — 写文件自动备份,`/rewind` 秒级回滚
24
+ - 🔌 **MCP 插件** — 接入社区工具,`.env` 里配 `MCP_SERVERS`
25
+ - 📦 **Skills 能力包** — `.aether/skills/*.md` 即插即用
26
+ - 🧠 **跨会话记忆** — 自动记住偏好和项目信息
27
+
28
+ ## ⚡ 快速开始
29
+
30
+ ```bash
31
+ pip install aether
32
+ aether
33
+ ```
34
+
35
+ 首次运行自动弹出终端配置向导 → 选模型 → 输 Key → 开始对话。
36
+
37
+ ## 💻 VS Code 中使用
38
+
39
+ 打开 VS Code 终端(Ctrl+`),输入:
40
+
41
+ ```bash
42
+ aether
43
+ ```
44
+
45
+ 或添加任务(`.vscode/tasks.json`):
46
+
47
+ ```json
48
+ {
49
+ "version": "2.0.0",
50
+ "tasks": [{
51
+ "label": "Aether",
52
+ "type": "shell",
53
+ "command": "aether",
54
+ "presentation": {"reveal": "always", "panel": "new"}
55
+ }]
56
+ }
57
+ ```
58
+
59
+ 然后 `Ctrl+Shift+P` → `Tasks: Run Task` → `Aether`。
60
+
61
+ ## ⌨️ 常用命令
62
+
63
+ | 命令 | 别名 | 作用 |
64
+ |------|------|------|
65
+ | `/mode plan` | `/m p` | 先出计划再执行 |
66
+ | `/model qwen` | `/md` | 实时切换模型 |
67
+ | `/usage` | `/u` | 查看 Token 消耗 |
68
+ | `/status` | `/s` | 会话状态 |
69
+ | `/memory` | — | 查看记忆 |
70
+ | `/git` | — | Git 状态 |
71
+ | `/checkpoint` | `/cp` | 快照列表 |
72
+ | `/rewind` | `/w` | 回滚到快照 |
73
+ | `/export` | `/ex` | 导出对话 |
74
+ | `/sandbox on` | `/sb on` | 开启 Docker 沙箱 |
75
+ | `/skill` | `/sk` | Skills 管理 |
76
+ | `/model` | `/md` | 切换模型 |
77
+ | `/quit` | `/q` | 退出 |
78
+
79
+ ## 🏗️ 架构
80
+
81
+ ```
82
+ 用户输入 → Supervisor(任务拆解+调度)
83
+
84
+ ┌─────────┼─────────┐
85
+ ▼ ▼ ▼
86
+ FileAgent SearchAgent CreateAgent
87
+ (文件) (搜索) (创作)
88
+ ```
89
+
90
+ ## 📄 许可
91
+
92
+ Apache 2.0
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "yaping-aether"
3
+ version = "0.9.0"
4
+ description = "1+N 多模态 Agent 框架 — 面向国产大模型的开源 AI 助手"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "Apache-2.0" }
8
+ authors = [{ name = "Aether Contributors" }]
9
+ keywords = ["agent", "llm", "chinese-llm", "multi-agent", "smolagents", "cli", "ai-assistant"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: Apache Software License",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+
21
+ dependencies = [
22
+ "smolagents>=1.20.0",
23
+ "litellm>=1.60.0",
24
+ "rich>=13.0.0",
25
+ "python-dotenv>=1.0.0",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/AQ-july/aether"
30
+ Repository = "https://github.com/AQ-july/aether.git"
31
+ Issues = "https://github.com/AQ-july/aether/issues"
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=8.0.0",
36
+ ]
37
+
38
+ [project.scripts]
39
+ aether = "aether.main:main"
40
+
41
+ [build-system]
42
+ requires = ["hatchling"]
43
+ build-backend = "hatchling.build"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/aether"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
@@ -0,0 +1,14 @@
1
+ """Aether 启动脚本
2
+ 用法:双击或在终端运行 python run.py
3
+ """
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ # 确保 src 目录在 Python 路径中
8
+ src_path = Path(__file__).parent / "src"
9
+ sys.path.insert(0, str(src_path))
10
+
11
+ from aether.main import main
12
+
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,3 @@
1
+ """Aether — 1+N 多模态 Agent 框架"""
2
+
3
+ __version__ = "0.9.0"
File without changes
@@ -0,0 +1,28 @@
1
+ """内容创作专家 Agent"""
2
+
3
+ from smolagents import CodeAgent, LiteLLMModel
4
+
5
+ from ..config import WorkerConfig
6
+ from ..tools.create_tools import write_article, generate_code, summarize_text
7
+ from ..tools.file_tools import write_file
8
+ from ..tools.project_tools import edit_file
9
+
10
+
11
+ def create_create_agent(config: WorkerConfig) -> CodeAgent:
12
+ """创建内容创作专家 Agent。支持独立模型(CREATE_MODEL),否则用默认。"""
13
+ mc = config.create_model or config.model
14
+ model = LiteLLMModel(model_id=mc.model_id, api_key=mc.api_key, temperature=mc.temperature)
15
+
16
+ agent = CodeAgent(
17
+ name="create_agent",
18
+ description=(
19
+ "内容创作专家。能写文章、生成代码、摘要文本。"
20
+ "当任务涉及创作内容(写文章、写代码、做摘要、生成文档)时调用我。"
21
+ ),
22
+ tools=[write_article, generate_code, summarize_text, write_file, edit_file],
23
+ model=model,
24
+ provide_run_summary=True,
25
+ max_steps=12,
26
+ )
27
+
28
+ return agent
@@ -0,0 +1,27 @@
1
+ """文件管理专家 Agent"""
2
+
3
+ from smolagents import CodeAgent, LiteLLMModel
4
+
5
+ from ..config import WorkerConfig
6
+ from ..tools.file_tools import read_file, write_file, list_directory, search_files
7
+ from ..tools.project_tools import project_map
8
+
9
+
10
+ def create_file_agent(config: WorkerConfig) -> CodeAgent:
11
+ """创建文件管理专家 Agent。支持独立模型(FILE_MODEL),否则用默认。"""
12
+ mc = config.file_model or config.model
13
+ model = LiteLLMModel(model_id=mc.model_id, api_key=mc.api_key, temperature=mc.temperature)
14
+
15
+ agent = CodeAgent(
16
+ name="file_agent",
17
+ description=(
18
+ "文件管理专家。能搜索、读取、写入、整理本地文件和目录,扫描项目结构。"
19
+ "当任务涉及文件操作(读文件、写文件、整理文件夹、搜索文件、了解项目结构)时调用我。"
20
+ ),
21
+ tools=[read_file, write_file, list_directory, search_files, project_map],
22
+ model=model,
23
+ provide_run_summary=True,
24
+ max_steps=10,
25
+ )
26
+
27
+ return agent
@@ -0,0 +1,26 @@
1
+ """搜索专家 Agent"""
2
+
3
+ from smolagents import CodeAgent, LiteLLMModel
4
+
5
+ from ..config import WorkerConfig
6
+ from ..tools.search_tools import web_search, fetch_url
7
+
8
+
9
+ def create_search_agent(config: WorkerConfig) -> CodeAgent:
10
+ """创建搜索专家 Agent。支持独立模型(SEARCH_MODEL),否则用默认。"""
11
+ mc = config.search_model or config.model
12
+ model = LiteLLMModel(model_id=mc.model_id, api_key=mc.api_key, temperature=mc.temperature)
13
+
14
+ agent = CodeAgent(
15
+ name="search_agent",
16
+ description=(
17
+ "搜索专家。能搜索网页、抓取 URL 内容、聚合资讯。"
18
+ "当任务涉及在网上查找信息、搜索资料、获取最新资讯时调用我。"
19
+ ),
20
+ tools=[web_search, fetch_url],
21
+ model=model,
22
+ provide_run_summary=True,
23
+ max_steps=8,
24
+ )
25
+
26
+ return agent
File without changes
@@ -0,0 +1,140 @@
1
+ """语义缓存层
2
+
3
+ 对相似的任务/查询进行缓存,避免重复调用 LLM。
4
+
5
+ 核心思路:
6
+ 1. 将用户任务文本做 embedding
7
+ 2. 与缓存中的历史任务做余弦相似度匹配
8
+ 3. 相似度超过阈值 → 直接返回缓存结果
9
+ 4. 否则执行任务 → 结果存入缓存
10
+
11
+ MVP 阶段使用简单的文本 hash + 模糊匹配,
12
+ 后续可升级为向量数据库方案。
13
+ """
14
+
15
+ import hashlib
16
+ import time
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+
21
+ @dataclass
22
+ class CacheEntry:
23
+ """单条缓存记录"""
24
+ task_hash: str
25
+ task: str
26
+ result: Any
27
+ timestamp: float = field(default_factory=time.time)
28
+ hit_count: int = 0
29
+
30
+
31
+ class SemanticCache:
32
+ """语义缓存管理器。
33
+
34
+ MVP 版本使用以下策略:
35
+ - 精确匹配:相同任务文本 → 直接命中
36
+ - 规范化匹配:去空白、小写后相同 → 命中
37
+ - 前缀匹配:新任务是缓存任务的子串 → 命中
38
+
39
+ 未来可升级为 embedding + 向量相似度匹配。
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ max_entries: int = 100,
45
+ ttl_seconds: int = 3600,
46
+ similarity_threshold: float = 0.92,
47
+ ):
48
+ self._cache: dict[str, CacheEntry] = {}
49
+ self._max_entries = max_entries
50
+ self._ttl_seconds = ttl_seconds
51
+ self._similarity_threshold = similarity_threshold
52
+
53
+ def _normalize(self, text: str) -> str:
54
+ """规范化文本:去除所有空白、转小写。
55
+
56
+ 中英文混排场景下,空格不是可靠的词边界("Python协程" vs "Python 协程"),
57
+ 因此直接去掉所有空白后做哈希,确保语义等价文本命中同一缓存。
58
+ """
59
+ # 去掉所有空白字符(空格、换行、制表等)
60
+ import re
61
+ return re.sub(r"\s+", "", text.lower())
62
+
63
+ def _hash(self, task: str) -> str:
64
+ """计算任务的哈希"""
65
+ return hashlib.md5(self._normalize(task).encode()).hexdigest()
66
+
67
+ def get(self, task: str) -> Any | None:
68
+ """查找缓存。返回结果或 None。"""
69
+ # 1. 精确匹配
70
+ task_hash = self._hash(task)
71
+ entry = self._cache.get(task_hash)
72
+ if entry:
73
+ # 检查是否过期
74
+ if time.time() - entry.timestamp > self._ttl_seconds:
75
+ del self._cache[task_hash]
76
+ return None
77
+ entry.hit_count += 1
78
+ return entry.result
79
+
80
+ # 2. 规范化匹配(遍历缓存查找)
81
+ normalized = self._normalize(task)
82
+ for cached_hash, cached_entry in self._cache.items():
83
+ cached_normalized = self._normalize(cached_entry.task)
84
+ # 完全相同
85
+ if normalized == cached_normalized:
86
+ if time.time() - cached_entry.timestamp < self._ttl_seconds:
87
+ cached_entry.hit_count += 1
88
+ # 将新 hash 也指向同一结果
89
+ self._cache[task_hash] = cached_entry
90
+ return cached_entry.result
91
+ # 新任务是缓存任务的子串(更具体的追问可能命中之前的大任务)
92
+ if normalized in cached_normalized and len(normalized) > 20:
93
+ if time.time() - cached_entry.timestamp < self._ttl_seconds:
94
+ cached_entry.hit_count += 1
95
+ return cached_entry.result
96
+
97
+ return None
98
+
99
+ def set(self, task: str, result: Any) -> None:
100
+ """存入缓存"""
101
+ # 清理过期条目
102
+ self._evict_expired()
103
+
104
+ # 超出容量时淘汰命中次数最少的
105
+ if len(self._cache) >= self._max_entries:
106
+ min_entry = min(self._cache.values(), key=lambda e: e.hit_count)
107
+ del self._cache[min_entry.task_hash]
108
+
109
+ task_hash = self._hash(task)
110
+ self._cache[task_hash] = CacheEntry(
111
+ task_hash=task_hash,
112
+ task=task,
113
+ result=result,
114
+ )
115
+
116
+ def _evict_expired(self) -> None:
117
+ """清理过期缓存"""
118
+ now = time.time()
119
+ expired = [
120
+ h for h, e in self._cache.items()
121
+ if now - e.timestamp > self._ttl_seconds
122
+ ]
123
+ for h in expired:
124
+ del self._cache[h]
125
+
126
+ def clear(self) -> None:
127
+ """清空所有缓存"""
128
+ self._cache.clear()
129
+
130
+ @property
131
+ def stats(self) -> dict:
132
+ """缓存统计信息"""
133
+ total = len(self._cache)
134
+ hits = sum(e.hit_count for e in self._cache.values())
135
+ return {
136
+ "entries": total,
137
+ "total_hits": hits,
138
+ "max_entries": self._max_entries,
139
+ "ttl_seconds": self._ttl_seconds,
140
+ }