# dotpromptz

一个 Python 库，用于解析和渲染 `.prompt` 格式的模板文件。`.prompt` 文件基于 Handlebars 模板语法，结合 YAML 前置元数据（frontmatter），提供了一种声明式的方式来定义 LLM 提示词。

> **快速上手**：[`examples/`](./examples/) 目录包含多个可直接运行的 `.prompt` 示例文件。如果你想一次了解所有可用字段和模板语法，请参考完整字段参考模板 [`all_fields.prompt.template`](./examples/all_fields.prompt.template)（该文件使用 `.prompt.template` 扩展名，仅作参考，不可被 `prompt run` 直接执行）。

## 安装

```bash
# 作为 CLI 工具安装（推荐）
uv tool install dotpromptz-py

# 或作为项目依赖安装
uv add dotpromptz-py
```

要求 Python >= 3.12。

### 升级

```bash
# CLI 工具升级
uv tool upgrade dotpromptz-py

# 项目依赖升级
uv add --upgrade dotpromptz-py
```

也可以通过 CLI 命令直接升级：

```bash
prompt --upgrade
# 或
prompt -U
```

## 快速开始

### 命令行使用

```bash
# 渲染并调用 LLM，输出结果到文件
prompt run my_prompt.prompt

# 仅渲染模板（不调用 LLM），输出渲染结果
prompt build my_prompt.prompt
```


---

## `.prompt` 文件格式

一个 `.prompt` 文件由两部分组成：

1. **YAML Frontmatter** — 配置元数据（用 `---` 包裹）
2. **模板正文** — Handlebars 模板，定义发送给 LLM 的消息内容

```
---
# YAML 前置元数据
adapter: openai
config:
  model: gpt-4o
output:
  format: txt
  file_name: "result"
---
这里是模板正文，支持 Handlebars 语法。
你好，{{name}}！
```

---

## Frontmatter 字段参考

### `version`

| 属性     | 值           |
| -------- | ------------ |
| 类型     | `int`      |
| 是否必需 | **是** |

dotpromptz 的主版本号（major version），对应 `dotpromptz` 包的大版本。用于前向兼容性检查：当 `.prompt` 文件的 `version` 高于当前引擎版本时，会抛出错误。

```yaml
version: 1
```

---

### `description`

| 属性     | 值       |
| -------- | -------- |
| 类型     | `str`  |
| 默认值   | `null` |
| 是否必需 | 否       |

对该 prompt 的人类可读描述。

```yaml
description: "生成产品描述的提示词"
```

---

### `adapter`

| 属性     | 值                         |
| -------- | -------------------------- |
| 类型     | `str` 或 `object`      |
| 默认值   | `null`                   |
| 是否必需 | **`run` 命令必需** |

指定使用的 LLM 适配器。支持的值：`openai`、`anthropic`、`google`。

**简写形式：**

```yaml
adapter: openai
```

**对象形式**（支持凭据组）：

```yaml
adapter:
  name: openai
  group: my-group        # 单个凭据组
  # 或
  groups:                 # 多个凭据组（轮询）
    - group-a
    - group-b
```

| 子字段     | 类型          | 是否必需 | 说明                 |
| ---------- | ------------- | -------- | -------------------- |
| `name`   | `str`       | 是       | 适配器名称           |
| `group`  | `str`       | 否       | 单个凭据组名         |
| `groups` | `list[str]` | 否       | 凭据组列表，轮询使用 |

### `config`

| 属性     | 值       |
| -------- | -------- |
| 类型     | `dict` |
| 默认值   | `null` |
| 是否必需 | 否       |

模型的具体配置参数。不同适配器支持的参数可能不同，以下是通用参数。在 `.prompt` 文件中统一使用 **snake_case** 命名，dotpromptz 会在内部自动映射为各 API 提供商要求的字段名。

```yaml
config:
  model: gpt-4o
  temperature: 0.7
  max_tokens: 2048
  top_p: 0.9
  seed: 42
  stop: ["\n\n"]
```

| 子字段                | 类型          | 说明              |
| --------------------- | ------------- | ----------------- |
| `model`             | `str`       | 模型名称          |
| `temperature`       | `float`     | 采样温度          |
| `max_tokens`        | `int`       | 最大生成 token 数 |
| `top_p`             | `float`     | 核采样参数        |
| `seed`              | `int`       | 随机种子          |
| `stop`              | `list[str]` | 停止序列          |
| `frequency_penalty` | `float`     | 频率惩罚          |
| `presence_penalty`  | `float`     | 存在惩罚          |

---

### `input`

| 属性     | 值         |
| -------- | ---------- |
| 类型     | `object` |
| 默认值   | `null`   |
| 是否必需 | 否         |

定义输入数据来源和默认值。当提供多条数据时，自动进入批量处理模式。

**简写形式**（直接作为数据）：

