590 字
3 分钟
系统架构与回调流程

系统架构与回调流程#

本篇承接 01_项目概览与技术栈,重点看这个项目的运行骨架:模块如何分层、Gradio 为什么采用两步回调,以及 history 数据结构为什么会成为调试关键点。


1 模块分层#

项目的分层可以概括为:

  1. 输入层:收集文字、图片、语音
  2. 预处理层:把三种输入转换成统一可组装格式
  3. LangChain 层:组织 Prompt、注入历史、调用模型
  4. 存储层:把多轮对话历史持久化

2 两步回调机制#

Gradio 的事件链设计是本项目的一个关键点。它不是“一次提交做完所有事”,而是分成两步:

  1. add_message():立即更新 UI,处理用户输入
  2. submit_messages():真正调用模型,等待 AI 回复
为什么不一步做完?

因为模型调用和外部 API 都是耗时操作。先让 add_message() 把界面更新出来,用户会明显感觉“消息已经发出”,而不是一直卡在输入阶段。


3 Gradio history 数据结构#

理解这个格式是调试的关键:

# history 是一个列表,每条消息是一个字典
history = [
# 文字消息(旧格式 - 纯字符串)
{"role": "user", "content": "你好"},
# 文字消息(新格式 - 列表)
{"role": "user", "content": [{"type": "text", "text": "你好"}]},
# 图片消息(dict 格式,type='messages' 模式下必须这样)
{"role": "user", "content": {"path": "C:/Temp/gradio/xxx.png"}},
# AI 回复
{"role": "assistant", "content": "你好!有什么可以帮你的?"},
]
常见坑:tuple 格式已废弃

旧版 Gradio 用 (file_path,) 元组存图片,新版 type='messages' 模式下会报 ValueError,必须改用 {'path': file_path} 字典格式。


4 常量设计#

源代码里把 URL 和轮询参数抽成常量,不只是代码风格问题,而是为了让“接口地址”和“重试策略”不散落在业务逻辑里。

# 为什么把 URL 提取为常量?
# 1. 阿里云接口地址偶尔会调整(如国际版 dashscope-intl.aliyuncs.com)
# 2. 集中管理,改一处全局生效
# 3. 代码可读性更高
DASHSCOPE_MULTIMODAL_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
DASHSCOPE_ASR_SUBMIT_URL = "https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription"
DASHSCOPE_TASK_QUERY_URL = "https://dashscope.aliyuncs.com/api/v1/tasks/{task_id}"
POLL_MAX_RETRIES = 20 # 最多等 20 × 2 = 40 秒
POLL_INTERVAL_SECONDS = 2

相关笔记