COZE / LangGraph 通用工作流项目模
阅读说明
本文档中的规范内容分为三类:
【硬约束】
指当前项目代码、运行时适配层或平台调用方式已锁定的内容。
这类内容不建议擅自修改,否则可能导致运行、导入、参数导出或部署失败。
【默认规范】
指当前模板已采用、且推荐继续沿用的工程组织方式。
这类内容通常与项目实际一致,并且适合脚手架生成器直接采用。
【v1 新增】
指项目原模板未提供,但为提升生产可维护性、测试性、开箱即用能力而补充的规范。
这类内容已按"强制 / 推荐"区分,避免生成无效或空洞目录。
1. 文档定位
本文档用于定义一套面向 COZE 运行时 、基于 LangGraph 的通用工作流项目模板规范。
V1.4.2 的目标只有一个:生成一套可运行、可复制、可验收、可直接作为新项目起点的工程模板。
这份规范面向:
- 团队内部统一开发
- 新项目脚手架生成
- 外包 / 新人接入
- LLM / 代码生成模型约束输入
- 代码评审与项目对照检查
2. V1.4.2 的核心原则
-
以可行为第一原则
不引入明知可能导致失败、歧义或高概率踩坑的默认实现。
-
以开箱即用为第一目标
生成出来的项目应至少能:
- 导入成功
- 启动成功
- 跑通一条 happy path
- 通过最小测试
- 导出 schema
-
以平台绑定点最小变更为前提
入口、主图、导出变量、节点签名、schema 机制等不做发散设计。
-
以新人和生成器都能稳定理解为标准
不依赖隐式约定,不把关键机制藏在实现细节里。
3. 适用项目范围
本规范覆盖以下项目类型:
- 纯 workflow 项目
- agent 项目
- workflow + agent 共存项目
- 带文件处理的项目
- 带数据库 / checkpoint 的项目
4. 约束分层
4.1 【硬约束】
以下内容视为平台适配层和当前模板实现的锁定行为:
- 入口文件固定为
src/main.py - 主图模块固定为
src/graphs/graph.py - 主图导出变量固定为
main_graph - 工作流输入输出通过
GraphInput/GraphOutput声明 - 状态模型使用
Pydantic BaseModel - 节点函数签名采用统一形式:
state、config、runtime - 节点函数中的
config和runtime参数必须保留,不得删除 .coze中必须声明project.entrypointscripts/目录路径不应随意改动- 运行时对
graphs.graph与agents.agent的识别和装载机制由main.py与graph_helper锁定 - 修改节点、子图、工具、目录结构、配置、状态模型或工作流行为后,必须同步更新对应
AGENTS.md - 反射导入相关目录必须保留
__init__.py
4.2 【默认规范】
以下为推荐直接用于脚手架生成的默认值:
graph.py只编排,不写业务逻辑- 节点默认返回自身完整
OutputModel - 图的最终对外输出由
output_schema=GraphOutput从全局状态提取 - 节点异常直接抛出,由入口层统一分类处理
- 文件类入参默认使用统一
File模型 - 测试默认按
unit / integration / smoke三层组织 - checkpoint 默认可配置启用,失败时降级到内存
- shell 脚本默认避免
eval + 字符串拼接 JSON main.py顶层初始化保持轻量,不在导入阶段执行高风险外部连接或重计算逻辑
4.3 【v1 新增 - 强制】
脚手架生成器或新建项目必须产出:
tests/unit、tests/integration、tests/smoke.env.example- pytest 配置样例
- 最小验收标准
- 最小
AGENTS.md - 最小可运行代码骨架
4.4 【v1 新增 - 推荐】
以下为推荐扩展,不作为最小脚手架强制项:
docs/治理文档目录observability/目录routing/conditions.pysubgraphs/
5. 推荐目录结构
project_root/
├── .coze
├── pyproject.toml
├── requirements.txt
├── README.md
├── AGENTS.md
├── .env.example
├── scripts/
│ ├── setup.sh
│ ├── pack.sh
│ ├── http_run.sh
│ ├── local_run.sh
│ ├── load_env.sh
│ └── load_env.py
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── graphs/
│ │ ├── __init__.py
│ │ ├── graph.py
│ │ ├── state.py
│ │ ├── routing/
│ │ │ ├── __init__.py
│ │ │ └── conditions.py
│ │ ├── subgraphs/
│ │ │ └── __init__.py
│ │ └── nodes/
│ │ ├── __init__.py
│ │ ├── input_node.py
│ │ ├── process_node.py
│ │ └── output_node.py
│ ├── agents/
│ │ ├── __init__.py
│ │ └── agent.py
│ ├── tools/
│ │ ├── __init__.py
│ │ └── example_tool.py
│ ├── storage/
│ │ ├── __init__.py
│ │ ├── memory/
│ │ │ ├── __init__.py
│ │ │ └── memory_saver.py
│ │ ├── database/
│ │ │ ├── __init__.py
│ │ │ └── db.py
│ │ └── s3/
│ │ ├── __init__.py
│ │ └── s3_storage.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── errors.py
│ │ └── file/
│ │ ├── __init__.py
│ │ └── file.py
│ └── observability/
│ ├── __init__.py
│ └── tracing.py
├── tests/
│ ├── conftest.py
│ ├── unit/
│ │ └── test_nodes.py
│ ├── integration/
│ │ └── test_graph.py
│ └── smoke/
│ └── test_service.py
└── docs/
├── architecture.md
├── node_conventions.md
├── state_schema.md
└── release_checklist.md
6. 目录职责说明
6.1 src/main.py 【硬约束】
作为平台统一入口与运行时适配层,负责:
- HTTP 服务暴露
- graph / agent 模式加载
- 请求上下文创建
- 同步 / 流式执行
- schema 导出
- 错误统一处理
6.2 src/graphs/ 【默认规范】
用于工作流图实现:
graph.py:主图编排state.py:状态模型nodes/:节点实现routing/:条件路由subgraphs/:子图
6.3 src/agents/ 【硬约束 + 默认规范】
用于 agent 模式实现。
可与 graphs/ 共存,但运行时只会选择一种模式执行。
6.4 src/tools/ 【默认规范】
工具目录不是自动发现目录。
必须显式导入、显式注册、显式调用。
6.5 src/storage/s3/ 命名兼容说明
src/storage/s3/ 下的实现文件推荐语义明确命名。
以下命名均可视为等价有效实现:
s3_storage.pyclient.py
若项目已有 s3_storage.py,生成器不得重复创建同职责的 client.py。
6.6 src/observability/ 【v1 新增 - 推荐】
不是最小模板强制项。
若项目无 tracing / metrics / logging 扩展需求,可不启用。
6.7 docs/ 【v1 新增 - 推荐】
用于治理文档。
不是最小可运行模板的必需项,但建议保留目录入口。
7. .coze 规范
7.1 .coze 的最小推荐配置
[project]
entrypoint = "src/main.py"
requires = ["python-3.12"]
[dev]
build = ["bash", "scripts/setup.sh"]
run = ["bash", "scripts/http_run.sh", "-p", "5000"]
pack = ["bash", "scripts/pack.sh"]
deps = ["git"]
[deploy]
build = ["bash", "scripts/setup.sh"]
run = ["bash", "scripts/http_run.sh", "-p", "5000"]
deps = ["git"]
7.2 路径原则
模板规范中,脚本路径统一推荐使用相对路径 。
若现有项目中存在绝对路径写法,应视为历史实现,后续应逐步收敛。
7.3 必填项
project.entrypoint
8. 依赖与打包规范
8.1 依赖管理方式
推荐优先使用 uv,同时保留 pip 回退能力。
8.2 文件职责
pyproject.toml:主依赖声明requirements.txt:兼容性依赖清单pack.sh:导出requirements.txt
8.3 双模式原则
模板应支持:
uv为首选安装方式pip为兜底回退方式
8.4 测试依赖要求 【v1 新增 - 强制】
若测试使用异步接口(如 ainvoke() 或 @pytest.mark.asyncio),测试环境必须安装:
pytestpytest-asynciohttpx
推荐在 pyproject.toml 中声明:
[project.optional-dependencies]
test = [
"pytest>=8,<9",
"pytest-asyncio>=0.23,<1",
"httpx>=0.27,<1",
]
9. 状态建模规范
9.1 状态分层
推荐采用以下状态层级:
| 状态类型 | 命名约定 | 作用 |
|---|---|---|
| 全局状态 | GlobalState |
图运行时全量业务状态 |
| 图输入 | GraphInput |
工作流对外输入 Schema |
| 图输出 | GraphOutput |
工作流对外输出 Schema |
| 节点输入 | {NodeName}Input |
节点消费的数据契约 |
| 节点输出 | {NodeName}Output |
节点产出的数据契约 |
9.2 基本要求 【硬约束】
- 所有状态模型使用
Pydantic BaseModel GraphInput/GraphOutput必须支持model_json_schema()- 图的输出通过
output_schema=GraphOutput对外暴露
9.3 输出映射约定 【默认规范】
- 节点优先返回自身的
OutputModel - 节点输出会写入运行时状态
- 图的最终对外输出由
output_schema=GraphOutput从全局状态中提取
9.4 GraphOutput 返回模式说明
当前模板默认模式是:
- 最后一个业务节点返回自身的
OutputModel - LangGraph 通过
output_schema=GraphOutput从全局状态中自动提取最终输出字段
在简单场景下,也可以让最后一个节点直接返回 GraphOutput,但这不是默认推荐示例。
9.5 节点间状态合并机制说明 【默认规范】
LangGraph 的执行机制如下:
- 节点返回的
OutputModel会被转换为字典 - 返回字段会合并进当前运行时状态
- 下一个节点看到的是已合并后的状态视图
因此:
- 节点 A 返回的字段,节点 B 之所以能读取,是因为这些字段已被合并进状态
- 节点的
InputModel所需字段,应存在于GlobalState中,或具备默认值 - 若节点声明了必填字段,但前序节点未将其写入状态,则运行时会因字段缺失而报错
9.6 状态合并风险警示 【默认规范】
若节点输出的字段未在 GlobalState 中声明,且状态模型未显式允许额外字段,则在状态合并时可能触发校验失败。
因此:
- 节点输出字段应与
GlobalState保持一致 - 尤其是
raw_data、debug_info、intermediate_result等辅助字段,不能随意返回而不建模
示例:
- 若
read_excel_node返回raw_data - 则
GlobalState中应显式声明raw_data - 否则后续状态更新可能失败
9.7 辅助字段聚合兜底方案 【v1 新增 - 推荐】
为减少 GlobalState 因辅助字段频繁扩容,推荐增加一个通用聚合字段:
metadata: dict = Field(
default_factory=dict,
description="节点间透传的辅助数据、调试信息、中间结果等"
)
使用原则:
- 非核心辅助字段优先写入
metadata - 核心业务字段仍应显式声明在
GlobalState顶层 metadata是兜底容器,不替代显式建模
例如:
raw_datadebug_infointermediate_result
这类字段若不适合作为顶层字段,可统一写入 metadata
9.8 metadata 与最终输出的关系
仅在 GlobalState 中声明 metadata,不会让它自动出现在最终输出中。
若希望 metadata 进入最终返回结果,则必须同时在 GraphOutput 中声明该字段。
9.9 文件类型规范 【默认规范】
当节点需要处理 Excel、PDF、图片等文件时,应优先使用 utils.file.file.File 作为字段类型,而不是原始字符串路径。
File 模型统一表达:
- 本地路径
- 远程 URL
- 文件类型推断
- 下载与本地化处理能力
9.10 最小状态示例
from typing import Optional, Literal
from pydantic import BaseModel, Field
class GraphInput(BaseModel):
query: str = Field(..., description="用户输入")
debug: bool = Field(default=False, description="调试开关")
class GraphOutput(BaseModel):
answer: str = Field(..., description="输出结果")
status: Literal["success", "failed"] = Field(..., description="执行状态")
metadata: Optional[dict] = None
class GlobalState(BaseModel):
query: str
debug: bool = False
normalized_query: Optional[str] = None
answer: Optional[str] = None
status: Optional[Literal["success", "failed"]] = None
metadata: dict = Field(default_factory=dict)
error_code: Optional[str] = None
error_message: Optional[str] = None
class InputNodeInput(BaseModel):
query: str
class InputNodeOutput(BaseModel):
normalized_query: str
class ProcessNodeInput(BaseModel):
normalized_query: str
metadata: dict = Field(default_factory=dict)
class ProcessNodeOutput(BaseModel):
answer: str
status: Literal["success", "failed"]
metadata: dict = Field(default_factory=dict)
10. 图编排规范
10.1 graph.py 职责 【默认规范】
src/graphs/graph.py 只负责:
- 创建
StateGraph - 绑定输入输出 Schema
- 注册节点
- 设置入口点
- 添加顺序边
- 按需添加条件边 / 子图 / 循环
- 编译并导出
main_graph
不应在 graph.py 中编写具体业务处理逻辑。
10.2 模块约束 【硬约束】
- 文件名固定为
graph.py - 模块路径固定为
graphs.graph - 必须导出
main_graph
10.3 主图最小示例
from langgraph.graph import StateGraph, END
from graphs.state import GlobalState, GraphInput, GraphOutput
from graphs.nodes.input_node import input_node
from graphs.nodes.process_node import process_node
from graphs.nodes.output_node import output_node
builder = StateGraph(
GlobalState,
input_schema=GraphInput,
output_schema=GraphOutput,
)
builder.add_node("input_node", input_node)
builder.add_node("process_node", process_node)
builder.add_node("output_node", output_node)
builder.set_entry_point("input_node")
builder.add_edge("input_node", "process_node")
builder.add_edge("process_node", "output_node")
builder.add_edge("output_node", END)
main_graph = builder.compile()
10.4 条件边规范 【v1 新增 - 推荐】
条件边不是基础模板强制项,但底层支持。
推荐将条件函数放在 src/graphs/routing/conditions.py。
最小示例:
from typing import Literal
from graphs.state import GlobalState
def decide_next(state: GlobalState) -> Literal["process_node", "output_node"]:
if state.query.strip():
return "process_node"
return "output_node"
10.5 子图与并行说明
- 子图支持,但不作为最小模板默认形态
- 并行支持,但不建议作为最小脚手架默认实现
- 若启用子图或并行,需额外补充状态合并策略
11. 节点开发规范
11.1 节点职责
节点是最小执行单元,应满足:
- 单一职责
- 输入输出明确
- 尽量纯函数化
- 便于单元测试
11.2 统一函数签名 【硬约束】
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context
def node_id_node(
state: NodeInput,
config: RunnableConfig,
runtime: Runtime[Context],
) -> NodeOutput:
...
11.3 节点参数要求
【硬约束】
config必须保留runtime必须保留
【默认规范】
- 支持
def与async def - 默认返回完整
OutputModel - 不在模块导入阶段执行副作用代码
11.4 命名规范
推荐命名为:
- 文件名:
{node_id}_node.py - 函数名:
{node_id}_node
11.5 DocString 元数据
推荐在节点函数中维护结构化 DocString:
def process_node(...):
"""
title: 数据处理节点
desc: 对输入数据进行标准化和计算
integrations: pandas, openpyxl
"""
11.6 异常处理规范
节点默认直接抛出异常,不建议吞错。
推荐做法:
- 参数问题抛
ValueError - 资源不存在抛
FileNotFoundError - 外部依赖失败抛语义明确的异常
- 统一在
main.py中做错误分类与返回格式化
11.7 最小节点示例
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context
from graphs.state import InputNodeInput, InputNodeOutput
def input_node(
state: InputNodeInput,
config: RunnableConfig,
runtime: Runtime[Context],
) -> InputNodeOutput:
"""
title: 输入节点
desc: 对输入进行清洗和标准化
integrations:
"""
query = state.query.strip()
if not query:
raise ValueError("query 不能为空")
return InputNodeOutput(normalized_query=query)
12. 服务入口与运行时规范
12.1 main.py 职责 【硬约束】
src/main.py 负责:
- 创建 HTTP 服务
- 装载 graph 或 agent
- 创建请求上下文
- 执行同步与流式运行
- 暴露 schema 查询
- 统一错误处理
12.2 main.py 修改原则
main.py 作为平台适配层,开发者通常不应修改。
仅在以下场景下,才可谨慎变更:
- 新增全局 HTTP 端点
- 调整统一错误处理策略
- 修改全局超时、执行包装或服务初始化逻辑
12.3 main.py 延迟初始化建议 【默认规范】
推荐 main.py 在模块顶层仅执行轻量初始化,例如:
app = FastAPI()- 轻量对象装配
- 常量级配置读取
将可能触发以下行为的逻辑尽量延迟到首次请求或首次调用时执行:
- 外部数据库连接
- 环境变量重度校验
- 模型加载
- 大计算量初始化
- 远程依赖探测
该建议用于降低:
from main import app的导入失败概率- 烟雾测试失败概率
- 本地开发首次接入门槛
12.4 模式识别与装载机制 【硬约束】
项目中 graphs/ 与 agents/ 可共存,但运行时只会选择一种模式执行。
运行时通过 graph_helper.is_agent_proj() 自动判断项目类型:
- 若识别为 Agent 项目,则加载
agents.agent - 否则加载
graphs.graph
12.5 推荐端点
推荐暴露:
POST /runPOST /stream_runPOST /cancel/{run_id}POST /node_run/{node_id}GET /graph_parameterGET /health
12.6 上下文规范
【硬约束】
当前已验证的核心上下文字段为:
run_id
【默认规范】
推荐要求:
- 从 Header
x-run-id读取 - 若不存在,由服务端生成
- 在整个请求链路中保持透传一致
12.7 流式输出规范 【硬约束】
推荐采用 SSE:
id: {run_id}
event: message
data: {json}
并通过 Header 区分:
- 常规流式
- debug 流式(
x-workflow-stream-mode: debug)
12.8 本地运行模式
建议保留以下启动模式:
python src/main.py -m http
python src/main.py -m flow
python src/main.py -m node -n process_node
python src/main.py -m agent
13. 持久化与存储规范
13.1 Checkpointer 策略
checkpoint 不是平台自动注入能力,应由模板代码显式管理。
推荐实现:
- 优先使用 Postgres checkpointer
- 失败时自动降级到
MemorySaver - 使用单例管理生命周期
13.2 原则
- checkpoint 为推荐能力,不是所有项目的强制要求
- 生产项目建议启用
- 本地开发可仅用内存模式
13.3 存储层职责
storage/memory/
- checkpointer 初始化
- 降级逻辑
- 生命周期管理
storage/database/
- 连接管理
- 凭据与连接字符串装配
- 重试逻辑
storage/s3/
- 文件上传下载
- 对象存储抽象
14. 工具层规范
14.1 基本原则
src/tools/ 不是自动发现目录。
模板要求:显式导入、显式注册、显式调用。
14.2 工具适用场景
适合封装:
- 外部 API 调用
- 文件读取
- 数据库查询
- 通用文本处理
- 多节点复用逻辑
14.3 最小示例
from pydantic import BaseModel
class ExampleToolInput(BaseModel):
text: str
class ExampleToolOutput(BaseModel):
result: str
def example_tool(payload: ExampleToolInput) -> ExampleToolOutput:
return ExampleToolOutput(result=payload.text.strip().lower())
15. 错误处理规范
15.1 分层原则 【v1 新增】
错误建议分为:
- 用户输入错误
- 节点执行错误
- 外部依赖错误
- 系统配置错误
- 未知错误
15.2 推荐做法
- 节点层抛出语义清晰的异常
main.py统一捕获与分类- debug 模式可附加诊断信息
- 生产模式避免泄露内部堆栈
15.3 最小错误模型 【v1 新增】
from typing import Optional
from pydantic import BaseModel
class ErrorResponse(BaseModel):
code: str
message: str
detail: Optional[str] = None
run_id: Optional[str] = None
16. 测试规范
16.1 测试分层 【v1 新增 - 强制】
单元测试
针对单个节点:
- 构造 Input Model
- mock
config - mock
runtime - 断言 Output Model 或异常
集成测试
针对 main_graph:
- 导入
main_graph - 调用
invoke()或ainvoke() - 断言主要状态流转与输出
烟雾测试
针对 HTTP 服务:
/health/graph_parameter
16.2 最小测试文件清单 【v1 新增 - 强制】
脚手架生成器必须产出以下最小测试文件:
tests/
├── conftest.py
├── unit/
│ └── test_nodes.py
├── integration/
│ └── test_graph.py
└── smoke/
└── test_service.py
每个文件都必须至少包含一个可运行测试函数,不得只生成空目录。
16.3 文件职责说明
| 文件 | 强制要求 | 覆盖目标 |
|---|---|---|
tests/conftest.py |
✅ | 提供 mock runtime / mock config 等公共 fixture |
tests/unit/test_nodes.py |
✅ | 至少覆盖 1 个核心节点的 success / error 分支 |
tests/integration/test_graph.py |
✅ | 至少覆盖 main_graph 的 1 条 happy path |
tests/smoke/test_service.py |
✅ | 覆盖 /health 与 /graph_parameter |
16.4 pytest 配置建议 【v1 新增 - 强制】
建议在 pyproject.toml 中补充:
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-ra -q"
16.5 最小测试示例
tests/conftest.py
import pytest
from types import SimpleNamespace
@pytest.fixture
def mock_config():
return {}
@pytest.fixture
def mock_runtime():
return SimpleNamespace(context=SimpleNamespace(run_id="test-run"))
tests/unit/test_nodes.py
import pytest
from graphs.nodes.input_node import input_node
from graphs.state import InputNodeInput
def test_input_node_success(mock_config, mock_runtime):
result = input_node(
InputNodeInput(query=" hello "),
mock_config,
mock_runtime,
)
assert result.normalized_query == "hello"
def test_input_node_empty_raises(mock_config, mock_runtime):
with pytest.raises(ValueError, match="query 不能为空"):
input_node(
InputNodeInput(query=" "),
mock_config,
mock_runtime,
)
tests/integration/test_graph.py
import pytest
from graphs.graph import main_graph
from graphs.state import GraphInput
@pytest.mark.asyncio
async def test_main_graph_happy_path():
result = await main_graph.ainvoke(
GraphInput(query="hello", debug=False)
)
assert result["status"] == "success"
assert "answer" in result
tests/smoke/test_service.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_health():
resp = client.get("/health")
assert resp.status_code == 200
def test_graph_parameter():
resp = client.get("/graph_parameter")
assert resp.status_code == 200
16.6 烟雾测试导入风险提醒
若 main.py 在模块顶层执行环境变量读取、数据库连接或 graph 初始化,from main import app 可能在导入阶段失败。
因此建议:
- 避免在
main.py顶层执行不可控外部资源初始化 - 必要时在测试中使用 monkeypatch 或测试专用环境变量
- 保证
test_service.py在无生产依赖的本地环境中也可导入并运行
17. 最小验收标准 【v1 新增 - 强制】
每个新建项目至少满足以下条件:
scripts/setup.sh可执行python src/main.py -m flow可跑通一条 happy pathGET /health正常返回GET /graph_parameter可返回合法 schema- 至少一个节点单测通过
- 至少一个主图集成测试通过
- 若启用 checkpoint,需验证其可初始化
pack.sh可成功导出依赖
18. AGENTS.md 规范
18.1 作用说明
AGENTS.md 用于记录项目结构、节点职责、工具能力、依赖与约束信息。
该文件既服务团队成员,也服务 LLM / 代码生成模型。
18.2 推荐维护内容
建议在 AGENTS.md 中维护:
- 节点清单
- 节点职责
- 子图清单
- 工具清单
- 输入输出摘要
- 外部依赖
- 风险点与注意事项
18.3 同步维护要求 【硬约束】
当以下内容发生变化时,必须同步更新对应 AGENTS.md:
- 节点新增、删除或重命名
- 子图新增、删除或重构
- 工具新增、删除或接口变更
- 目录结构调整
- 配置与工作流行为变化
- 节点职责、输入输出、依赖关系变化
- 节点的
InputModel或OutputModel发生字段新增、删除、重命名或类型变更
18.4 格式兼容说明
AGENTS.md 的格式不限于列表或表格,以清晰表达以下信息为准:
- 节点清单
- 职责边界
- 输入输出摘要
- 约束规则
建议:
- 节点较少的项目可使用列表
- 节点较多的项目可使用表格
生成器不应因列表 / 表格格式差异而重复改写内容。
18.5 最小模板
# AGENTS.md
## Project Overview
- 项目类型:workflow / agent / hybrid
- 主入口:src/main.py
- 主图模块:src/graphs/graph.py
- 主图导出:main_graph
## Nodes
- input_node: 输入清洗与标准化
- process_node: 核心业务处理
- output_node: 输出整理
## State Models
- GraphInput: 外部输入
- GraphOutput: 外部输出
- GlobalState: 运行时全局状态
## Tools
- example_tool: 示例工具
## Constraints
- 节点签名必须保留 state/config/runtime
- 修改节点、工具、目录结构、状态模型后必须同步更新本文件
- graph.py 只负责编排,不写业务逻辑
19. 环境变量规范
19.1 .env.example 【v1 新增 - 强制】
建议提供最小示例:
# 数据库连接(用于 checkpoint 持久化)
PGDATABASE_URL=postgresql://user:password@host:port/dbname
# 可选:开发环境标识
COZE_PROJECT_ENV=DEV
19.2 使用原则
.env.example只放占位示例,不放真实凭证- 生产敏感信息应通过安全配置系统注入
- README 或 AGENTS.md 中应说明哪些变量是必需的,哪些是可选的
20. 治理文档建议 【v1 新增 - 推荐】
建议保留最小治理文档入口:
architecture.mdnode_conventions.mdstate_schema.mdrelease_checklist.md
不是最小可运行模板的强制项。
21. 脚本实现规范
21.1 基本原则
脚本必须以稳定传参为优先,不得默认采用易出错的 eval + 拼接 JSON 方案。
21.2 local_run.sh 安全要求 【默认规范】
禁止默认采用如下高风险模式:
cmd="$cmd -i '$input'"
eval "$cmd"
原因:
- JSON 中若包含单引号、换行或特殊字符,极易失败
- 导致本地调试行为与真实输入不一致
21.3 推荐做法
应优先选择以下方案之一:
- 使用
"$@"原样透传参数 - 将 JSON 写入临时文件再读取
- 采用 base64 编码传输再在程序内解码
模板默认建议使用"临时文件"或"参数原样透传"方式,不使用 eval。
21.4 安全版 local_run.sh 参考实现 【v1 新增 - 强制示例】
#!/bin/bash
set -e
mode=""
node=""
input=""
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${COZE_WORKSPACE_PATH:-$(dirname "$SCRIPT_DIR")}"
usage() {
echo "用法: $0 -m <模式> [-n <节点ID>] [-i <输入JSON>]"
}
while getopts "m:n:i:h" opt; do
case "$opt" in
m) mode="$OPTARG" ;;
n) node="$OPTARG" ;;
i) input="$OPTARG" ;;
h) usage; exit 0 ;;
\?) usage; exit 1 ;;
esac
done
[ -z "$mode" ] && { echo "错误: 必须指定 -m 参数"; usage; exit 1; }
args=("-m" "$mode")
[ -n "$node" ] && args+=("-n" "$node")
if [ -n "$input" ]; then
tmpfile=$(mktemp)
printf '%s' "$input" > "$tmpfile"
args+=("-i" "@$tmpfile")
trap 'rm -f "$tmpfile"' EXIT
fi
if [ -f "${SCRIPT_DIR}/load_env.sh" ]; then
source "${SCRIPT_DIR}/load_env.sh"
fi
python "${WORK_DIR}/src/main.py" "${args[@]}"
说明:
- 该方案避免
eval - 通过临时文件规避 JSON 引号逃逸问题
- 若采用
-i "@file"约定,则main.py的输入解析逻辑必须支持从文件读取内容
22. 最小可运行代码骨架
22.1 .coze
[project]
entrypoint = "src/main.py"
requires = ["python-3.12"]
[dev]
build = ["bash", "scripts/setup.sh"]
run = ["bash", "scripts/http_run.sh", "-p", "5000"]
pack = ["bash", "scripts/pack.sh"]
deps = ["git"]
[deploy]
build = ["bash", "scripts/setup.sh"]
run = ["bash", "scripts/http_run.sh", "-p", "5000"]
deps = ["git"]
22.2 src/graphs/state.py
from typing import Optional, Literal
from pydantic import BaseModel, Field
class GraphInput(BaseModel):
query: str = Field(..., description="用户输入")
debug: bool = Field(default=False, description="调试开关")
class GraphOutput(BaseModel):
answer: str = Field(..., description="输出结果")
status: Literal["success", "failed"] = Field(..., description="执行状态")
metadata: Optional[dict] = None
class GlobalState(BaseModel):
query: str
debug: bool = False
normalized_query: Optional[str] = None
answer: Optional[str] = None
status: Optional[Literal["success", "failed"]] = None
metadata: dict = Field(default_factory=dict)
error_code: Optional[str] = None
error_message: Optional[str] = None
class InputNodeInput(BaseModel):
query: str
class InputNodeOutput(BaseModel):
normalized_query: str
class ProcessNodeInput(BaseModel):
normalized_query: str
metadata: dict = Field(default_factory=dict)
class ProcessNodeOutput(BaseModel):
answer: str
status: Literal["success", "failed"]
metadata: dict = Field(default_factory=dict)
22.3 src/graphs/nodes/input_node.py
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context
from graphs.state import InputNodeInput, InputNodeOutput
def input_node(
state: InputNodeInput,
config: RunnableConfig,
runtime: Runtime[Context],
) -> InputNodeOutput:
"""
title: 输入节点
desc: 对输入进行清洗和标准化
integrations:
"""
query = state.query.strip()
if not query:
raise ValueError("query 不能为空")
return InputNodeOutput(normalized_query=query)
22.4 src/graphs/nodes/process_node.py
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context
from graphs.state import ProcessNodeInput, ProcessNodeOutput
def process_node(
state: ProcessNodeInput,
config: RunnableConfig,
runtime: Runtime[Context],
) -> ProcessNodeOutput:
"""
title: 处理节点
desc: 生成最终回答
integrations:
"""
return ProcessNodeOutput(
answer=f"processed: {state.normalized_query}",
status="success",
metadata={"source": "process_node"},
)
22.5 src/graphs/nodes/output_node.py
from langchain_core.runnables import RunnableConfig
from langgraph.runtime import Runtime
from coze_coding_utils.runtime_ctx.context import Context
from graphs.state import GlobalState, GraphOutput
def output_node(
state: GlobalState,
config: RunnableConfig,
runtime: Runtime[Context],
) -> GraphOutput:
"""
title: 输出整理节点
desc: 从全局状态整理最终输出
integrations:
"""
if not state.answer or not state.status:
raise ValueError("输出字段缺失")
return GraphOutput(
answer=state.answer,
status=state.status,
metadata=state.metadata or None,
)
22.6 src/graphs/graph.py
from langgraph.graph import StateGraph, END
from graphs.state import GlobalState, GraphInput, GraphOutput
from graphs.nodes.input_node import input_node
from graphs.nodes.process_node import process_node
from graphs.nodes.output_node import output_node
builder = StateGraph(
GlobalState,
input_schema=GraphInput,
output_schema=GraphOutput,
)
builder.add_node("input_node", input_node)
builder.add_node("process_node", process_node)
builder.add_node("output_node", output_node)
builder.set_entry_point("input_node")
builder.add_edge("input_node", "process_node")
builder.add_edge("process_node", "output_node")
builder.add_edge("output_node", END)
main_graph = builder.compile()
23. 适用于代码生成模型的规则
为保证模板生成稳定性,LLM 或代码生成器在基于本文档生成项目时,应严格遵守以下规则:
- 不修改
src/main.py、src/graphs/graph.py、main_graph、.coze的关键绑定关系 - 所有状态模型使用 Pydantic
- 节点签名必须包含
state/config/runtime - 节点默认返回
OutputModel - 文件入参优先使用统一
File模型 - 新增节点、工具、目录、状态模型后同步更新
AGENTS.md - 必须生成最小测试文件清单,而不是只生成空目录
- 必须生成
.env.example与 pytest 配置 graph.py只编排,不写业务实现- 不默认引入复杂子图、并行编排和不必要扩展目录
- 不生成使用
eval处理 JSON 入参的脚本 - 对辅助字段默认使用
metadata兜底,除非用户明确要求单独顶层建模 - 对异步测试默认补齐
pytest-asyncio依赖 - 不因
AGENTS.md使用列表或表格而进行无意义改写
24. V1.4.2 的最小落地标准
若一个项目声称"符合 V1.4.2 模板",则至少应满足:
- 可导入
graphs.graph - 可导入
main main_graph存在GraphInput / GraphOutput存在- 节点签名为
state/config/runtime - 本地可执行最小 happy path
/health和/graph_parameter可访问tests/unit、tests/integration、tests/smoke存在且含可运行测试.env.example存在AGENTS.md存在且与代码一致
25. 边界说明
V1.4.2 的重点不是"写得最全",而是"默认生成后尽量不出错"。
因此,以下内容被有意弱化或不作为默认项:
- 不强制引入复杂错误处理节点
- 不默认引入并行分支
- 不默认引入子图
- 不默认引入重型 observability 设计
- 不使用高风险 shell 拼接方式
- 不推荐返回未建模的辅助字段
26. 结论
本规范适合作为 正式脚手架生成标准、项目验收清单与代码评审基线。
其中:
- 平台入口、主图模块、主图导出、Pydantic 状态、节点签名、
config/runtime参数保留、脚本路径、agent/workflow 自动识别、AGENTS.md同步要求、__init__.py保留等属于【硬约束】 OutputModel返回、output_schema映射、File类型、状态合并机制、metadata兜底、工具显式注册、相对路径优先、测试三层结构、轻量顶层初始化等属于【默认规范】- 最小测试文件清单、
.env.example、pytest 配置、最小AGENTS.md、治理文档目录、路由目录、子图目录、安全版local_run.sh参考实现等属于【v1 新增】
V1.4.2 追求的不是理论完备,而是:
生成即可用,运行不踩坑,验收有标准,扩展有边界。
附录 经验补充
- 先跑通最小闭环:先让基础工作流能端到端运行,再逐步叠加分支和外部依赖,避免一开始就陷入复杂调优。
- 数据与代码分离:配置和常亮数据等结构化数据单独放置在 JSON 配置文件,更新时只改数据文件,不动业务逻辑代码。需要外部用户更改的参数配置,可以使用.toml文件,更易于人类阅读。
- 分支兜底防崩溃:用条件路由拦截非法或无关输入,闲聊走对话分支,查询走工作流,杜绝原始报错抛给用户。
- LLM 一箭双雕:让入口节点同时完成"回复用户"和"判断是否触发工作流",把两次模型调用压缩为一次。
- Prompt 边界约束:严格限制 LLM 只输出简短确认语,禁止其编造具体日期,所有事实数据交由后端结构化节点生成。
- 流式+耗时透视 :开启
--stream逐节点追踪,配合elapsed_ms统计,一眼看清耗时到底卡在哪个环节。 - 诊断脚本科学定位:写专门脚本拆分 TTFB、连接建立和内容生成时间,避免凭感觉猜,精准定位是网络慢还是模型慢。
- 换模型有时比改代码更有效:当代码逻辑已最优仍很慢时,果断换更快端点或更轻量模型,收益可能远超代码层面优化。
- 将工作流生成workflow.md:在根目录创建workflow.md,用严格mermaid规范格式来描述工作流包括:## flowchart LR、 ## 节点说明、## 输入输出、## 关键设计