```yaml
input:
  name: Alice
  topic: Python
```

**完整形式**（支持 `defaults` 和 `data`）：

```yaml
input:
  defaults:
    language: Chinese
  data:
    - name: Alice
      topic: Python
    - name: Bob
      topic: Rust
```

**从文件加载**（支持多个文件路径）：

```yaml
# 单个文件
input:
  data: data/users.jsonl

# 多个文件
input:
  data:
    - data/batch_1.jsonl
    - data/batch_2.json
    - data/extra.yaml
```

> **文件路径解析规则**：`data` 中的文件路径为**相对于 `.prompt` 文件所在目录**的相对路径。例如，若 `.prompt` 文件位于 `prompts/my_task.prompt`，`data: data/users.jsonl` 会解析为 `prompts/data/users.jsonl`。也支持绝对路径。

| 子字段       | 类型                                             | 说明                                                       |
| ------------ | ------------------------------------------------ | ---------------------------------------------------------- |
| `defaults` | `dict`                                         | 固定默认值，合并到每条记录（记录中的同名字段会覆盖默认值） |
| `data`     | `dict`、`list[dict]`、`str`、`list[str]` | 内联数据、单个文件路径或**多个文件路径列表**         |

**支持的数据文件格式**：

| 格式                 | 说明                                   |
| -------------------- | -------------------------------------- |
| `.json`            | 单个对象或对象数组                     |
| `.yaml` / `.yml` | 单个对象或对象数组                     |
| `.jsonl`           | 每行一个 JSON 对象（始终视为批量数据） |

---

### `output`

| 属性     | 值                         |
| -------- | -------------------------- |
| 类型     | `object`                 |
| 默认值   | `null`                   |
| 是否必需 | **`run` 命令必需** |

定义输出格式和存储路径。

```yaml
output:
  format: json
  schema: |
    name: string, The user's name
    age: integer, The user's age
    tags(array): string
  output_dir: "output/{{NAME}}"
  file_name: "profile_{{name}}"
  aggregate: false
```

| 子字段         | 类型     | 默认值                         | 是否必需                   | 说明                                                                 |
| -------------- | -------- | ------------------------------ | -------------------------- | -------------------------------------------------------------------- |
| `format`     | `str`  | —                             | **是**               | 输出格式：`json`、`yaml`、`txt`、`image`                     |
| `schema`     | `str`  | `null`                       | 否                         | 输出的 schema 定义（Picoschema 格式），仅 `json`/`yaml` 格式有效 |
| `output_dir` | `str`  | `output/{{NAME}}_{{TIME}}` | 否                         | 输出目录路径，支持模板变量                                           |
| `file_name`  | `str`  | —                             | **`run` 命令必需** | 输出文件名（不含扩展名），支持自动变量和输入变量模板 |
| `aggregate`  | `bool` | `false`                      | 否                         | 设为 `true` 时，批量处理的所有结果合并到一个文件                   |

> **注意**：`output_dir` 和 `file_name` 均支持 `{{NAME}}`、`{{TIME}}` 模板变量。`file_name` 还支持 `{{INDEX}}`（批量索引）和输入变量（如 `{{name}}`、`{{lang}}`）。批量处理时若 `file_name` 不含 `{{INDEX}}`，每个文件会自动添加 `_{{INDEX}}` 后缀。

```yaml
# 示例：使用自动变量和输入变量
output:
  file_name: "profile_{{name}}"        # 输入变量
  file_name: "translation_{{INDEX}}"  # 批量索引
  file_name: "{{NAME}}_{{TIME}}"     # prompt 文件名 + 时间戳
  file_name: "{{NAME}}_{{lang}}"      # 混合使用
```

---

### `tools`

| 属性     | 值            |
| -------- | ------------- |
| 类型     | `list[str]` |
| 默认值   | `null`      |
| 是否必需 | 否            |

声明可供模型调用的工具名称列表。

```yaml
tools:
  - get_weather
  - search_database
```

---

### `toolDefs`

| 属性     | 值               |
| -------- | ---------------- |
| 类型     | `list[object]` |
| 默认值   | `null`         |
| 是否必需 | 否               |

内联定义工具。每个工具包含名称、描述和参数 schema。

```yaml
toolDefs:
  - name: get_weather
    description: "获取指定城市的天气"
    inputSchema:
      type: object
      properties:
        city:
          type: string
          description: "城市名称"
      required: [city]
```

---

### `runtime`

| 属性     | 值         |
| -------- | ---------- |
| 类型     | `object` |
| 默认值   | `null`   |
| 是否必需 | 否         |

执行时的运行参数，控制并发、超时和重试。

```yaml
runtime:
  max_workers: 5
  timeout: 30.0
  retry:
    max_retries: 3
    retry_delay: 1.0
```

