# Ollama（本地 / 自托管）实测记录

> 日期：2026-06-09 · 环境：macOS，Ollama @ `127.0.0.1:11434`，模型 `qwen2.5:7b` / `qwen2.5-coder:32b`

把 claim ④ 的"本地 Ollama 灵活切换"从**纸面声明**变为**实测通过**。坐实"自托管"这条结构性护城河（大厂 SaaS 无法提供气隙部署）。

## 实测结论

| 层级 | 结果 |
|---|---|
| Provider `chat()` | ✅ 正常，usage 累积 |
| Provider `chat_typed()` | ✅ 正常，返回 token/model/finish_reason |
| `get_usage()` 会话累计 | ✅ 正常（修复后，见下） |
| 全栈 simple agent（YAML→compile→Sandbox→workspace） | ✅ 完成，产物三件套齐全 |
| 全栈 react agent（含 terminate 工具） | ✅ 完成，token 捕获正确（232 tokens） |
| 本地模型计费 | ✅ **$0**（修复后，见下） |

## 实测中发现并修复的 3 个真问题

1. **本地模型被按 Claude 定价计费**（`agentpaas/engine/sandbox.py::_compute_cost_usd`）
   - 修复：`provider == "ollama"` → 直接返回 `0.0`。本地模型无 per-token API 成本。

2. **`chat_typed()` 不累积 session usage**（`lambdagent/.../openai_compat_provider.py`）
   - 只有 `chat()` 累积，`chat_typed()` 返回了 usage 却没加进 `_usage_input/_usage_output`，导致用 typed 接口时 `get_usage()` 永远返回 0。
   - 修复：`chat_typed()` 内同步累积，与 `chat()` 对齐。

3. **cost.json 的 `source` 标签对非 Claude provider 误导**
   - 原来只要有 usage 就写死 `"claude_jsonl"`，即使是 ollama/qwen。
   - 修复：按 provider 反映真实来源——Claude=`claude_jsonl`，其余=`{provider}_api`。

## 已知遗留（非 ollama 专属，跨 provider 既有限制）

- **simple agent token 计数为 0**：simple 类型没有 react 的 `_think_ref`，Sandbox 读不到 provider 已累积的 usage → `source: trace_fallback, tokens: 0`。对 ollama 无害（成本本就 $0），但对计费型 provider 会低估 token。属既有架构限制，待单独处理（让 simple 路径也能回读 provider.get_usage()）。

## 可重复测试

`tests/test_ollama.py`：
- 成本单测（**不需** ollama 运行）：local=$0、Claude/qwen 定价回归。
- live 测试（**ollama 可达才跑，否则自动 skip**）：chat 往返、usage 累积、全栈 $0 run。
- 复现前置：`ollama pull qwen2.5:7b && ollama serve`。

## 配置用法

```yaml
model:
  provider: ollama
  name: qwen2.5:7b      # 或 qwen2.5-coder:32b 等本地模型
  temperature: 0.0
  maxTokens: 256
```
默认 base_url `http://127.0.0.1:11434/v1`，可用 `base_url` 覆盖远程 ollama。
