Day 51:综合项目后端开发

今天的学习目标是基于昨天完成的PRD搭建后端服务。你要用FastAPI构建一个完整的后端系统,包括模型调用模块、Prompt管理模块、RAG服务模块、Agent工作流模块、评估模块和报告生成模块。这不是写几个demo接口,而是搭建一个有清晰分层架构、有错误处理机制、有日志追踪能力、可以真正支撑前端调用的生产级后端骨架。

后端开发是把PRD中的功能设计变成可执行的代码的过程。你需要做的不是追求最酷的技术栈,而是做出一个结构清晰、逻辑正确、方便调试和迭代的系统。很多初学者在后端开发中容易犯的错误是过度设计——还没写清楚业务逻辑就去搞微服务、搞消息队列、搞容器编排。我们的原则是用最简单可靠的架构把核心功能跑通,后续根据需要逐步演进。


一、FastAPI项目初始化与目录结构

FastAPI是目前Python生态中最适合构建AI应用后端的框架。它自带异步支持、自动生成API文档、类型校验和依赖注入,这些特性在AI应用中尤其有用——你的接口经常需要接收复杂的嵌套数据结构(比如一个包含行业、岗位、痛点描述的请求体),FastAPI的Pydantic模型可以自动校验这些数据。

项目目录结构的设计原则是”按功能分层而不是按技术分层”。一个常见的新手错误是按文件类型组织——所有模型放在models文件夹、所有路由放在routes文件夹、所有工具放在utils文件夹。这种方式在小项目中没问题,但项目一复杂就会导致改一个功能要跳四五个文件夹。

我们推荐的目录结构是按照业务模块组织。顶层目录包含主入口文件、配置文件和依赖管理文件。app目录下按模块划分——analyzer模块负责分析相关的路由和逻辑,agent模块负责Agent的实现和编排,rag模块负责知识库检索,report模块负责报告生成,eval模块负责评估。每个模块内部有自己的路由文件、服务文件和数据模型文件。

配置管理是项目初始化中容易被忽视的部分。你需要管理至少三类配置:环境配置(开发环境、测试环境、生产环境)、模型配置(不同Agent使用哪个模型、温度参数、最大token数)和业务配置(分析超时时间、并发限制、文件大小限制)。这些配置不应该硬编码在代码中,而应该通过环境变量和配置文件来管理。推荐使用Pydantic的BaseSettings来定义配置模型,这样配置项有类型校验和默认值,也方便在不同环境间切换。

日志配置也需要在初始化阶段就设置好。AI应用后端的日志和传统Web应用有所不同——除了记录请求响应,你还需要记录每次LLM调用的输入输出(用于调试Prompt)、每次Agent的执行耗时(用于性能优化)、每次分析的整体耗时和token消耗(用于成本控制)。日志格式建议使用JSON,方便后续用日志分析工具处理。


二、LLMClient:模型调用模块的设计

LLMClient是整个后端系统的基础组件,所有需要调用大模型的地方都通过它来统一管理。一个设计良好的LLMClient需要解决三个问题:统一的调用接口、灵活的模型切换和可靠的错误处理。

统一的调用接口意味着无论你底层用的是OpenAI、Anthropic还是其他模型提供商,上层业务代码的调用方式是一样的。你需要定义一个标准的调用接口,接收Prompt和参数,返回模型输出。不同模型提供商的差异在LLMClient内部处理,对外透明。

这个接口需要支持同步和异步两种调用方式。FastAPI是异步框架,大部分场景下你应该使用异步调用来避免阻塞。但有些场景(比如测试脚本、初始化脚本)用同步调用更方便,所以两种方式都要支持。

模型切换的灵活性在AI应用中尤其重要。不同Agent可能需要不同能力的模型——需求理解Agent用便宜的快速模型就够了,方案设计Agent可能需要更强的推理能力。LLMClient需要支持通过配置或参数来指定使用哪个模型,而不是把模型名称写死在业务代码里。

LLMClient还需要实现重试机制。模型API调用失败是常态——可能是网络超时、可能是速率限制、可能是服务端临时故障。你需要实现指数退避重试策略:第一次失败后等一秒重试,第二次等两秒,第三次等四秒,最多重试三次。对于速率限制错误(状态码429),重试的间隔应该更长,因为这种错误通常意味着你的调用频率超过了API的限制。

