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.
- yaping_aether-0.9.0/.env.example +39 -0
- yaping_aether-0.9.0/.github/workflows/test.yml +30 -0
- yaping_aether-0.9.0/.gitignore +33 -0
- yaping_aether-0.9.0/LICENSE +59 -0
- yaping_aether-0.9.0/PKG-INFO +120 -0
- yaping_aether-0.9.0/README.md +92 -0
- yaping_aether-0.9.0/pyproject.toml +49 -0
- yaping_aether-0.9.0/run.py +14 -0
- yaping_aether-0.9.0/src/aether/__init__.py +3 -0
- yaping_aether-0.9.0/src/aether/agents/__init__.py +0 -0
- yaping_aether-0.9.0/src/aether/agents/create_agent.py +28 -0
- yaping_aether-0.9.0/src/aether/agents/file_agent.py +27 -0
- yaping_aether-0.9.0/src/aether/agents/search_agent.py +26 -0
- yaping_aether-0.9.0/src/aether/cache/__init__.py +0 -0
- yaping_aether-0.9.0/src/aether/cache/semantic_cache.py +140 -0
- yaping_aether-0.9.0/src/aether/checkpoint.py +103 -0
- yaping_aether-0.9.0/src/aether/compact.py +112 -0
- yaping_aether-0.9.0/src/aether/config.py +244 -0
- yaping_aether-0.9.0/src/aether/controller.py +100 -0
- yaping_aether-0.9.0/src/aether/learn.py +69 -0
- yaping_aether-0.9.0/src/aether/main.py +700 -0
- yaping_aether-0.9.0/src/aether/mcp.py +76 -0
- yaping_aether-0.9.0/src/aether/memory.py +128 -0
- yaping_aether-0.9.0/src/aether/modes.py +33 -0
- yaping_aether-0.9.0/src/aether/sandbox.py +91 -0
- yaping_aether-0.9.0/src/aether/session.py +84 -0
- yaping_aether-0.9.0/src/aether/skill.py +76 -0
- yaping_aether-0.9.0/src/aether/supervisor.py +113 -0
- yaping_aether-0.9.0/src/aether/toolkit.py +78 -0
- yaping_aether-0.9.0/src/aether/tools/__init__.py +0 -0
- yaping_aether-0.9.0/src/aether/tools/create_tools.py +90 -0
- yaping_aether-0.9.0/src/aether/tools/file_tools.py +120 -0
- yaping_aether-0.9.0/src/aether/tools/git_tools.py +101 -0
- yaping_aether-0.9.0/src/aether/tools/project_tools.py +161 -0
- yaping_aether-0.9.0/src/aether/tools/search_tools.py +90 -0
- yaping_aether-0.9.0/src/aether/usage.py +93 -0
- yaping_aether-0.9.0/src/aether/ux.py +63 -0
- yaping_aether-0.9.0/src/aether/vscode.py +34 -0
- yaping_aether-0.9.0/tests/__init__.py +0 -0
- 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()
|
|
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
|
+
}
|