Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def check_permission(state, cls, name, args): # 决策 +

```bash
python agent.py --selfcheck # 零依赖、零网络、零 key。改完先跑这个
python -m pytest tests/ -q # 336 条。CI 跑 ubuntu/windows × 3.10/3.13
python -m pytest tests/ -q # 337 条。CI 跑 ubuntu/windows × 3.10/3.13
```

`--selfcheck` 覆盖 4 个工具的读/写/改、`edit_file` 的"找不到 / 不唯一"两种报错、
Expand Down Expand Up @@ -190,14 +190,14 @@ set TALOS_MAX_STEPS=12
| **行为类** | **模型读到守卫那条消息之后干什么** | ❌ 必须用你真在用的那个 |

「拒绝之后会不会改用 `python -c` 绕路」是那个具体模型的性格,换了模型不算数。
而**行为类正是 live 测试唯一还值钱的部分** —— 管道通不通,336 条判据已经免费覆盖了。
而**行为类正是 live 测试唯一还值钱的部分** —— 管道通不通,337 条判据已经免费覆盖了。

按这条界,**目标闸的 live 判据是行为类的**:离线判据已经证明判断器拿得到 `read_file`、
越界会被驳回、判不出来时不假装成功;它们证明不了的是**这个具体模型拿到只读工具之后
会不会真的去用**,还是照样信对话里那句「验证通过」。这一条不成立的话,只读工具就是装饰。

```bash
$env:TALOS_PROVIDER="deepseek"; $env:TALOS_MODEL="deepseek-chat"
$env:TALOS_PROVIDER="deepseek"; $env:TALOS_MODEL="deepseek-flash"
.venv\Scripts\python.exe benchmarks\goal\judge_live.py --n 3
```

Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![tests](https://github.com/Jerry-TZ/Talos/actions/workflows/test.yml/badge.svg)](https://github.com/Jerry-TZ/Talos/actions/workflows/test.yml)

**一个你能完整读完的编程 agent。** 5309 行 Python,336 条离线判据,一份不糊弄人的安全说明。
**一个你能完整读完的编程 agent。** 5311 行 Python,337 条离线判据,一份不糊弄人的安全说明。

<img src="docs/demo.svg" alt="Talos 终端界面:动盘之前先弹确认框" width="100%">

Expand All @@ -19,7 +19,7 @@

| 文件 | 行数 | 职责 |
|---|---|---|
| `agent.py` | 4207 | 循环 + 工具 + 权限门 + 自学习 |
| `agent.py` | 4209 | 循环 + 工具 + 权限门 + 自学习 |
| `console_ui.py` | 214 | 终端界面(可整体替换) |
| `recall.py` | 582 | 联想记忆:扩散激活检索 |
| `session.py` | 306 | 会话持久化(想换 SQLite 只改这个) |
Expand All @@ -33,7 +33,7 @@
极简 agent 赛道很挤,有人用 Zig 做到 678KB 二进制。**Talos 不比谁小,它比谁都好读。**

- **能读完** — 四个文件,注释解释的是*为什么*,不是*是什么*。几乎每条防御旁边都写着它挡的那次真实翻车。
- **能验证** — 336 个测试,**离线、免 API key、几秒跑完**,CI 在 Linux/Windows × Python 3.10/3.13 上都跑。clone 下来立刻知道它没坏。
- **能验证** — 337 个测试,**离线、免 API key、几秒跑完**,CI 在 Linux/Windows × Python 3.10/3.13 上都跑。clone 下来立刻知道它没坏。
- **不吹牛** — [`SECURITY.md`](SECURITY.md) 明写 `create_tool` 就是进程内 RCE、正则黑名单只是减速带。**没有沙箱就是没有沙箱** —— 真要隔离,[三条现成方案](SECURITY.md#真要隔离怎么办)按代价从低到高列在那儿。
- **有考卷** — [`EXAM.md`](EXAM.md) / [`EXAM2.md`](EXAM2.md) 是两份可复现的能力测试,带标准答案和作弊检测(比如逐个核验 arXiv ID 真伪,防止编造引用)。记录的是"我怎么验证它真的有用",不是功能列表。
- **有实测** — [`FINDINGS.md`](FINDINGS.md) 记了二十二个真实任务量出来的东西:哪条提示词生效、哪条从头到尾没生效、六次翻车、检索改动的前后数字,以及**两个被数据否掉的自己的方案**。样本小,局限写在最前面。
Expand Down Expand Up @@ -81,10 +81,10 @@ python agent.py --selfcheck
|---|---|---|
| `claude`(默认) | `ANTHROPIC_API_KEY` | claude-haiku-4-5 |
| `openai` | `OPENAI_API_KEY` | gpt-4o-mini |
| `gemini` | `GEMINI_API_KEY` | gemini-2.0-flash |
| `deepseek` | `DEEPSEEK_API_KEY` | deepseek-chat |
| `gemini` | `GEMINI_API_KEY` | gemini-3.5-flash |
| `deepseek` | `DEEPSEEK_API_KEY` | deepseek-flash |
| `glm` | `ZHIPUAI_API_KEY` | glm-4.7-flash |
| `kimi` | `MOONSHOT_API_KEY` | moonshot-v1-8k |
| `kimi` | `MOONSHOT_API_KEY` | kimi-k2.6 |

---

Expand Down Expand Up @@ -200,7 +200,7 @@ Ctrl+C 停下当前这轮 —— 做过的都留着,可以直接说

```bash
.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
.venv\Scripts\python.exe -m pytest tests/ -q # 336 条,约 8 秒,不联网、不需要 key
.venv\Scripts\python.exe -m pytest tests/ -q # 337 条,约 8 秒,不联网、不需要 key
python agent.py --selfcheck # 免依赖的冒烟检查
```

Expand Down
8 changes: 5 additions & 3 deletions agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ def _load_dotenv(path: str = ".env") -> None:

# ── providers: 大家都讲 OpenAI 兼容 API,只有 base_url + 模型名不同 ────────────────
# base_url 稳定;模型名常变 —— 用 TALOS_MODEL 环境变量覆盖成你有权限的那个。
# 默认模型 2026-09 核对过:moonshot-v1 整个系列(8/31)、deepseek-chat(7/24)、
# gemini-2.0-flash(6/1)都已下线,换成了各家当前的对应档。README 那张表跟这里由判据绑着。
PROVIDERS = {
# name key 环境变量 base_url (None = OpenAI 官方) 默认模型
"claude": ("ANTHROPIC_API_KEY", "https://api.anthropic.com/v1/", "claude-haiku-4-5-20251001"),
"openai": ("OPENAI_API_KEY", None, "gpt-4o-mini"),
"gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/", "gemini-2.0-flash"),
"deepseek": ("DEEPSEEK_API_KEY", "https://api.deepseek.com/v1", "deepseek-chat"),
"gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/", "gemini-3.5-flash"),
"deepseek": ("DEEPSEEK_API_KEY", "https://api.deepseek.com/v1", "deepseek-flash"),
"glm": ("ZHIPUAI_API_KEY", "https://open.bigmodel.cn/api/paas/v4", "glm-4.7-flash"),
"kimi": ("MOONSHOT_API_KEY", "https://api.moonshot.cn/v1", "moonshot-v1-8k"),
"kimi": ("MOONSHOT_API_KEY", "https://api.moonshot.cn/v1", "kimi-k2.6"),
}
PROVIDER = os.environ.get("TALOS_PROVIDER", "claude").lower()