另一个重要的功能是调用统计。LLMClient应该记录每次调用的模型名称、输入token数、输出token数和耗时。这些统计数据对成本控制和性能优化至关重要。你可以在后台做一个简单的统计面板,展示每日的token消耗趋势和成本分布。

流式输出支持也是一个需要考虑的功能。当Agent生成的内容比较长时(比如报告生成Agent可能输出几千字),同步等待全部生成完毕再返回会导致用户等待时间过长。流式输出可以让前端逐步展示生成内容,大幅提升用户体验。FastAPI支持Server-Sent Events来实现流式输出,你可以在LLMClient中封装流式调用的逻辑。


三、PromptManager:Prompt的版本化管理

Prompt管理是AI应用后端中一个独特的挑战。传统软件的业务逻辑写在代码里,改代码需要重新部署。但AI应用的核心逻辑有一半在Prompt里——你的需求理解Agent为什么能准确解析用户输入,不是因为你写了多复杂的代码,而是因为你写了一个好的Prompt。Prompt的质量直接决定了系统的输出质量,所以Prompt需要被当作一等公民来管理。

PromptManager的核心功能是Prompt的版本化管理。每个Agent的Prompt都应该有一个版本号和变更记录。当你调整了需求理解Agent的Prompt使得解析准确率从百分之七十提升到百分之八十五时,你需要记录这次变更的内容和效果。如果某次变更导致了问题(比如新的Prompt在某些边缘场景下表现更差),你需要能快速回滚到上一个版本。

Prompt的存储方式有几种选择。最简单的是文件系统存储——每个Agent的Prompt存为一个独立的文本文件,文件名包含版本号。这种方式的好处是直观,可以用任何文本编辑器修改Prompt,也可以用Git来追踪变更历史。缺点是缺乏元数据管理(比如每个版本的创建时间、修改原因、效果评分)。

更完善的方式是用数据库存储Prompt版本。每个Prompt版本记录包含Prompt内容、版本号、创建时间、修改说明、效果评分和状态(草稿、测试中、生产环境)。这种方式支持A/B测试——你可以同时运行两个版本的Prompt,比较它们在相同输入下的输出质量,然后选择更好的版本上线。

PromptManager还需要支持Prompt模板化。很多Agent的Prompt包含可变部分和固定部分。比如行业研究Agent的Prompt模板是”你是一个行业研究专家。请对以下行业进行AI应用机会分析:{industry_name}。该行业的主要业务包括:{business_description}。请从以下维度进行分析…”。花括号中的变量在运行时由系统填充。PromptManager需要支持定义和管理这些模板,包括变量的类型校验和默认值设置。

Prompt测试功能也是PromptManager的重要组成部分。你需要一个测试界面,可以在上面选择一个Prompt模板、填入测试变量、执行Prompt并查看输出。这个功能在Prompt优化时极其有用——你可以快速迭代Prompt而不用跑完整个Agent流程。同时PromptManager应该维护一套测试用例集——包含典型输入和期望输出的对照表,每次修改Prompt后跑一遍测试用例确保没有退化。


四、RAGService:知识库检索服务

RAGService负责从行业知识库中检索与用户输入相关的信息,为Agent提供知识支撑。没有RAG的Agent只能依赖模型的训练数据来回答问题,这意味着它对行业细节的了解可能过时或不准确。有了RAG,Agent可以检索到最新的行业报告、案例研究和专业分析,输出质量会有质的提升。

RAGService的架构分为三个层次。最底层是向量存储层,负责存储和检索文档的向量表示。中间层是文档处理层,负责将原始文档(PDF、Word、网页)转化为可检索的向量。最上层是检索接口层,为Agent提供统一的查询接口。

向量存储的选择需要考虑数据量和查询性能。对于我们的系统来说,数据量不会太大(几十个行业的知识文档,总计几千篇),使用本地的向量数据库(如Chroma或FAISS)就足够了。如果未来需要支持更大规模的数据或分布式部署,可以迁移到专门的向量数据库服务。

文档处理层需要处理几个关键问题。第一个是文档分块策略——一篇行业报告可能有一万字,不能整篇作为一个检索单元(太长导致检索不精确),也不能按固定字数切分(可能把一个完整的段落切成两半)。推荐使用语义分块——按照文档的自然结构(标题、段落、列表项)来切分,每个块保持语义完整性。

