Skip to main content
Glama
cpidkuub
by cpidkuub
README.md
# BattForge Agent(MCP 侧)

自然语言提电芯需求 → 编排后端求解 → 给出设计形态与性能指标。

**本仓库全开源**;电芯仿真内核(BattForge 离线包)**独立分发**。两者只通过
一条**进程边界 + 版本化 JSON 协议**通信(见 `docs/` 内的边界文档)。

> 第三方规格参照表因再分发权未确认,不随本公开库分发。

**能力清单见 [`CAPABILITIES.md`](CAPABILITIES.md)**:这里只说明 MCP 能做什么、输入输出与边界。

## 与后端的关系

```
        用户(自然语言)
              │
        ┌─────▼─────┐
        │    MCP    │  本仓库:需求解析 / 参照查找 / 尺寸提议与确认 /
        │  (agent)  │  多后端编排 / 与用户对话
        └─────┬─────┘
              │  design(需求) → form          ← 设计权威,仅 BattForge 提供
              │  evaluate(form, 问题清单) → 指标 ← 每个后端各自实现
      ┌───────┴────────┐
      ▼                ▼
 BattForge 离线包    COMSOL(未接入)
 电化学 + 设计权威    温度场 / 应力 / 变形
```

**两种后端角色不对称,这是有意的**:BattForge 是**设计权威**(它拥有参数集里标定过的
参考极片厚度,因而能解出极片);COMSOL 是**附加物理提供方**。不必强行对称。

## 边界原则

公开侧只处理需求语义与公开知识;产品侧标定/实测得到的数据由 BattForge 离线包提供,
不在本仓库内。具体:`form`(语义形态描述)可以过界,`overrides`(pybamm 参数字典)不行。
详见 `docs/` 内的边界文档。

## 目录

| 目录 | 装什么 |
|---|---|
| `CAPABILITIES.md` | **公开能力清单**:MCP 能做什么、输入输出、边界;不包含内部实现/策略说明 |
| `skills/` | **客户端工作流说明**(`SKILL.md`):设计、扣电、热失控、COMSOL、evidence;不是 MCP 工具 |
| `docs/` | **电芯设计探索路径**(七步流程 / 四个口径 / 三个自检 / 九个失效模式)、边界与协议、评测集 |
| `client/` | **NDJSON 客户端 + worker 池 + 粘性会话**(`ndjson.py` / `pool.py`)+ **UI 草稿通道**(`ui_channel.py`)+ **COMSOL profile/runner/docs/mph**(`comsol_*.py`) |
| `tools/` | **生产工具 schema + 调度**(`tool_schemas.json` / `registry.py`)+ **可执行需求编译器**(`requirements.py`)+ 探针(`coin_probe.py` / `ui_probe.py`);设计探索探针不随本仓库分发 |
| `tests/` | worker 池 / 工具 schema 的回归测试(默认用 `stub/bridge.py`,不依赖 BattForge) |
| `stub/` | **开放侧假实现**(§6.4):固定数据的 NDJSON 桥,供没有离线包的 CI/评测 |

## 关键约定

* **尺寸由用户确认,不由产品侧决定**:同一容量有多套外形并存,选哪套取决于产线/空间/平台。
* **高度默认取「不含极柱」**(Battsim 与 COMSOL 常规建模都不带极柱)。
* **并行由 MCP 侧统一调度**:后端在单个请求内串行。两个后端互相看不见对方负载,
  只有 MCP 能仲裁——所以并行不能下放给后端,否则会超订。

## 状态