| 子字段                | 类型      | 默认值   | 说明                                                     |
| --------------------- | --------- | -------- | -------------------------------------------------------- |
| `max_workers`       | `int`   | `5`    | 批量处理时的最大并发数                                   |
| `timeout`           | `float` | `null` | 单次请求的超时时间（秒）                                 |
| `retry.max_retries` | `int`   | `3`    | 最大重试次数                                             |
| `retry.retry_delay` | `float` | `1.0`  | 重试间隔基数（秒），实际延迟 =`delay × (attempt + 1)` |

---

### 扩展字段 (`ext`)

YAML frontmatter 中任何不属于上述保留字段的键值对，会自动归入 `ext` 字典。你可以用它存储自定义元数据。

```yaml
---
adapter: openai
my_custom_field: hello
tags: [test, demo]
---
```

此时 `ext` 为 `{"my_custom_field": "hello", "tags": ["test", "demo"]}`。

---

## 模板语法

模板正文使用 [Handlebars](https://handlebarsjs.com/) 语法。

### 变量插值

```handlebars
你好，{{name}}！你对 {{topic}} 感兴趣。
```

支持嵌套属性访问：

```handlebars
用户 {{user.name}} 的邮箱是 {{user.email}}。
```

### 条件判断

```handlebars
{{#if premium}}
你是高级用户，享有优先服务。
{{else}}
欢迎注册成为高级用户。
{{/if}}
```

### 循环

```handlebars
你的兴趣爱好：
{{#each hobbies}}
- {{this}}
{{/each}}
```

### `{{#with}}` 块

```handlebars
{{#with user}}
姓名：{{name}}
年龄：{{age}}
{{/with}}
```

---

## 自动变量（UPPERCASE 变量）

dotpromptz 自动注入以下大写变量，可在模板正文和 frontmatter 的部分字段中使用。大写变量为自动注入，小写变量为用户输入：

| 变量            | 说明                                           | 可用位置                                                   | 示例值                      |
| --------------- | ---------------------------------------------- | ---------------------------------------------------------- | --------------------------- |
| `{{NAME}}`   | 当前 `.prompt` 文件的名称（不含扩展名）      | 模板正文、frontmatter 字段、`output_dir`、`file_name` | `draw_cat`                |
| `{{TIME}}`   | 当前时间戳                                     | 模板正文、frontmatter 字段、`output_dir`、`file_name` | `2025_01_15_14_30_00`     |
| `{{INDEX}}`  | 批量处理中的项目索引（从 0 开始）              | `file_name`                                              | `0`、`1`、`2`           |
| `{{SCHEMA}}` | 从 `output.schema` 生成的 JSON Schema 字符串 | 模板正文、frontmatter 字段                                 | `{"type": "object", ...}` |
### `{{SCHEMA}}` 的使用

当你定义了 `output.schema` 后，`{{SCHEMA}}` 会自动包含对应的 JSON Schema。你可以在模板中引用它来指导模型输出结构化数据：

```
---
output:
  format: json
  schema: |
    name: string, The user's name
    age: integer
---
请按照以下 JSON Schema 输出：
{{SCHEMA}}

为 {{name}} 生成一份用户档案。
```

### `{{NAME}}`、`{{TIME}}`、`{{INDEX}}` 在 frontmatter 中的使用

`output_dir` 和 `file_name` 字段支持这些变量：

```yaml
output:
  output_dir: "results/{{NAME}}"
  file_name: "{{NAME}}_{{INDEX}}"
```

`file_name` 还支持输入变量（如 `{{name}}`、`{{lang}}`），它们会根据每条输入记录动态解析：

```yaml
output:
  file_name: "profile_{{name}}"  # 每条记录生成不同的文件名
```


---

## 内置 Handlebars Helpers

### `{{role "..."}}`

切换消息角色。有效角色：`system`、`user`、`model`、`tool`。

```handlebars
{{role "system"}}
你是一个专业的翻译助手。

{{role "user"}}
请翻译以下内容：{{text}}
```

默认（不使用 `{{role}}` 时），整个模板正文作为 `user` 角色的消息。

---

### `{{history}}`

插入对话历史记录。用于多轮对话场景。

```handlebars
{{role "system"}}
你是一个聊天助手。

{{history}}

{{role "user"}}
{{message}}
```

---

### `{{media url="..." contentType="..."}}`

嵌入媒体内容（如图片）。

```handlebars
{{role "user"}}
请描述这张图片：
{{media url=imageUrl contentType="image/png"}}
```

| 参数            | 类型    | 说明                                          |
| --------------- | ------- | --------------------------------------------- |
| `url`         | `str` | 媒体资源的 URL 或 base64 数据                 |
| `contentType` | `str` | MIME 类型（如 `image/png`、`image/jpeg`） |

---

### `{{json variable indent=N}}`

将变量序列化为 JSON 字符串。

```handlebars
数据如下：
{{json userData indent=2}}
```

| 参数       | 类型    | 默认值   | 说明            |
| ---------- | ------- | -------- | --------------- |
| 第一个参数 | 变量    | —       | 要序列化的变量  |
| `indent` | `int` | `null` | JSON 缩进空格数 |

---

### `{{#ifEquals a b}}...{{/ifEquals}}`

条件判断：当两个值相等时渲染内容。

```handlebars
{{#ifEquals role "admin"}}
你拥有管理员权限。
{{else}}
你是普通用户。
{{/ifEquals}}
```

---

### `{{#unlessEquals a b}}...{{/unlessEquals}}`

条件判断：当两个值不相等时渲染内容。

```handlebars
{{#unlessEquals status "active"}}
你的账号状态异常。
{{/unlessEquals}}
```

---

### `{{section "name"}}`

创建一个占位标记（pending section），用于在后续处理中插入内容。

```handlebars
{{section "summary"}}
```

---

## Picoschema 格式

`output.schema` 使用 Picoschema — 一种简洁的 schema 定义语法，会自动转换为标准 JSON Schema。

### 基本类型

```
name: string
age: integer
score: number
active: boolean
data: any
```

支持的标量类型：`string`、`number`、`integer`、`boolean`、`null`、`any`。

### 可选字段

使用 `?` 标记可选字段：

```
name: string
nickname?: string
```

### 字段描述

在类型后用逗号添加描述：

```
name: string, The user's display name
age: integer, User's age in years
```

### 数组类型

```
tags(array): string
scores(array): number
```

### 嵌套对象

```
address(object):
  street: string
  city: string
  zip?: string
```

### 枚举类型

```
status(enum):
  - PENDING
  - APPROVED
  - REJECTED
```

### 通配符

允许对象包含任意额外属性：

```
config(object):
  name: string
  (*): any
```

### 完整示例

```yaml
output:
  format: json
  schema: |
    name: string, The user's full name
    age: integer, Age in years
    email?: string, Email address
    hobbies(array): string
    address(object):
      city: string
      country: string
    role(enum):
      - ADMIN
      - USER
      - GUEST
```

---

## Frontmatter 中的模板渲染

Frontmatter 中的字段值（**`input` 部分除外**）支持 Handlebars 模板语法。这意味着你可以在 `config`、`description` 等字段中引用变量：

```yaml
---
adapter: openai
config:
  model: "{{model_name}}"
output:
  format: txt
  output_dir: "output/{{category}}"
  file_name: "report"
---
```

> **注意**：`input` 部分不会进行模板渲染，以避免循环引用。`file_name` 支持自动变量和输入变量（见上方 `output` 章节说明）。

---

## 批量处理

当 `input.data` 包含多条记录（数组或 `.jsonl` 文件）时，dotpromptz 自动进入批量处理模式：

- 每条记录独立渲染模板并调用 LLM
- 使用 `runtime.max_workers` 控制并发数（默认 5）
- 每条记录生成独立的输出文件

### 合并输出

设置 `output.aggregate: true` 可将所有批量结果合并到一个文件中：

- **JSON 格式**：合并为 JSON 数组
- **YAML 格式**：合并为 YAML 列表
- **TXT 格式**：合并为 JSON 字典（键为文件名，值为内容），输出为 `.json` 文件

---

## 示例

完整的可运行示例请参考 [`examples/`](./examples/) 目录：

| 示例             | 文件                                                             | 功能演示                              |
| ---------------- | ---------------------------------------------------------------- | ------------------------------------- |
| **完整字段参考** | [`all_fields.prompt.template`](./examples/all_fields.prompt.template) | 所有 frontmatter 字段 + 模板语法一览（不可运行） |
| 基础文本生成     | [`basic_text.prompt`](./examples/basic_text.prompt)               | 单条输入、文本输出、多角色消息        |
| 批量处理         | [`batch_translate.prompt`](./examples/batch_translate.prompt)     | `.jsonl` 文件输入、批量并发         |
| 结构化 JSON 输出 | [`structured_output.prompt`](./examples/structured_output.prompt) | `SCHEMA`、Picoschema、JSON 输出    |
| 合并输出         | [`aggregate_reviews.prompt`](./examples/aggregate_reviews.prompt) | `aggregate: true`、批量合并         |
| 多轮对话         | [`chat_with_history.prompt`](./examples/chat_with_history.prompt) | `{{role}}`、`{{history}}`、多角色 |
| 媒体输入         | [`describe_image.prompt`](./examples/describe_image.prompt)       | `{{media}}`、图片理解               |

每个示例输出到不同的子目录下，运行方式：

```bash
prompt run examples/basic_text.prompt
# ...以此类推
```

---

## 许可证

参见 [LICENSE](./LICENSE) 文件。