第二个是向量化模型的选择。向量化模型的质量直接影响检索的准确性。你可以使用OpenAI的embedding API或者开源的中文embedding模型。选择时需要平衡效果和成本——如果你的知识库主要是中文行业内容,用专门针对中文优化的embedding模型效果会更好。

第三个是检索策略。最基础的检索是向量相似度搜索——用户的查询被向量化后与知识库中的所有文档块比较相似度,返回最相关的几个。但单纯依赖向量相似度可能不够——有些知识虽然语义相似但实际上回答的是不同的问题。混合检索策略结合了向量相似度和关键词匹配,可以提高检索的精确度。

检索结果的后处理也很重要。RAGService不应该把原始的检索结果直接扔给Agent,而应该做一层加工——去重(多个文档块可能包含重复信息)、排序(按相关性从高到低排列)、截断(只返回最相关的前N个结果,避免超出Agent的上下文窗口)和格式化(统一输出格式方便Agent使用)。


五、AgentService:多Agent编排服务

AgentService是整个后端的核心引擎,负责管理多个Agent的执行流程和协作逻辑。在Day 53我们会深入设计每个Agent的具体实现,今天的重点是搭建Agent的编排框架——一个通用的、可扩展的Agent执行管道。

Agent编排的核心挑战是流程控制和状态管理。流程控制回答的是”哪个Agent先执行、哪个后执行、什么时候需要暂停等用户确认”的问题。状态管理回答的是”每个Agent执行到哪里了、中间结果存在哪里、失败了怎么恢复”的问题。

我们采用基于状态机的编排模式。把整个分析流程定义为一系列状态,每个状态对应一个Agent或一个检查点。状态之间的转换是确定的——需求理解完成后自动进入行业研究,行业研究完成后自动进入岗位拆解,以此类推。在检查点状态(如AI机会评分完成后),系统暂停并等待用户确认后才继续执行。

每个Agent的执行遵循统一的生命周期。初始化阶段加载Prompt模板和配置参数,准备阶段校验输入数据的完整性,执行阶段调用LLM生成输出,验证阶段检查输出格式和内容是否符合预期,完成阶段保存输出结果并触发下一个状态。这个生命周期对所有Agent都是一样的,只是每个阶段的具体实现不同。

状态持久化是AgentService必须实现的功能。一次完整的分析可能需要五到十分钟(多个Agent串行执行,每个Agent需要调用LLM),如果中间某个Agent失败或者服务器重启,你不希望从头开始重新分析。每个Agent完成后把中间结果保存到数据库,这样即使中断也可以从最后一个成功完成的Agent恢复。

AgentService还需要实现超时和熔断机制。如果一个Agent的执行时间超过了预设的上限(比如三分钟),说明可能出了问题(Prompt过长导致模型响应慢、API超时等),此时应该终止执行并返回错误信息而不是让用户无限等待。熔断机制是指在连续多次失败后暂停接受新的分析请求,防止错误级联扩散。

并发控制也是AgentService需要考虑的。如果同时有十个用户提交分析请求,每个请求需要串行执行八个Agent,那么后端需要同时管理八十个Agent实例。你需要设计一个合理的并发策略——控制同时运行的Agent数量上限,超出上限的请求排队等待。同时要注意LLM API的并发限制——大多数模型API都有每分钟请求数的限制,你需要在AgentService层面做好速率控制。


六、EvalService:评估服务

EvalService负责对Agent的输出进行自动化质量评估。虽然在生产环境中最终的质量评判依赖用户反馈和人工审核,但在开发和迭代过程中你需要一个快速的、自动化的方式来评估Prompt变更和Agent优化的效果。

EvalService实现两层评估。第一层是格式评估——检查Agent的输出是否符合预定义的结构化格式。比如岗位拆解Agent的输出应该是一个包含多个步骤的列表,每个步骤应该有步骤名称、步骤描述、频率、耗时和AI潜力评分这五个字段。如果输出缺少任何字段或字段类型不对,格式评估就会报错。