| 项 | 状态 |
|---|---|
| 电芯设计路径 | ✅ 跑通。**方壳**与**圆柱**两类形态都跑出了参考解与闭环校验(具体数值留本地,不入公开仓库)。在内部引擎上重跑,锚点全过 |
| 评测集 | ✅ 起草(`docs/`),判据待转成可执行断言 |
| 边界/协议文档 | ✅ `docs/产品边界与协议.md` |
| 协议客户端 | ✅ `client/ndjson.py`(单 worker)+ `client/pool.py`(worker 池 + 粘性会话) |
| 传输参数随配方重标定 | ⏳ 几何与配方成立,但**倍率预测偏保守**(利用率按现值,数值见本地案例) |
| 扣电校正 / 预测 | ✅ 协议操作(`dataset.put` / `calibrate.*` / `predict` / `job.*`)+ `tools/coin_probe.py` |
| 生产工具 schema | ✅ `tools/tool_schemas.json`(26 个定义;默认 router 2 / full 24)+ `tools/registry.py`(校验/调度/异步作业/会话表) |
| 工具面配置 | ✅ 默认 `router` 暴露 2 个;`full` 暴露 24 个底层工具 |
| Skills | ✅ `skills/`:设计/扣电/热失控/工步/COMSOL/evidence/router/semantic 九份 `SKILL.md` |
| Evidence | ✅ engine / COMSOL 结果附带 `battforge.evidence/1`(输入/结果哈希、产物、checks/warnings、验证状态、人工复核) |
| 语义接口 | ✅ `battforge_task` 的 `semantic_catalog/compose/apply`;cell/pack/coin/thermal 行业字段、别名、单位、依赖;Pack 只做输入语义,不计算 |
| 可执行需求编译器 | ✅ `tools/requirements.py`(需求目录/编译/校验;COMSOL 执行与出图在下游) |
| 电芯热失控 | ✅ `thermal.runaway` + `dataset.put(kind=dsc/arc)` |
| UI 自动填写 | ✅ `client/ui_channel.py` + 4 个 `battforge_ui_*` 工具;草稿写本地 spool,页面轮询应用;**不自动保存全电池参数集** |
| 开放侧 stub | ✅ `stub/bridge.py`(固定数据;`BATTSIM_BRIDGE_CMD` 接入 CI) |
| 出口边界闸 | ✅ `tools/boundary.py`(结果递归扫描;错误码透传;progress/取消/超时) |
| COMSOL 操作网关 | ⏳ 部分:`battforge_comsol_task` 做启动/运行/状态/错误地址/手册地址/排障 + MPh 可用性探测;`start/run` 需 `BATTSIM_COMSOL_ALLOW_EXEC=1`(单次)或 `=always`(进程级);`battforge_export_comsol` 取 prepared 参数;参数写入/出图由执行侧 |

## Worker 池与工具 schema

### 为什么需要**粘性会话**

BattForge 的 `dataset_id` / `calibration_id` / `fit_id` / `job_id` 都是**worker 进程本地**的
(桥的注册表在进程内)。所以一次扣电工作流必须在**同一个 worker** 上完成:

```python
from client.pool import WorkerPool
from tools.registry import dispatch_session

pool = WorkerPool(size=3)                  # BATTSIM_WORKERS 可覆盖
with pool.session() as s:                  # 独占一个 worker
    ds = dispatch_session(s, "battforge_dataset_put", {...})
    cal = dispatch_session(s, "battforge_calibrate_ocp", {...})
    out = dispatch_session(s, "battforge_predict", {...})
```

MCP server 多客户端时用 `tools.registry.SessionRegistry(pool)`:按客户端 id
取/关粘性会话(一个对话一个 worker)。

* 并发用户/对话各占一个 session(各一个 worker);池满时排队。
* `dataset.put` / `calibrate.*` / `predict` / `job.*` **必须在 session 内**调用;
  `hello` / `design` / `describe` / `microstructure.apply` 可无会话。
* worker 死亡后下一次 checkout 自动重启并重新握手。
* 会话结束时发 `session.reset`,清空该 worker 的 dataset/calibration/fit/job;
  worker 死掉时池会清掉绑定它的 job 映射,避免长驻 server 残留。
* 环境变量:`BATTSIM_ROOT`(离线包根)、`BATTSIM_PYTHON`、`BATTSIM_WORKERS`、
  `BATTSIM_BRIDGE_CMD`(stub/CI 的假桥命令)、`BATTSIM_BRIDGE_LOG`
  (worker stderr 日志,默认 `~/.battsim/logs/bridge.log`)、
  `BATTSIM_BRIDGE_SHA256`(可选桥文件哈希校验)、
  `BATTSIM_ALLOW_CWD_ROOT=1`(仅开发/CI 允许从 cwd 查找根目录)。

### MCP stdio server

长驻入口:

