One-shot codebase Q&A CLI. 问一个关于当前仓库的问题,拿到一个带文件引用的 Markdown 答案。
位于 apps/ask/,是 Calcifer SDK 的一个独立消费者。
和 SDK 物理隔离:独立 pyproject.toml,独立 package(ask),单向依赖 calcifer。
这两个 app 故意覆盖 SDK 的不同 surface,目的是 dogfood 整个 API 表面:
| 维度 | apps/chatbot/ |
apps/ask/ |
|---|---|---|
| 交互形态 | 长会话,多轮聊天 | 一次性问答 |
| Agent API | run_stream() 流式 + 会话封装 |
run_sync() 一次性 |
| 自定义 tool | ❌ 只用内置工具模式 | ✅ git_log 通过 @tool 装饰器 |
| Stop hook | ❌ | ✅ register_stop_hook 限 8 轮 |
| 测试 | ✅ MockProvider 单元测试 |
✅ MockProvider 单元测试 |
| 工具集 | none / readonly / all 三档 | 过滤成只读(glob/grep/file_read)+ 自定义 |
- Read-only:只加载 Glob、Grep、FileRead 这三个只读内置工具,加一个自定义的
git_log - 有上限:
register_stop_hook强制最多 8 个 turn,防止跑飞 - Markdown 渲染:Rich 把答案渲染成漂亮的终端 Markdown
--raw:可以直接打印纯文本,方便管道 / 脚本- 可测试:配套
tests/test_ask.py用MockProvider覆盖核心行为,pytest一键跑
从 calcifer 仓库根目录:
pip install -e . # 装 SDK 本体
pip install -e apps/ask # 装 ask
# 跑单元测试还需要 dev extras:
pip install -e "apps/ask[dev]"export OPENAI_API_KEY=sk-...
export OPENAI_BASE_URL=https://api.openai.com/v1 # 可选
export OPENAI_MODEL=gpt-4o-mini # 可选
ask "Agent loop 如何处理 429 错误?"
ask "哪里定义了 6 层 context 压缩管线?"
ask "最近三次 commit 改了什么"
ask --raw "列出所有 @tool 装饰的函数" | tee tools.txt示例输出:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ question ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Agent loop 如何处理 429 错误? ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
╭─ answer ────────────────────────────────────────────────────────────────╮
│ Agent loop 在 calcifer/services/api/provider.py:165-192 检测到 429 响应 │
│ 时,走三级恢复: │
│ │
│ 1. 指数退避重试(BASE_DELAY_S × 2^attempt) │
│ 2. 如果响应里带 retry-after header,优先用这个值 │
│ 3. 连续 3 次 529 后如果有 fallback_model,切到降级模型 │
╰─────────────────────────────────────────────────────────────────────────╯
turns=3 tool_calls=2 tokens=1420 cost=$0.000180
pytest apps/ask/tests5 个测试,完全不需要真实 LLM,用 calcifer.testing.MockProvider 模拟响应:
test_ask_returns_canned_text_when_model_answers_directlytest_ask_runs_tool_then_final_answertest_ask_tool_call_is_observable_via_assert_tool_calledtest_ask_message_count_helpertest_git_log_tool_returns_string_for_self_repo
apps/ask/
├── README.md
├── pyproject.toml
├── ask/
│ ├── __init__.py
│ ├── __main__.py
│ └── app.py # CLI + build_agent + @tool git_log + stop hook
└── tests/
└── test_ask.py # MockProvider-based unit tests
MIT(继承自 calcifer 仓库)。