配置资产与平台底座
这一篇只看平台底座,不展开推理细节。
这层真正要回答的问题是:为什么当前这套 Agent 开发平台里的模型、工具、知识库、工作流,不是散在服务里的参数拼接,而是能被稳定编辑、校验、发布、再装配成运行时对象的正式资产。
适合谁读:已经接受“这是个平台,不只是一个聊天应用”,接下来想看平台底层资产是怎么立住的人。
读前建议:先读 平台定义与总览,再回来读这一篇会更顺。
先建立阅读坐标
- 这篇在主线里的位置:第二篇,用来回答“平台为什么能稳定编辑、发布、复用”,而不是只在 service 里拼参数。
- 带着这三个问题读:
- 为什么
App要作为配置资产的总锚点。 - 为什么草稿态和发布态必须分开。
- 为什么
AppConfigService会成为真正的运行时翻译器。
- 为什么
- 先记住的对象:
App、AppConfigVersion、AppConfig、Workflow.draft_graph / graph。 - 如果时间有限,优先看:资产闭环、第 1 节、第 3 节、第 7 节和第 8 节。
先看资产闭环
这一层至少有三类资产:
- 文件型注册资产
- 模型 provider 和 model YAML
- 内置工具 YAML
- 内置应用模板 YAML
- 内置工作流模板 YAML
- 可编辑草稿资产
AppConfigVersion(config_type=draft)Workflow.draft_graph
- 发布态运行资产
AppConfigWorkflow.graphAppDatasetJoinMCPTool这类 discovery 后的目录缓存
平台底座的工作,不是把这些资产堆在一起,而是把它们接成一条稳定的“编辑 -> 校验 -> 发布 -> 装配”链。
1. App 是配置资产的总锚点
这个项目里,真正挂起平台 大部分能力的产品对象不是 Conversation,也不是 Workflow,而是 App。
App 本身只保存产品级基础信息和几个关键指针:
app_config_iddraft_app_config_iddebug_conversation_idtokenstatus
对应代码边界很清楚:
class App(db.Model):
account_id = Column(UUID, nullable=False)
app_config_id = Column(UUID, nullable=True)
draft_app_config_id = Column(UUID, nullable=True)
debug_conversation_id = Column(UUID, nullable=True)
token = Column(String(255), nullable=True)
status = Column(String(255), default="", nullable=False)
它自己不是运行配置,而是“配置资产入口 + 运行面入口”的聚合壳。
1.1 App 挂着三种派生资产
App 上 有三个很关键的只读属性:
draft_app_config- 当前可编辑草稿
app_config- 当前正式运行配置
debug_conversation- 当前调试会话
这三个对象都不是构造函数里一次性塞好的,而是按需解析、按需懒创建。
最典型的是 draft_app_config:
@property
def draft_app_config(self) -> "AppConfigVersion":
app_config_version = (
db.session.query(AppConfigVersion)
.filter(
AppConfigVersion.app_id == self.id,
AppConfigVersion.config_type == AppConfigType.DRAFT,
)
.one_or_none()
)
if not app_config_version:
app_config_version = AppConfigVersion(
app_id=self.id,
version=0,
config_type=AppConfigType.DRAFT,
**DEFAULT_APP_CONFIG
)
db.session.add(app_config_version)
db.session.commit()
return app_config_version
这说明草稿配置不是“必须先显式初始化”的独立流程,而是 App 级懒初始化的一部分。
1.2 App 还挂着两个发布面状态机
除了配置本身,App 还挂着两个很容易被忽略的发布面:
token_with_default- 管 WebApp token
- 只有发布态才会生成 token
- 回退到草稿态时会清掉 token
wechat_config- 管微信发布配置
- 会根据应用发布状态和凭证完整性自动修正
configured / unconfigured
也就是说,这个项目不是把 WebApp、WeChat 当独立产品线做,而是当 App 的派生发布面做。
2. 默认配置不是“提示值”,而是第一版配置 schema
这套平台从一开始就没有允许“空应用”直接流进运行时。
大部分默认骨架定义在 DEFAULT_APP_CONFIG:
DEFAULT_APP_CONFIG = {
"model_config": {
"provider": "openai",
"model": "gpt-4o-mini",
"parameters": {
"temperature": 0.5,
"top_p": 0.85,
"frequency_penalty": 0.2,
"presence_penalty": 0.2,
"max_tokens": 8192,
},
},
"dialog_round": 3,
"preset_prompt": "",
"tools": [],
"workflows": [],
"datasets": [],
"retrieval_config": {
"retrieval_strategy": "semantic",
"k": 10,
"score": 0.5,
},
"long_term_memory": {"enable": False},
"opening_statement": "",
"opening_questions": [],
"speech_to_text": {"enable": False},
"text_to_speech": {"enable": False, "voice": "xiaoxiao", "auto_play": False},
"review_config": {
"enable": False,
"keywords": [],
"inputs_config": {"enable": False, "preset_response": ""},
"outputs_config": {"enable": False},
},
}
这份字 典定义的不是 UI 占位符,而是第一版应用配置的字段边界:
- 一定有模型配置
- 一定有会话轮数
- 一定有工具、工作流、知识库开关位
- 一定有检索、长期记忆、语音、审核这些策略位
2.1 默认值是分层的,不是单一来源
这一点要写清,不然会把实现讲假。
当前项目里的“默认配置”至少分三层:
DEFAULT_APP_CONFIG- 应用草稿骨架的主要来源
- 数据库列默认值
- 例如
AppConfig/AppConfigVersion里的suggested_after_answer
- 例如
- 模板资产覆盖
- 内置应用 YAML
- 内置工作流 YAML
例如:
DEFAULT_APP_CONFIG里没有显式写suggested_after_answer- 但
AppConfig和AppConfigVersion的列默认值会补上{"enable": true} - 内置应用 YAML 里又可以显式覆盖它
所以这里不是“一个 dict 统治全部默认值”,而是“骨架默认值 + 存储默认值 + 模板覆盖”三层叠加。
2.2 这些补充配置项也要单独讲清楚
如果只把注意力放在模型、工具、知识库、工作流这几块,很容易把另外一组配置当成“页面修饰项”。
实际不是。opening_statement、opening_questions、speech_to_text、text_to_speech、suggested_after_answer、review_config 都已经是正式配置协议的一部分,而且每一项都有明确的校验和消费位置。
| 字段 | 结构和默认来源 | 写入校验 | 运行时或发布面落点 |
|---|---|---|---|
opening_statement | str,默认空串,来自 DEFAULT_APP_CONFIG | 必须是字符串,长度 0-2000 | 由 AppConfigService 返回,并在 WebAppService.get_web_app_info() 暴露给发布面;不进入 Agent prompt 主链 |
opening_questions | list[str],默认空列表,来自 DEFAULT_APP_CONFIG | 最多 3 个元素,且每个元素都必须是字符串 | 由 AppConfigService 返回,并在 WebAppService.get_web_app_info() 暴露给发布面,作为开场建议问题 |
speech_to_text | {"enable": bool},来自 DEFAULT_APP_CONFIG | 只能有 enable 这一个键,且值必须是布尔值 | 作为发布面的语音输入能力开关返回,不参与模型推理参数拼装 |
text_to_speech | {"enable": bool, "voice": str, "auto_play": bool},来自 DEFAULT_APP_CONFIG | 字段集合必须完整,voice 必须在 ALLOWED_AUDIO_VOICES 中,其他值必须是布尔值 | 作为发布面的 播报配置返回,不进入 Agent 推理主链 |
suggested_after_answer | {"enable": bool},当前主要靠 AppConfig / AppConfigVersion 的列默认值补齐 | 只能有 enable 这一个键,且值必须是布尔值 | 由 AppConfigService 和 WebAppService.get_web_app_info() 返回,控制回答后建议问题开关 |
review_config | {"enable", "keywords", "inputs_config", "outputs_config"},来自 DEFAULT_APP_CONFIG | 结构严格;开启审核时 keywords 不能为空且不能超过 100 个;输入审核和输出审核至少要开一项;开启输入审核时 preset_response 不能为空 | 直接插进 Agent 主链:输入命中关键词时在进模型前短路返回 preset_response,输出命中关键词时在流式片段里脱敏 |
这一组字段里,真正会改变 Agent 执行路径的是 review_config。
FunctionCallAgent._preset_operation_node()会先跑输入审核。- 命中关键词时不会继续走 LLM,而是直接发布
AGENT_MESSAGE和AGENT_END。 FunctionCallAgent._llm_node()和ReACTAgent._llm_node()在输出流式片段时又会按关键词做脱敏替换。
所以它不是“前端是否展示某个开关”的问题,而是已经进入运行链的治理配置。