```bash
python3 -m pip install -r requirements.txt
BATTSIM_ROOT=/path/to/BattForge python3 -m tools.server
```

COMSOL `start/run` 默认关闭;需要本地用户显式设置
`BATTSIM_COMSOL_ALLOW_EXEC=1`(单次放行)或 `=always`(进程级放行);`=once` 是 `=1` 的别名。
`profile_set` 只接受 `comsol_exe` / `version`,不接受 `commands` 或自定义
workspace/doc 路径。自定义命令模板用本地 CLI:

```bash
python3 -m client.comsol_profile set --exe "C:\Program Files\COMSOL\..." --version 6.4
python3 -m client.comsol_profile set --exe ... --command server '["...","server"]'
```

`BATTSIM_COMSOL_ALLOW_EXEC` **不是安全边界**:它只对无 shell 的 MCP 客户端有效;
宿主给模型 Bash 时,模型仍可设置环境变量/重启 server。强隔离请让 COMSOL 跑在
独立低权限账户或容器里。

stdio server 默认:单行 ≤ 32 MB、同时 8 个 `tools/call`、最多 16 个客户端会话;
可用 `BATTSIM_MAX_LINE_CHARS` / `BATTSIM_MAX_INFLIGHT` 调整。生产环境应显式设置
`BATTSIM_ROOT`;开发/CI 才用 `BATTSIM_ALLOW_CWD_ROOT=1` 允许从 cwd 查找。

它持有**一个** `WorkerPool` 和一个 `SessionRegistry`,实现 MCP 的
`initialize` / `tools/list` / `tools/call`;stdout 只写 JSON-RPC,日志走 stderr。
不再需要每个工具调用新建 3 个 worker。

### 协议契约核对

BattForge 的 `protocol/contract.json` 是 op/版本的单一来源:

```bash
BATTSIM_ROOT=/path/to/BattForge python3 -m tools.contract_check
```

检查 `tool_schemas.json` 的工具名、`TOOL_OPS` 映射和契约 op 是否一致;CI/发版前跑。

### 工具 schema

`tools/tool_schemas.json` 是 MCP `tools/list` 的唯一出处(26 个定义,JSON Schema
2020-12,`additionalProperties: false`)。默认 `BATTSIM_TOOLSET=router` 只暴露
`battforge_task` + `battforge_state`;`full` 暴露 24 个底层工具。

- `battforge_task` / `battforge_state`(默认 router 面)
- `battforge_hello` / `battforge_design` / `battforge_describe`
- `battforge_dataset_put`(kind: coin / dsc / arc)
  / `battforge_calibrate_ocp` / `battforge_calibrate_kinetics`
- `battforge_microstructure_apply` / `battforge_predict`
- `battforge_thermal_runaway`(协议 v3;电芯级 DSC→ARC / 实测 ARC)
- `battforge_job_status` / `battforge_job_cancel`
- `battforge_ui_sessions` / `battforge_ui_fill_design` / `battforge_ui_fill_coin`
  / `battforge_ui_status`(UI 自动填写控制面,不走 engine worker)
- `battforge_design_find` / `battforge_design_remember`(MCP 本地设计记忆:
  自然语言找旧设计、加别名/置顶;用户不需要记 `design_id`)
- `battforge_requirement_catalog` / `battforge_requirement_compose`
  / `battforge_requirement_validate` / `battforge_requirement_describe`
  (可执行需求编译器:自然语言/槽位 → COMSOL P2D 需求;**不执行 COMSOL、
  不处理参数/图片**)
- `battforge_procedure`(工步 IR `battforge.procedure/1`:catalog/compose/validate/describe;
  只编译/校验,不执行、不生成任意代码)
- `battforge_comsol_task`(COMSOL 单入口网关:profile、启动/运行前台/后台、
  status/cancel、错误/手册相对地址、排障;**不做几何/物理场/网格**)
- `battforge_export_comsol`(BattForge prepared 参数导出:OCP/D/R_cc;
  出口只含语义量,插值由 BattForge 完成)

`tools/registry.py`:

