---
title: LycheeAI-coder-2b-II-pro
canonical_url: "https://www.modelscope.cn/models/whcl412/LycheeAI-coder-2b-II-pro"
md_url: "https://www.modelscope.cn/models/whcl412/LycheeAI-coder-2b-II-pro.md"
repository: whcl412/LycheeAI-coder-2b-II-pro
chinese_name: "LycheeAI-coder-2b-II-pro 荔枝AI编程助手"
last_updated: 2026-09-13
license: apache-2.0
pipeline_tag: text-generation
tasks:
  - text-generation
model_type:
  - llama
architectures:
  - LlamaForCausalLM
base_model:
  - openbmb/MiniCPM5-2B
base_model_relation: quantized
parameters: 393.4M
tensor_type:
  - U32
  - BF16
library_name:
  - mlx
  - safetensors
downloads: 42
stars: 2
tags:
  - code
  - minicpm
  - chinese
  - conversational
  - tool-calling
  - mlx
  - quantization
---

# LycheeAI-coder-2b-II-pro

> LycheeAI-coder-2b-II-pro - whcl412 在 ModelScope 开源的模型。基于 MiniCPM5-2B 微调的轻量编程助手，主打工具调用（裸 JSON），支持本地 MLX 推理

whcl412/LycheeAI-coder-2b-II-pro 是 ModelScope 魔搭社区上的 393.4M 参数text-generation模型，采用 apache-2.0 许可，基于 openbmb/MiniCPM5-2B 构建。

- **Repository**: whcl412/LycheeAI-coder-2b-II-pro
- **License**: apache-2.0
- **Tasks**: text-generation
- **Parameters**: 393.4M
- **Base model**: openbmb/MiniCPM5-2B
- **Tags**: code, minicpm, chinese, conversational, tool-calling, mlx, quantization
- **Downloads**: 42
- **Stars**: 2
- **Last updated**: 2026-09-13

Source: https://www.modelscope.cn/models/whcl412/LycheeAI-coder-2b-II-pro

---

# LycheeAI-coder-2b-II-pro · MLX 4bit

> **v7** —— 用「精选 1000 条 × 1 epoch」替代「全量 5341 条 × 0.22 epoch」，
> 验收 26/27 通过（v6.1 是 25/27）。