第二层是内容评估——使用LLM来评估另一个LLM的输出质量。这种方式通常被称为”LLM-as-Judge”。你需要写一个评估用的Prompt,让模型从多个维度给Agent输出打分。比如对于方案设计Agent的输出,评估维度包括方案的具体性(是否包含可操作的实施步骤而不是泛泛建议)、方案的完整性(是否覆盖了方案概述、技术架构、实施路径和资源需求)、方案的一致性(各部分内容是否逻辑自洽)、方案的创新性(是否提供了有价值的独特洞察)。

EvalService还需要维护一个评估数据集。这个数据集包含标准化的输入和对应的高质量输出样例(由领域专家编写或审核)。每次Agent优化后,跑一遍评估数据集,对比优化前后的输出质量变化。这个评估数据集就是你的回归测试集——确保优化不会在某些场景提升质量的同时在另一些场景降低质量。

评估报告的输出格式应该是可读的、可比较的。每次评估生成一份报告,包含每个Agent在每个测试用例上的得分、与上一版本相比的分数变化、具体的问题描述和改进建议。这些报告是你持续优化系统的数据基础。


七、ReportService:报告生成服务

ReportService负责将所有Agent的输出整合为一份结构化的最终报告。报告生成不仅仅是把各个Agent的结果拼接在一起,更需要在逻辑上做统一的梳理——消除冗余、解决矛盾、建立上下文关联、确保整体叙事连贯。

报告生成的第一步是内容整合。每个Agent的输出都是独立的,可能存在信息重叠(行业研究Agent和岗位拆解Agent可能都提到了某个业务流程)或者信息矛盾(AI机会评分Agent认为某个场景可行但风险审查Agent认为风险太高)。ReportService需要识别这些重叠和矛盾,进行合并或标注。

第二步是结构组织。最终报告需要有一个清晰的章节结构。推荐的结构是:执行摘要(一页纸的结论概要)、行业背景分析(来自行业研究Agent)、岗位流程拆解(来自岗位拆解Agent)、AI机会评估(来自AI机会评分Agent,包含评分矩阵和排名)、方案详细设计(来自方案设计Agent,针对排名最高的机会展开)、ROI分析(来自ROI Agent)、风险评估与应对(来自风险审查Agent)、实施建议与下一步行动。

第三步是格式渲染。报告以Markdown作为基础格式,但同时需要支持富文本元素——表格(用于评分矩阵和ROI数据)、图表(用于趋势展示和对比分析)、流程图(用于Agent方案设计展示)。ReportService需要将Markdown格式的报告内容转化为可渲染的结构化数据,方便前端展示。

报告生成还需要支持自定义。付费用户可能需要在报告中加入自己的公司信息、调整报告的章节顺序、修改某些措辞以适应内部汇报的需求。ReportService需要提供一个报告模板机制,支持模板级别的自定义而不影响内容生成逻辑。


八、数据库设计与日志系统

数据库是后端系统的持久化层,存储所有需要长期保存的数据。对于我们的系统,推荐使用SQLite作为开发环境的数据库(零配置、方便调试)和PostgreSQL作为生产环境的数据库(更强的并发支持和数据类型支持)。

数据库表的设计需要覆盖前面提到的所有数据类型。用户表存储用户基本信息和认证数据。分析任务表存储每次分析请求的元信息和状态。Agent输出表存储每个Agent在每次分析中的中间结果。报告表存储最终生成的报告内容和元信息。Prompt版本表存储Prompt的版本历史和效果数据。评估结果表存储自动化评估的历史记录。

表之间的关联关系需要仔细设计。一次分析任务对应多个Agent输出(一对多),一个用户可以有多次分析任务(一对多),一次分析任务生成一份报告(一对一)。这些关联关系通过外键约束来保证数据一致性。

日志系统在AI应用后端中比传统应用更重要。除了常规的请求日志(谁在什么时间调用了什么接口、返回了什么状态码),你还需要记录LLM调用日志(每次调用的Prompt和响应、token消耗、耗时)、Agent执行日志(每个Agent的开始时间、结束时间、状态和异常信息)和业务指标日志(每日分析次数、平均分析耗时、平均token消耗、错误率)。

日志级别的设计也有讲究。INFO级别记录正常的业务流程(分析开始、Agent完成、报告生成)。WARNING级别记录需要关注但不影响功能的问题(某次LLM调用超时但重试成功、某次检索返回的结果数量少于预期)。ERROR级别记录需要立即处理的问题(LLM调用连续失败、数据库连接中断)。DEBUG级别记录用于排查问题的详细信息(完整的Prompt内容、中间变量的值)。