- `tool_definitions()` → `tools/list`;
- `call_tool(name, args, session=...)` → MCP `content`/`isError`;
- `dispatch_session(session, name, args)` → 原始语义 dict;
- 长任务默认阻塞到完成;传 `wait=false` 返回 `job_id`,用 `job_status` 轮询;
- 结果只含曲线/指标/语义 checks,不返回拟合参数值/内部键名。

### 设计记忆(MCP 本地)

`client/design_memory.py` 在 MCP 侧持久化设计摘要(`~/.battsim/agent/designs.json`,
`BATTSIM_DESIGN_MEMORY` 可覆盖):`design_id`、原始 requirement、自动 label、别名、
容量/形状/尺寸摘要、创建/最近使用时间、使用次数。BattForge 侧不需要改。

* `battforge_design` 成功后自动记住;
* 用户说“上次那个 280Ah 方形” → `battforge_design_find` 检索并排序;
* 用户给设计起名/别名 → `battforge_design_remember`;
* `battforge_predict` 可直接传 `design_id`,MCP 内部 `describe`/本地缓存转成
  `cell_form`,不再要求调用方拼电极 spec;
* `battforge_ui_fill_design` 使用设计时自动更新 `last_used_at/use_count`。

```bash
# 自然语言找设计(不需要 ID)
python3 -m tools.registry --tool battforge_design_find \
  --args '{"query":"上次那个 280Ah 方形","limit":3}'

# 给设计加别名,以后用“280方壳”也能找到
python3 -m tools.registry --tool battforge_design_remember \
  --args '{"design_id":"d...","label":"280Ah VDA","aliases":["280方壳"]}'
```

### UI 自动填写(BattForge localhost 页面)

MCP 不直接改 Streamlit 的 `session_state`(外部进程没有这个 API,且
`st.file_uploader` / `st.tabs` 也不能服务端代填)。走一条**本地草稿通道**:

```text
MCP tools/ui_*  →  <BATTSIM_ROOT>/data/ui/sessions/<sid>/inbox/<draft>.json
BattForge app.py →  控件之前 consume_pending() → st.session_state
                 →  outbox/<draft>.json (applied/rejected/busy)
```

* 草稿只含语义量:形态/内腔尺寸/电极字段/扣电曲线/倍率;**不含** pybamm 键名。
* `cell_design`:默认只填页面;显式 `auto_run=true` 才自动跑 0.5C;**不保存**参数集。
* `coin_calibration`:默认只填草稿;显式 `auto_calibrate=true` 才自动校正,
  显式 `save_components=true` 才自动保存正/负极**组件**参数集;
  全电池合成只留在会话里,**永不自动保存**。
* 默认 spool 根目录 `<BATTSIM_ROOT>/data/ui`;`BATTSIM_UI_SPOOL` 可覆盖。
* 页面心跳 10 s TTL;`battforge_ui_sessions` 列活跃页面,`ui_status` 查 ack。
* `lang`:UI 工具按用户提问语言传 `zh` / `en`,BattForge 页面自动切换语言。

```bash
# 列出运行中的页面
python3 tools/ui_probe.py --list

# 设计 280 Ah 并填进页面(页面需已启动)
python3 tools/ui_probe.py --capacity 280 --shape prismatic \
    --dims 172.7,70.5,206.0 --cores 4 --rate 0.5

# 同时填热边界(x-full + 散热系数 + 环境温度)
python3 tools/ui_probe.py --capacity 52 --shape blade \
    --dims 300,13.5,90 --rate 0.5 --thermal x-full \
    --ambient-temp 25 --htc-surface 10 --htc-tab 8 --htc-edge 0.5

# 扣电正/负极曲线填进页面;显式 --save-components 才保存两个组件集
python3 tools/ui_probe.py \
    --coin-positive "$BATTSIM_ROOT/examples/coin/chen2020_positive_0p05C_window_discharge.csv" \
    --coin-negative "$BATTSIM_ROOT/examples/coin/chen2020_negative_0p05C_window_discharge.csv" \
    --coin-rate 0.05 --base-set Chen2020 --save-components
```

开发/回归:

```bash
python3 -m pytest tests/ -q
python3 -m tools.registry --list
python3 tools/coin_probe.py --synthetic
```

## License

MIT — see `LICENSE`。第三方文档原文、本地设计案例与产品侧标定数据不随本仓库分发。