[中文](#中文) | [English](#english)

> 基于 [MiniCPM5-2B](https://huggingface.co/openbmb/MiniCPM5-2B) 微调的轻量编程助手 · Apache 2.0
> **2B 参数，主打本地工具调用**

**这是个人开发者的项目，2B 参数注定它只是个"本地小助手"。** 它比 1B 版更能干，但请别用 GPT / Claude / DeepSeek 的标准要求它。

它最大的用处是：**跑在你自己的机器上，不用联网，能调工具，响应飞快。**

**本仓库是 MLX 4bit 量化版本**，1.4 GB，Apple Silicon 上开箱即用。

**四个版本，按需要选：**

| 版本 | 体积 | 用在什么场景 |
|---|---|---|
| **MLX 4bit**（本仓库） | 1.42 GB | Mac 上日常跑，最省内存、最快 |
| [MLX 无损 bf16](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16) | 5.03 GB | **要质量基准 / 二次量化 / 继续微调** |
| [-f16](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-f16) | 5.03 GB | 同上（**同一份权重**，方便 transformers 用户找） |
| [GGUF](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-GGUF) | 1.45 / 2.50 / 4.69 GB | Ollama、llama.cpp |

> **MLX 无损版（bf16）**是给"要在它身上再加工"的人用的：4bit 是有损的，不能作为二次量化或续训的基础。
> 它和 `-f16` 仓库是**同一份权重**（字节相同），两个名字只是为了让 MLX 用户和 transformers 用户都能找到。
> 格式是标准 safetensors，**MLX 和 transformers 都能直接加载**。

---

# 中文

## 这一版做了什么

上一版（1b-II）最大的短板是**工具调用完全不会**——训练数据里一条工具样本都没有。这一版专门补上了这块：

| 能力 | 1b-II | **2b-II-pro** |
|---|---|---|
| 工具调用格式 | ❌ 全错、字段编造 | ✅ 裸 JSON，格式稳定 |
| 单步调用 | ❌ | ✅ |
| 多步调用 | ❌ | ✅ |
| 拒绝不该做的事 | ⚠️ 时好时坏 | ✅ |
| 结果解读 | ❌ | ✅ |

**基座换了**：从 MiniCPM5-1B 换成 MiniCPM5-2B（面壁智能，Apache 2.0，512K 上下文）。

---

## 快速开始

### MLX（Apple Silicon，本仓库）

```bash
pip install mlx-lm

# 直接加载（4bit，1.4 GB）
python -m mlx_lm.generate \
  --model whcl412/LycheeAI-coder-2b-II-pro \
  --prompt "帮我写个快速排序"

# 起个 OpenAI 兼容的服务
python -m mlx_lm.server --model whcl412/LycheeAI-coder-2b-II-pro --port 8080
```

> **要在 MLX 上用无损版**（二次量化、继续微调、要质量基准），把模型名换成
> `whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16` 即可（5.03 GB，MLX 直接加载）。
> 手工拼 prompt 时记得套 chat template 并传 `enable_thinking=False`，否则会复读乱码。

### transformers

本仓库是 **MLX 4bit 格式**，transformers 不能直接加载。
需要 HF safetensors 请用无损版 → [`whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16)（或等价的 [`-f16`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-f16)）。

### Ollama（GGUF）

已发布 → [`whcl412/LycheeAI-coder-2b-II-pro-GGUF`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-GGUF)

三档可选：`q4_k_m`（1.45 GiB）/ `q8_0`（2.50 GiB，推荐）/ `f16`（4.69 GiB 无损）。
仓库里带 `Modelfile`，下载后 `ollama create` 即可用。国内可用 [ModelScope 镜像](https://modelscope.cn/models/whcl412/LycheeAI-coder-2b-II-pro-GGUF)。

⚠️ 注意：Ollama 的**自动 `tool_calls` 解析对这个小模型不可靠**——它是按「直接输出 JSON」训练的，不带运行时期望的包裹标签。生产环境请从 `content` 里自己解析 JSON，详见 GGUF 仓库 README。

---

## 工具调用

### 输出格式

它输出的是**裸 JSON**（OpenAI function calling 风格），不带任何包裹：

```json
{"name": "get_weather", "arguments": {"city": "北京"}}
```

**没有** ```json 代码块围栏、**没有** `<tool_call>` 标签、**没有**"好的我来调用工具"这种旁白。

### 单步调用

```
system: 你是 LycheeAI-coder-2b-II-pro。可以使用工具来帮助用户。

可用工具：
- get_weather(city: string): 查询指定城市的天气

user:   北京今天天气怎么样？
```

输出：

```json
{"name": "get_weather", "arguments": {"city": "北京"}}
```

### 多步调用（有依赖时只输出第一个）

问"北京今天适合出门吗"，它需要先查天气再判断，此时**只输出第一步**：

```json
{"name": "get_weather", "arguments": {"city": "北京"}}
```

你把工具结果喂回去，它再继续。**宿主需要循环驱动**：解析 → 执行 → 喂回结果 → 再解析。

### 完全独立的调用会并列输出

同时查北京和上海，它会一次给两行：

```json
{"name": "get_weather", "arguments": {"city": "北京"}}
{"name": "get_weather", "arguments": {"city": "上海"}}
```

### 该拒绝时会明确拒绝

不是所有请求都调工具。超出能力或不合规的，它会说明原因并给替代方案，不会硬编一个工具调用糊弄过去。

---

## 宿主接入注意

### 格式不是通用的

**这一点很重要**：不同 agent 框架的工具调用格式不一样。

| 框架/模型 | 格式 |
|---|---|
| OpenAI / 大多数本地框架 | 裸 JSON `{"name":..., "arguments":{...}}` |
| Anthropic / Claude Tools | XML 标签 `<function_calls><invoke name="...">` |
| 部分国产模型 | `<tool_call>` 自定义标签包 JSON |
| MCP | 传输层协议，内部通常还是 JSON-RPC |

**它默认输出裸 JSON**。如果你的宿主需要别的格式，在 system 里明确告诉它，它会按你说的来。

### 什么时候给工具，什么时候别给

它训练时见过四类场景，行为是分开的：

| 类别 | 例子 | 它的行为 |
|---|---|---|
| 工具能算出确定结果 | `789*123`、`1000 美元换人民币` | 调工具 |
| 能力/伦理不允许 | 订机票、破解 WiFi、伪造证明 | 明确拒绝 + 给替代方案 |
| 工具帮不上忙 | 什么是递归、写二分查找 | 直接回答 |
| **宿主没有工具** | 纯聊天软件里的"帮我搜一下" | 说明没有这个能力，不硬调 |

**最后一行很关键**：如果你的宿主是纯聊天（微信、客服机器人），**不要给它任何工具定义**，它会自然地说"我这边没有联网能力"，而不是吐一坨宿主接不住的 JSON。

**如果你给它 `calculate` 却不希望它算简单加减**，可以在 system 里说明。默认行为是"算术优先用工具"。

---

## 训练信息

| 项 | 值 |
|---|---|
| 基座 | `openbmb/MiniCPM5-2B` |
| 方法 | LoRA（rank 8，dropout 0，scale 20，16 层） |
| 数据量 | 980 条（精选，v7） |
| 轮次 | 1 epoch（490 步） |
| 学习率 | 5e-5（adam） |
| batch / seq | 2 / 2048 |
| 框架 | MLX-LM 0.31.3 |
| 本版本量化 | 4bit affine（group_size 64，4.501 bits/weight） |

### 数据构成（v6 系列）

v6 是在 v5 基础上做的**定向修复**，共四批：

| 批次 | 条数 | 解决什么 |
|---|---|---|
| v5 基础 | 4,526 | v6.1 起点（v5 + OOD 初版） |
| v6.1 | 4,526 | 修 v6 的 ⑥⑨ 退步项，OOD 补强 |
| v6.2 | 4,778 | 补算术泛化（K6，252 条程序化生成） |
| v6.3 | 5,093 | 补能力边界 + 结果收尾（K7，315 条） |
| **v6.4** | 5,341 | 补运算类型均衡（K8，248 条） |

**发布的是 v6.1**（不是 v6.4）。原因见下面"关于迭代"。

### 三类行为严格区分

这是训练中最容易搞混的地方：

| 类别 | 例子 | 正确行为 |
|---|---|---|
| 工具调用 | `789*123` | 调 `calculate` |
| **真拒绝**（能力/伦理不允许） | 订机票、破解 WiFi | 说不做 + 给替代方案 |
| **无需工具** | 什么是递归、写二分查找 | 直接回答，不提工具 |

混为一谈会导致模型把正常提问也当成要拒绝的事——踩过这个坑。

---

## 能力边界

**能做：**
- 写代码、调 bug、解释技术概念
- 单步 / 多步工具调用（裸 JSON）
- 拒绝能力范围外或不合规的请求
- 解读工具返回结果
- 中文 / 英文 / 粤语

**不擅长：**
- 复杂多步推理（2B 容量限制）
- 超长上下文（虽然基座支持 512K，但实际长文表现没测）
- **无工具时的算术**（会算错，请给 `calculate`——见下面"已知问题"）
- 自称身份的 OOD 场景（见下面"已知问题"）

---

## ⚠️ 已知问题

### 1. 无工具时会心算，而且**算错**

这是当前最实际的短板。实测：

```
1234 + 5678 = 2000      (正确 6912)
35 ** 2     = 70        (正确 1225)
13.5 × 4    = 7.8       (正确 54)
199 × 257   = 40043     (正确 51143)
```

**根因**（做过对照实验确认）：模型是按**运算类型**决定调不调工具的，而这跟训练数据里各类型的样本量完全对应。

| 运算类型 | 训练样本量 | 行为 |
|---|---|---|
| 乘法 `*` | 432 | ✅ 调工具 |
| 加法 `+` | 86 | ❌ 心算 |
| 幂 `**` | 61 | ❌ 心算 |
| 小数 | 61 | ❌ 心算且算错 |
| 减法 `-` | 49 | ❌ 心算 |

不是"字符串记忆"（见过 38% vs 没见过 50%，差不多），也不是"难度"（`1234+5678` 也在心算）。

**规避方法**：**只要涉及算术，就在 system 里明确要求"任何计算都必须用 calculate"**：

```
需要任何数值计算时，一律调用 calculate，不要自己心算。
```

这条指令实测有效。v6.4 试图用数据修正（K8，按运算类型均衡），但引入了更严重的副作用（工具调用率从 60%+ 崩到 12%），所以**发布了 v6.1 而没有发布 v6.4**。

### 2. 身份问答在「宿主提供的通用 system」下会出错

如果你的 system prompt 是这样（很多 agent 框架的默认写法）：

```
你是一个AI助手，可以使用工具来帮助用户。

可用工具：
- get_weather(city: str): 查询指定城市的天气
...

需要调用工具时，请直接输出 JSON：{"name": "工具名", "arguments": {...}}
```

那么问"你叫什么名字"时，它可能乱答（实测出现过"我叫 Qwen"）。

**但如果 system 是它训练时的格式，就完全正常：**

```
你是 LycheeAI-coder-2b-II-pro，由 MiniCPM5-2B 通过 LoRA 微调而来的编程助手。
```

→ 答"我叫 LycheeAI-coder-2b-II-pro，由 MiniCPM5-2B 微调而来的编程助手。"

**原因**：训练数据的身份样本里，system 全部是上面第二种。模型没见过"通用 system + 工具列表 + 问身份"这个组合，没有训练信号。

**规避方法**：接入时把身份写进 system。比如：

```
你是 LycheeAI-coder-2b-II-pro。可以使用工具来帮助用户。

可用工具：
...
```

### 3. 两种 system 结构的一致性

训练数据混了两种结构：工具定义放 `system` / 放 `user`。实测两种都能用，但**放 system 更稳**（这也是主流 agent 框架的做法）。

### 4. 工具结果有两种结构，宿主需要知道

模型见过两种上下文，**两种都认**：

**结构 A** —— 有明确的调用轮（主流 agent 框架的样子）：

```
user:      789*123 等于多少
assistant: {"name": "calculate", "arguments": {"expression": "789*123"}}
user:      [工具调用结果] {"name": "calculate", "result": {"value": 97047}}
assistant: 789 × 123 = **97047**。
```

**结构 B** —— 宿主已代为执行，直接把结果给模型（67 条早期样本用这个）：

```
user:      跑一下这段代码

[工具调用结果] {"exit_code": 0, "stdout": "45\n"}
assistant: 执行成功，输出 `45`。
```

**区别**：结构 B 的结果**不带 `name`**，因为上下文里本来就没有调用轮，工具身份从 system 的工具列表和问题本身就能推断。

**建议**：正常接入用**结构 A**（多轮循环）。只有在你已经知道该调什么、只想让模型解读结果时，才用结构 B。

---

## v7 做了什么：用「精选」代替「全量」

### 起因：一个 500 步的硬约束

新版训练被限制在 **500 步以内**。而 `步数 = ceil(条数 / batch_size)`，batch=2 → **最多 1000 条**。

原来的数据集有 5,341 条，两条路：

| 方案 | 效果 |
|---|---|
| 5,341 条 × 250 步 | 每条样本只被见到 **0.22 次**（22% epoch） |
| **980 条 × 490 步** | 每条都被完整学到 **1 次**（100% epoch）✅ |

**为什么第二条更好**——不只是"学得够"，而是**行为惯性按样本条数加权**：

原数据集里 **51.4% 是单轮纯文本**（普通聊天），它们把梯度拉向"纯聊天"；而"多轮完整链路"（调用→拿结果→收尾，宿主真实交互形态）只占 10.7%。在 22% epoch 下，每条多轮样本平均只贡献 0.22 次更新——**根本学不到**。

### 重新配比：按行为重要性，而不是按原始比例

| 类别 | 原数据集 | v7 精选 | 说明 |
|---|---|---|---|
| 多轮链路（调用→结果→收尾） | 569 | **201（20.5%）** | 最值钱，宿主真实路径 |
| 工具在 user + 调用 | 780 | 243（24.8%） | 占原始 66%，结构不能丢 |
| 有工具但不调 | 819 | 216（22.0%） | 压住"手痒调工具"的反面样本 |
| 工具在 system + 调用 | 397 | 179（18.3%） | 主结构 |
| 单轮纯文本 | 2,746 | **130（13.3%）** | 从 51.4% 砍到 13.3% |

### 算术问题的真正解法：两头夹

之前的分析已经找到根因——**乘法独占 50.5% 的样本**，所以模型学到的是"乘法 → 调工具"这个**特例**，而不是"算术 → 调工具"这个**通则**。

v6.4 试过只做"补"（补 248 条稀缺类型），结果**配比反而更差**，还把工具调用率从 60%+ 砸到 12%。

v7 用**两头夹**：

```
一头补稀缺：K9 生成 90 条，按稀缺度加权（小数加法最缺 → 补最多）
一头削过剩：对基线里超量的乘法样本下采样，删掉 48 条
```

结果——乘法占比 **50.5% → 20.0%**，七个运算类型的最热/最冷差距从 **30.5 倍降到 2.4 倍**：

| 运算类型 | v6.1 | v7 |
|---|---|---|
| 乘法 | 244（50.5%） | 81（20.0%） |
| 幂 | 44（9.1%） | 66（16.3%） |
| 除法 | 53（11.0%） | 62（15.3%） |
| 加法 | 71（14.7%） | 54（13.3%） |
| 小数乘法 | 31（6.4%） | 47（11.6%） |
| 减法 | 31（6.4%） | 42（10.4%） |
| 小数加法 | 8（1.7%） | 35（8.6%） |

**直接效果**：v7 修好了两个长期存在的验收失败项——①b 第二道算术题、⑧b 多轮追问。

### 修掉一个源数据缺陷：工具名幻觉

质检时发现 **9 处自相矛盾的样本**：system 里只定义了 `calculate`，模型却去调 `read_file` / `get_weather`。

根因在数据生成脚本：「多轮追问」那批轨迹里有些是查天气、读文件，但 system 被写死只定义 `calculate`。**这等于在教模型"调用没定义的工具"**。

修法：先扫轨迹实际调用了哪些工具，只定义那些。

> 但修完发现新问题：「见过 ≥3 种工具」的样本从 56 条掉到 0 条——模型失去了「在多个候选里挑对的」训练信号。于是又加了**干扰工具**（2/3 的样本掺入 1~3 个无关工具，但不掺 `calculate`），恢复到 24 条。
>
> **教训：修一个问题很容易带出另一个。每次修改后必须重新做全量质检。**

### 一个影响深远的方法论修正：稳定采样

第一次尝试修验收 ⑥ 时，我调高了某类配额、砍了另一类，结果 **26/1 变成 23/4**——修一个坏三个。

查数据才发现真因：**改配额会让 40% 的样本被重洗**（两版只有 585 条共有）。等于每做一次实验就换了个数据集，任何结论都不可归因。**这正是 v6 系列"修一个坏一个"的机制。**

修法：把采样改成**按内容哈希排序**，改配额变成"单调扩展"——原来选中的样本仍然全部在，只多补差额。

验证：只改一类配额（260→220），**重叠率 99.9%**。

### 验收结果

| 版本 | 验收 | 说明 |
|---|---|---|
| v6.1（上一版） | 25 / 2 | ❌ ①b 第二道算术、❌ ⑧b 多轮追问 |
| **v7（本版）** | **26 / 1** | ✅ ①b、⑧b 全修好；❌ ⑥ 工具放 user |

**26/1 已经过复现验证**：同一份数据独立重跑一次训练，结果完全一致（含失败项相同）。

### 为什么没继续修 ⑥

⑥ 的考点是"工具定义放在 user 消息里"（而不是 system）。v7 在这一项退化成心算。

后续试了两个方向：
- **v7b**：调高 user 结构配额 + 砍多轮样本 → **23/4**，修一个坏三个
- **v7c**：不动配额，纯追加 90 条该考点样本 → **22/5**，工具调用倾向被整体稀释

三次尝试指向同一个结论：

> **在 1000 条预算内，任何"定向补强"都会挤掉其他能力。26/1 已经是这个预算下的最优解。**

⑥ 属于**兼容性场景**，不是核心能力。如果你的宿主把工具定义放在 user 消息里，请在 system 中补一句身份/工具说明（见上面「已知问题 3」），或改用把工具定义放 system 的标准写法。

### 五轮迭代的完整复盘

| 版本 | 验收 | 结果 |
|---|---|---|
| v6 | 24 / 3 | ⑥⑨ 坏，①b 好 |
| v6.1 | 25 / 2 | ✅ 曾发布 |
| v6.2 | 24 / 3 | 修好 ①b，但 ⑧a ⑨g 退步 |
| v6.3 | 25 / 2 | 修好 ⑧a ⑨g，①b 又退；**逐项与 v6.1 相同（等于白跑）** |
| v6.4 | 25/2 + ④坏 | 调用率崩到 12% |
| **v7** | **26 / 1** | ✅ 本版发布 |
| v7b | 23 / 4 | 尝试修 ⑥ 失败 |
| v7c | 22 / 5 | 尝试修 ⑥ 失败（另一方向） |

**核心教训**：v6 系列每轮都是「发现问题 → 补 5% 数据 → 全量重训」，把整个能力分布重洗一遍，结果**修一个坏一个**。**补丁数据的效力取决于它占新数据集的比例，不是它的绝对条数**——v6.4 补了 248 条去对冲 244 条乘法基线，占比只有 4.6%，压不住已有惯性。

v7 换了方法：**改配比，不改增量**。不往大数据集里塞补丁，而是重新精选整个数据集。

---

## 许可

Apache 2.0，跟基座 MiniCPM5-2B 一致。

训练数据、脚本都在项目里，欢迎 issue 交流。

---

# English

## What's new

The biggest gap in the previous release (1b-II) was that it **couldn't call tools at all** — the training data contained zero tool-use samples. This release is built specifically to fix that:

| Capability | 1b-II | **2b-II-pro** |
|---|---|---|
| Tool-call format | ❌ malformed, invented fields | ✅ bare JSON, stable |
| Single-step calls | ❌ | ✅ |
| Multi-step calls | ❌ | ✅ |
| Refusing what it shouldn't do | ⚠️ inconsistent | ✅ |
| Interpreting tool results | ❌ | ✅ |

**New base model**: switched from MiniCPM5-1B to MiniCPM5-2B (OpenBMB, Apache 2.0, 512K context).

**This repo is the MLX 4-bit quantized build**, 1.4 GB, ready to run on Apple Silicon.

**Four builds, pick by use case:**

| Build | Size | Use case |
|---|---|---|
| **MLX 4-bit** (this repo) | 1.42 GB | Everyday use on a Mac — fastest, smallest footprint |
| [MLX lossless bf16](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16) | 5.03 GB | **Quality baseline / re-quantization / further fine-tuning** |
| [-f16](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-f16) | 5.03 GB | Same as above (**identical weights**, named for transformers users) |
| [GGUF](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-GGUF) | 1.45 / 2.50 / 4.69 GB | Ollama, llama.cpp |

> **The MLX lossless (bf16) build** is for anyone who needs to *build on top of* this model —
> 4-bit is lossy and unsuitable as a base for re-quantization or continued training.
> It holds **byte-identical weights** to the `-f16` repo; the two names exist so both MLX and
> transformers users can find it. It's a standard safetensors checkpoint — **loadable by MLX and transformers alike**.

---

## Quick start

### MLX (Apple Silicon, this repo)

```bash
pip install mlx-lm

python -m mlx_lm.generate \
  --model whcl412/LycheeAI-coder-2b-II-pro \
  --prompt "write a quicksort"

python -m mlx_lm.server --model whcl412/LycheeAI-coder-2b-II-pro --port 8080
```

### transformers

This repo is in **MLX 4-bit format** and cannot be loaded by transformers.
Use the lossless build for HF safetensors → [`whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-MLX-bf16) (or the equivalent [`-f16`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-f16)).

### Ollama (GGUF)

Published → [`whcl412/LycheeAI-coder-2b-II-pro-GGUF`](https://huggingface.co/whcl412/LycheeAI-coder-2b-II-pro-GGUF)

Three variants: `q4_k_m` (1.45 GiB) / `q8_0` (2.50 GiB, recommended) / `f16` (4.69 GiB, lossless).
The repo ships a `Modelfile` — download, then `ollama create`.

⚠️ Note: Ollama's **automatic `tool_calls` parsing is unreliable for this small model** — it was trained to emit bare JSON, without the wrapper tags runtimes expect. For production, parse the JSON from `content` yourself. See the GGUF repo README.

---

## Tool calling

### Output format

It emits **bare JSON** (OpenAI function-calling style), with no wrapper at all:

```json
{"name": "get_weather", "arguments": {"city": "Beijing"}}
```

**No** ```json fences, **no** `<tool_call>` tags, **no** "Sure, let me call the tool" narration.

### Multi-step calls (only the first step is emitted when dependent)

For "Is it a good day to go out in Beijing?", it needs to check the weather first. In that case it **emits only the first step**:

```json
{"name": "get_weather", "arguments": {"city": "Beijing"}}
```

Feed the tool result back and it continues. **Your host must drive the loop**: parse → execute → feed result back → parse again.

### Independent calls are emitted in parallel

```json
{"name": "get_weather", "arguments": {"city": "Beijing"}}
{"name": "get_weather", "arguments": {"city": "Shanghai"}}
```

### It refuses clearly when it should

Not every request becomes a tool call. When something is out of scope or disallowed, it says so and offers alternatives instead of fabricating a tool call.

---

## When to give it tools, and when not to

| Category | Example | Behavior |
|---|---|---|
| Tool gives a definite answer | `789*123`, `1000 USD to CNY` | calls the tool |
| Out of scope / disallowed | booking flights, cracking WiFi | refuses + offers alternatives |
| Tool doesn't help | "what is recursion", "write binary search" | answers directly |
| **Host has no tools** | "search for me" in a plain chat app | says it can't, doesn't emit JSON |

**The last row matters**: if your host is a plain chat app, **give it no tool definitions at all**. It will say "I have no web access" instead of emitting JSON your host can't handle.

---

## Training

| Item | Value |
|---|---|
| Base | `openbmb/MiniCPM5-2B` |
| Method | LoRA (rank 8, dropout 0, scale 20, 16 layers) |
| Data | 5,341 samples |
| Epochs | 1 (2,671 steps) |
| LR | 5e-5 (adam) |
| batch / seq | 2 / 2048 |
| Framework | MLX-LM 0.31.3 |
| Quantization | 4-bit affine (group_size 64) |

---

## ⚠️ Known issues

### 1. It does mental arithmetic without tools — and gets it wrong

```
1234 + 5678 = 2000      (should be 6912)
35 ** 2     = 70        (should be 1225)
13.5 × 4    = 7.8       (should be 54)
199 × 257   = 40043     (should be 51143)
```

**Root cause** (confirmed by controlled experiment): the model decides based on **operation type**, and this maps exactly onto per-type sample counts in the training data — multiplication 432 samples → calls the tool; addition 86 / power 61 / subtraction 49 → does it in its head.

It is **not** string memorization (38% vs 50%, no real difference), and **not** difficulty (`1234+5678` also gets mental math).

**Workaround**: state it explicitly in the system prompt:

```
需要任何数值计算时，一律调用 calculate，不要自己心算。
```

### 2. Identity questions fail under a generic system prompt

If your system prompt is the generic "You are an AI assistant with tools..." style, asking "what's your name" may produce nonsense (observed: "I'm Qwen").

Put the identity in the system prompt:

```
You are LycheeAI-coder-2b-II-pro. You have access to tools.
```

Cause: every identity sample in the training data used the specific system prompt above; the model never saw the generic-tools-plus-identity combination.

### 3. Tool results come in two shapes — your host should know which it's sending

Both are understood:

**Shape A** — an explicit call turn (what mainstream agent frameworks do):

```
user:      789*123 等于多少
assistant: {"name": "calculate", "arguments": {"expression": "789*123"}}
user:      [工具调用结果] {"name": "calculate", "result": {"value": 97047}}
assistant: 789 × 123 = **97047**。
```

**Shape B** — the host already executed it and hands the result straight over (used by 67 early samples):

```
user:      跑一下这段代码

[工具调用结果] {"exit_code": 0, "stdout": "45\n"}
assistant: 执行成功，输出 `45`。
```

**Difference**: shape B has no `name` in the result, because there is no call turn in context — the tool identity is inferable from the system tool list and the question itself.

**Recommendation**: use **shape A** for normal integration (multi-turn loop). Use shape B only when you already know which tool to run and just want the model to interpret the result.

### 4. Why v6.1 and not v6.4

Five iterations were trained. v6.2 fixed one item and broke two; v6.3 was item-for-item identical to v6.1; v6.4 collapsed the tool-call rate to 12%. **v6.1 is the only version with a real improvement over v6** (25/2 vs 24/3 on a 27-item eval).

Lesson: each round patched ~5% of the data and retrained on everything, reshuffling the whole behavior distribution. The next version will validate on small-scale runs first.

---

## License

Apache 2.0, same as the MiniCPM5-2B base.

---

<p align="center">
<sub>LycheeAI —— 小的，但能干活。</sub><br>
<sub>LycheeAI — small, but it gets the job done.</sub>
</p>
