# How to Create Custom Report Templates in ReportEngine: A Complete Guide

> Learn how to create custom report templates in ReportEngine by adding Markdown files or using the custom_template parameter. Unlock powerful, personalized reporting today.

- Repository: [BaiFu/bettafish](https://github.com/666ghj/bettafish)
- Tags: how-to-guide
- Published: 2026-02-23

---

**You can create custom report templates in ReportEngine by either adding Markdown files to the `ReportEngine/report_template` directory for automatic discovery or passing a raw Markdown string via the `custom_template` parameter to bypass auto-selection.**

The bettafish repository provides a flexible ReportEngine that generates structured reports using Markdown-based templates. Understanding how to create custom report templates in ReportEngine allows you to control the visual and structural skeleton of every generated report, from section headings to overall layout.

## Understanding ReportEngine's Template Architecture

### Template Storage and Discovery

Templates are simple Markdown (`.md`) files stored under `ReportEngine/report_template`. The engine discovers these files through `TemplateSelectionNode._get_available_templates` in [`ReportEngine/nodes/template_selection_node.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/nodes/template_selection_node.py) (lines 26-44). This method walks the directory specified by `self.template_dir`, which defaults to `Settings.TEMPLATE_DIR` defined in [`ReportEngine/utils/config.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/utils/config.py) (line 59).

Each file is read, and `_extract_template_description` (lines 54-68) derives a short description from the filename to present to the LLM for selection.

### Template Selection Logic

The central orchestration happens in [`ReportEngine/agent.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/agent.py). The `ReportAgent._select_template` method (lines 914-918) checks if a `custom_template` argument is provided. If non-empty, it returns a dictionary with `template_name="custom"` and the supplied content, skipping the LLM-driven selection logic entirely.

## Method 1: Creating Reusable Template Files

To create a permanent template that the LLM can auto-select, add a new Markdown file to the template directory. The engine treats these as first-class templates that can be automatically selected based on query context.

Create a file following this structure:

```markdown

### **我的专项报告模板**

- **1.0 项目概览**
  - 1.1 背景
  - 1.2 目标
- **2.0 方法论**
  - 2.1 数据来源
  - 2.2 分析模型
- **3.0 结果与建议**
  - 3.1 关键洞察
  - 3.2 行动方案

```

Save this as `ReportEngine/report_template/我的专项报告模板.md`. Use a numeric heading hierarchy (e.g., `### 1.0 章节标题`) so that `parse_template_sections` can recognize sections correctly. The engine automatically includes it in the candidate list shown to the LLM.

## Method 2: Passing Custom Templates at Runtime

For ad-hoc layouts without modifying repository files, pass a raw Markdown string via the `custom_template` parameter.

### Via Command Line Interface

The CLI entry point [`report_engine_only.py`](https://github.com/666ghj/bettafish/blob/main/report_engine_only.py) forwards the `custom_template` argument to the engine:

```bash
python report_engine_only.py --query "2024 市场趋势" \
    --custom-template "$(cat my_template.md)"

```

### Via Flask API

The HTTP API endpoint in [`ReportEngine/flask_interface.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/flask_interface.py) accepts `custom_template` in the JSON payload:

```python
import requests
import json

url = "http://localhost:5000/api/report"
payload = {
    "query": "企业品牌声誉分析",
    "custom_template": """### **企业品牌声誉报告模板**

- **1.0 报告概览**
  - 1.1 品牌现状
  - 1.2 受众情感
- **2.0 竞争对手对比**
  - 2.1 关键指标
  - 2.2 差距分析
- **3.0 行动建议**
  - 3.1 媒体策略
  - 3.2 危机预案"""
}
headers = {"Content-Type": "application/json"}
resp = requests.post(url, data=json.dumps(payload), headers=headers)
print(resp.json())

```

### Via Python API

Use `ReportAgent` directly for programmatic control:

```python
from ReportEngine.agent import ReportAgent

agent = ReportAgent()
custom_md = """

### **专项调研报告模板**

- **1.0 调研目标**
  - 1.1 背景描述
  - 1.2 关键问题
- **2.0 方法与数据**
  - 2.1 数据来源
  - 2.2 分析框架
- **3.0 发现与结论**
  - 3.1 主要洞察
  - 3.2 建议措施
"""
result = agent.generate_report(
    query="AI 产业链趋势",
    reports=["..."],
    forum_logs="",
    custom_template=custom_md,
    save_report=False
)
print(result["html_content"])

```

## How the Engine Processes Custom Templates

Once selected, templates are processed by `ReportAgent._slice_template` (lines 1003-1016 in [`agent.py`](https://github.com/666ghj/bettafish/blob/main/agent.py)). This method calls `parse_template_sections` to convert the Markdown into a list of `TemplateSection` objects.

These sections drive the subsequent layout, word-budget, and chapter-generation nodes, regardless of whether the template came from a file in `ReportEngine/report_template` or a custom string passed at runtime.

## Summary

- **Template files** are Markdown documents stored in `ReportEngine/report_template` that the LLM can auto-select based on query context.
- **Custom templates** bypass auto-selection when passed as strings via the `custom_template` parameter in the CLI, Flask API, or Python API.
- The selection logic resides in `ReportAgent._select_template` (lines 914-918), while parsing happens in `_slice_template` (lines 1003-1016).
- Both methods ultimately produce `TemplateSection` objects that define the report structure.

## Frequently Asked Questions

### What file format should custom report templates use?

ReportEngine expects templates to be valid Markdown (`.md`) files. The engine parses headings (e.g., `### 1.0 报告摘要`) to identify sections, so using a clear numeric hierarchy helps the `parse_template_sections` function correctly slice the template into chapters.

### Can I use custom templates without modifying the repository files?

Yes. You can pass a raw Markdown string via the `custom_template` parameter when using the CLI ([`report_engine_only.py`](https://github.com/666ghj/bettafish/blob/main/report_engine_only.py)), the Flask API ([`ReportEngine/flask_interface.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/flask_interface.py)), or the Python API (`ReportAgent.generate_report`). This bypasses the file-based discovery mechanism entirely.

### How does the engine decide between auto-selected and custom templates?

The decision happens in `ReportAgent._select_template` at lines 914-918 of [`agent.py`](https://github.com/666ghj/bettafish/blob/main/agent.py). If the `custom_template` argument is non-empty, the method immediately returns a dictionary with `template_name="custom"` and the provided content, skipping the LLM-driven selection logic that would otherwise query available templates from `ReportEngine/report_template`.

### Where should I place template files for automatic discovery?

Place Markdown files in the `ReportEngine/report_template` directory. The default path is defined by `Settings.TEMPLATE_DIR` in [`ReportEngine/utils/config.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/utils/config.py) (line 59). The `TemplateSelectionNode._get_available_templates` method scans this directory, extracts descriptions from filenames, and presents the list to the LLM for selection.