Expand Down
30 changes: 30 additions & 0 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,33 @@ def test_the_test_count_the_docs_advertise_is_the_real_one():
assert not wrong, (
f"文档宣传的测试数跟 tests/ 里真实的 {real} 条对不上:\n " + "\n ".join(wrong)
+ "\n加判据是这个项目每天在干的事,所以这个数**每天都会陈** —— 这条就是为它存在的。")


# `| `claude`(默认) | `ANTHROPIC_API_KEY` | claude-haiku-4-5 |`
_PROVIDER_ROW = re.compile(r"^\|\s*`(\w+)`[^|]*\|\s*`(\w+)`\s*\|\s*([\w.\-]+)\s*\|", re.M)


def test_the_provider_table_in_the_readme_is_what_the_code_does():
"""README 那张 provider 表 == `agent.PROVIDERS`:哪几家、读哪个 key、默认哪个模型。

**默认模型会被供应商下线,而且下得很快。** 2026-09 核对时,六家里有三家的默认值
已经调不通了:`moonshot-v1-8k`(整个 v1 系列 8 月 31 日下线)、`deepseek-chat`
(7 月 24 日停用)、`gemini-2.0-flash`(6 月 1 日关停)。那次是两边一起改的 ——
这条判据管的是**下一次只改了一边**:读者照着 README 设了 provider,跑起来却是另一个模型。

README 可以写去掉日期后缀的名字(`claude-haiku-4-5` 对 `claude-haiku-4-5-20251001`),
那是同一个模型的别名;别的一律要逐字相同。"""
import agent
readme = io.open(os.path.join(HOME, "README.md"), encoding="utf-8").read()
rows = {p: (key, model) for p, key, model in _PROVIDER_ROW.findall(readme)}
assert set(rows) == set(agent.PROVIDERS), (
f"README 表里的 provider 跟代码对不上:多了 {sorted(set(rows) - set(agent.PROVIDERS))}、"
f"少了 {sorted(set(agent.PROVIDERS) - set(rows))}")
wrong = []
for p, (key, model) in sorted(rows.items()):
want_key, _url, want_model = agent.PROVIDERS[p]
if key != want_key:
wrong.append(f"{p}:README 写 key {key},代码读 {want_key}")
if not (model == want_model or want_model.startswith(model + "-")):
wrong.append(f"{p}:README 写默认 {model},代码默认 {want_model}")
assert not wrong, "README 的 provider 表跟代码对不上:\n " + "\n ".join(wrong)
2 changes: 1 addition & 1 deletion tests/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ def test_a_refusal_says_what_was_refused_instead_of_something_that_never_happene


def test_a_file_that_merely_starts_with_check_is_not_a_verification_script(ws):
"""`checksum.py` / `checkpoint.py` / `checklist.py` / `checkout.py` 不是验证脚本。
r"""`checksum.py` / `checkpoint.py` / `checklist.py` / `checkout.py` 不是验证脚本。

正则原来是 `^(verify|validate|check)[\w-]*\.py$` —— 它认「以 check 开头」,
而这几个名字在真实仓库里到处都是。命中之后 `write_file` / `edit_file` 一律拒绝:
Expand Down
Loading