九、文件管理与异步任务

文件管理主要处理两类需求:用户上传的业务文档和系统生成的报告文件。

用户上传的文档需要经过几个处理步骤。第一步是文件校验——检查文件格式是否支持、文件大小是否在限制内、文件内容是否可以正常解析。第二步是文件存储——将原始文件保存到本地文件系统或对象存储服务,同时记录文件元信息到数据库。第三步是文件解析——使用文档解析工具提取文件中的文字内容,将提取的内容传递给RAGService进行向量化处理。

文件存储的策略需要考虑成本和访问效率。对于开发阶段,本地文件系统就够了。对于生产环境,建议使用对象存储服务(如S3或兼容的MinIO),这样文件存储和计算服务可以独立扩展。

报告文件导出涉及格式转换。Markdown格式的报告需要转换为PDF和Word格式。PDF转换可以使用weasyprint或reportlab库,Word转换可以使用python-docx库。转换过程需要注意中文支持和样式保持——确保导出的文件排版美观、字体正确、图表清晰。

异步任务处理是后端架构中必须考虑的问题。一次完整分析可能需要五到十分钟,这个时间远超HTTP请求的正常超时时间。所以分析任务必须设计为异步模式——用户提交分析请求后立即返回一个任务ID,后端在后台异步执行Agent流程。前端通过轮询或WebSocket获取任务进度。

异步任务框架推荐使用Celery搭配Redis作为消息队列,或者对于更轻量的方案直接使用FastAPI的BackgroundTasks。无论选择哪种方案,核心需求是一样的——任务提交后可以查询进度、任务完成后可以获取结果、任务失败后可以查看错误信息、任务可以取消。


十、接口文档与服务模块说明

接口文档是后端开发的交付物之一,也是前后端协作的桥梁。FastAPI自动生成的Swagger文档是一个很好的起点,但你需要在每个接口上添加详细的描述、参数说明、返回值说明和示例数据,让前端开发者不需要来问你就能理解怎么调用每个接口。

核心接口包括以下几组。用户管理接口:注册、登录、获取用户信息。分析任务接口:创建分析任务、获取任务列表、获取任务详情、获取任务进度、取消任务。报告接口:获取报告列表、获取报告详情、导出报告。知识库管理接口:上传文档、查询文档列表、触发知识库索引更新。评估接口:运行评估、获取评估结果。

每个接口都需要定义清晰的请求格式和响应格式。使用Pydantic模型来定义请求体和响应体,FastAPI会自动校验请求数据的类型和格式。响应格式建议统一为一个标准结构——包含状态码、消息和数据三个字段。成功时状态码为零,失败时状态码为非零错误码,消息中包含可读的描述信息。

服务模块说明文档需要描述每个模块的职责、接口、依赖和配置。这份文档的目标是让一个新加入项目的开发者能快速理解每个模块做什么以及模块之间的关系。文档不需要面面俱到,但需要覆盖以下关键信息:模块的职责边界(这个模块做什么和不做什么)、模块暴露的接口列表、模块依赖的其他模块和外部服务、模块的配置参数和环境变量、模块的已知限制和未来改进方向。


今日实践任务总结

今天的核心任务是搭建后端服务的完整骨架。具体交付物如下。

第一份交付物是后端服务代码,包含项目初始化配置、LLMClient模型调用模块、PromptManager管理模块、RAGService知识库服务、AgentService编排框架、EvalService评估服务和ReportService报告生成服务的基本实现。

第二份交付物是接口文档,通过FastAPI自动生成并补充人工描述,覆盖所有核心API端点的请求格式、响应格式和使用说明。

第三份交付物是服务模块说明,描述每个模块的设计思路、接口定义、依赖关系和配置要求。

第四份交付物是后端测试记录,记录你启动后端服务后执行的基本功能测试——能否正常调用LLM、能否正常检索知识库、能否正常运行单Agent流程、能否正常生成报告。记录测试过程中遇到的问题和解决方式。

后端搭建完成后,明天你将基于这些接口构建前端页面和报告展示功能。后端和前端的分离设计让你可以独立开发和测试两边,这也是工程化能力的一个重要体现。