—— 让软件开发回归需求本质
在日常的开发工作中,你是否遇到过这样的痛点:需求文档缺失或混乱,设计方案与实现代码脱节,原本该花在需求分析上的时间,不得不去弥补文档缺失带来的沟通成本?又或者,团队想升级AI能力,却苦于对大模型的维护成本高?
今天给大家分享一套基于 LangChain + FastAPI + Ollama + MongoDB 的全生命周期AI开发平台思路,帮助我们通过自然语言聊天的方式,以模块为单位,快速完成需求管理、设计方案生成、代码实现等软件开发流程。
1. 背景与初衷
几个月前,我们团队在一次大型软件项目中遭遇了「需求变更」的泥潭。需求沟通和文档管理极其混乱,大家都在讨论“这个功能的需求点在哪里”、“为什么设计方案不符合业务期望”、“到底是哪一段代码实现了这个需求”……最终导致项目严重拖期。
后来我们逐渐意识到,一个全流程的统一管理平台,能够帮助我们把需求、设计、代码和过程文档串联起来,并且最好能有AI能力来辅助整理、归纳、补充。于是我们萌生了一个想法:让AI以智能体(Agent)的形式成为团队可信赖的小伙伴,在内部提供一个统一的HTTP接口,按照需求、设计、代码、测试用例等关键节点进行“对话驱动式”的开发。
2. 平台整体思路
在开发平台中,我们将 LangChain 作为核心“大脑”,它可以灵活地接入不同的大模型(LLM)。我们会在后端使用 FastAPI 提供统一的HTTP接口,而数据库选用 MongoDB 存储需求文档、设计方案、代码片段以及会话上下文。为了充分利用大模型的“上下文记忆”能力,我们需要在会话过程中保存大量中间信息。
平台的优势主要体现在以下几个方面:
- 灵活切换大模型:通过将LangChain的LLM抽象集成进来,我们可以随时替换不同供应商的模型,或切换到不同规模的参数模型,让平台的AI能力与前沿技术保持同步。
- 全流程管理软件开发:从需求源头开始,通过预先设计好的对话流,自动生成功能点、测试用例、设计文档、以及最关键的代码实现,让开发回归对需求本质的思考。
- 强大的文本理解:借助大模型对文本知识进行分析、理解、整理、索引,工程师不用再为编写、补全大段的文档或测试用例耗费精力。
- 自动生成过程文档:这一功能让每一次需求-设计-实现的过程都能被自动沉淀为文档,避免后期同样的问题反复出现,大大提升协作效率。
接下来,通过一段有趣的“代码之旅”,来看看关键的核心实现流程。
3. 基于FastAPI的统一Agent入库
让我们先从一个大家最熟悉的场景开始:搭建一个简单的 FastAPI 接口服务,提供给前端或内部的其他微服务调用。在这里,我们需要实现一个“流式”响应(text/event-stream
),以便在前端可以一边接收大模型的回答,一边更新客户端UI。
下面是一个示例代码片段(整合了部分依赖注入和路由逻辑),我们先放出来,再进行行文解读。
from fastapi import Depends, FastAPI, Request, Response
from fastapi.params import Body
from pydantic import BaseModel
from components.MongoDbClient import get_database
from components.OllamaModel import get_llm, register_events
from fastapi.responses import JSONResponse, StreamingResponse
from agents.Agent import langchain_stream
from services.ModuleMgr import getContent, loadScript, saveContent
from services.LogPusher import log_push
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
app = FastAPI()
@app.post("/stream")
async def stream(
request: StreamRequest = Body(...),
db=Depends(get_database),
llm=Depends(get_llm)
):
"""
HTTP接口:/stream
用于接受用户的对话请求,并将其转发给langchain_stream进行处理。
通过StreamingResponse,将大模型的输出以流式的方式返回给前端。
"""
return StreamingResponse(
langchain_stream(request.prompt, request.session_id, request.stream_mode, db, llm),
media_type="text/event-stream"
)
3.1 代码解读
app = FastAPI()
:初始化FastAPI应用。@app.post("/stream")
:对外暴露的接口,前端只要POST到/stream
,并带上一个prompt
(用户输入)以及session_id
(会话上下文标识)即可开始与AI对话。- 依赖注入:
db=Depends(get_database)
: 将数据库MongoDB的句柄传递给这个路由函数。llm=Depends(get_llm)
: 将大模型实例(或元组)通过依赖注入的方式传进来。
StreamingResponse
:返回一个流式响应,保证前端可以边发送(或等待)数据边展示结果,提升交互体验。langchain_stream(...)
:这是一个核心方法,内部会调用LangChain的有向图流程(StateGraph),根据不同的用户输入类别(需求描述、设计方案、代码生成、还是普通闲聊?)跳转到对应的计算节点去“思考”和输出结果。
这一步的思路是:将大模型的对话流抽象成一个HTTP服务,让它能够随时被访问——不管是网页端还是CLI或其他后端微服务都能直接调用。
4. 统一管理的LLM创建
在这个项目里,我们准备了三个不同的模型实例:llm_normal
, llm_medium
, llm_large
,它们对应不同的上下文长度(num_ctx
),以应对需求分析、设计方案、代码生成功能中的上下文深度需求各不相同。
下面贴上一段关键代码:
from typing import Tuple
from langchain_ollama import ChatOllama
# Initialize language model and state graph
OLLAMA_URL = "http://172.0.0.1:11434" # Replace with实际地址和端口
model_name = "qwen2.5-coder:14b"
llm_format = "json"
llm_normal = None
llm_medium = None
llm_large = None
async def initialize_llm():
global llm_normal, llm_medium, llm_large # 使用全局变量存储 LLM 实例
llm_normal = ChatOllama(
model=model_name,
format=llm_format,
temperature=0.1,
stream=True,
num_ctx = 2048,
base_url=OLLAMA_URL
)
llm_medium = ChatOllama(
model=model_name,
format=llm_format,
temperature=0.1,
stream=True,
num_ctx = 20480,
base_url=OLLAMA_URL
)
llm_large = ChatOllama(
model=model_name,
format=llm_format,
temperature=0.1,
stream=True,
num_ctx = 40960,
base_url=OLLAMA_URL
)
def register_events(app):
@app.on_event("startup")
async def startup_event():
await initialize_llm()
# 定义一个依赖,用于获取 LLM 实例
def get_llm() -> Tuple[ChatOllama, ChatOllama, ChatOllama]:
"""
返回全局 LLM 实例,供依赖注入使用。
"""
if llm_normal is None:
raise RuntimeError("LLM instance is not initialized.")
return llm_normal, llm_medium, llm_large
4.1 代码解读
ChatOllama
:来自langchain_ollama
,这是一个将Ollama模型封装进LangChain接口的适配器类。initialize_llm()
:异步函数,在服务启动时调用,将三个规模配置不同的模型实例化。@app.on_event("startup")
:通过FastAPI提供的事件机制,我们让服务启动时就自动完成大模型加载,避免运行过程中重复初始化。temperature=0.1
:控制大模型的采样温度,让回答更偏向确定性。num_ctx
:表示上下文长度(token数量)。一般情况下需求分析用2048
就足够,但当我们进行大篇幅的需求或代码生成时,会用更大的num_ctx
。get_llm()
:借用FastAPI依赖注入的特性,将不同规模的LLM实例一次性传递给路由或者其他服务,方便在不同场景下使用不同上下文深度的模型。
5. 基于LangChain打造工作流(StateGraph)
在这个平台中,最核心的思路是:把对话视为一个有向图,每个节点对应一个“功能处理器”,通过条件判断决定要“走向”哪个节点。这样就能将需求管理、设计方案、代码生成等逻辑模块化。
下面就是一个示例实现(为了让大家更好理解,这里的实现基于 langgraph.graph
这种比较简洁的流程编排方式):
import traceback
from typing import Tuple, TypedDict, Dict, AsyncGenerator
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage
from langchain_core.prompts.chat import ChatPromptTemplate
from agents.AgentRequirments import handler_requirements, requirement_template
from agents.AgentDesignPlans import fill_design_plan, design_plan_template
from agents.AgentGenerateCode import fill_code_generated, code_generated_template
import json
from langchain_core.output_parsers import JsonOutputParser
class GraphStateGraph(StateGraph):
def __init__(self):
super().__init__(GraphState) # Pass the state schema here
# 定义工作流节点
self.add_node("process_input", lambda state: process_input(state))
self.add_node("fill_requirements", handler_requirements)
self.add_node("fill_design_plan", fill_design_plan)
self.add_node("fill_code_generated", fill_code_generated)
self.add_node("chitchat", chitchat)
# 设置连接
self.add_edge(START, "process_input")
self.add_conditional_edges(
"process_input",
lambda state: state['input_category'],
{
"闲聊": "chitchat",
"其他": "chitchat",
"需求描述": "fill_requirements",
"设计方案": "fill_design_plan",
"代码生成": "fill_code_generated",
},
)
self.add_edge("chitchat", END)
self.add_edge("fill_requirements", END)
self.add_edge("fill_design_plan", END)
self.add_edge("fill_code_generated", END)
async def langchain_stream(
user_input: str,
session_id:str,
stream_mode:str,
db:object,
llm: Tuple[ChatOllama, ChatOllama, ChatOllama]
) -> AsyncGenerator[str, None]:
llm_normal, llm_medium, llm_large = llm
# 初始化图
state_graph = GraphStateGraph()
compiled_graph = state_graph.compile()
artifact = await query_artifact(session_id, db)
context = {
"messages": [HumanMessage(content=json.dumps({'content':user_input}, ensure_ascii=False))],
"session_id": session_id,
"user_input": user_input,
"complete": False,
"requirements": artifact['requirements'] if artifact else requirement_template,
"requirements_to_fill": artifact['requirements_to_fill'] if artifact and attr_in(artifact,'requirements_to_fill') else "",
"requirements_integrity": artifact['requirements_integrity'] if artifact and attr_in(artifact,'requirements_integrity') else 0.0,
"design_plan": artifact['design_plan'] if artifact and attr_in(artifact,'design_plan') else design_plan_template,
"design_plan_to_fill": artifact['design_plan_to_fill'] if artifact and attr_in(artifact,'design_plan_to_fill') else "",
"design_plan_integrity": artifact['design_plan_integrity'] if artifact and attr_in(artifact,'design_plan_integrity') else 0.0,
"code_generated": artifact['code'] if artifact and attr_in(artifact,'code') else code_generated_template,
"code_generated_to_fill": artifact['code_generated_to_fill'] if artifact and attr_in(artifact,'code_generated_to_fill') else "",
"code_generated_integrity": artifact['code_generated_integrity'] if artifact and attr_in(artifact,'code_generated_integrity') else 0.0,
"package_name": 'top.wiwo.'+session_id.replace('-','.'),
"db": db,
"llm_medium": llm_medium | JsonOutputParser(),
"llm_normal": llm_normal | JsonOutputParser(),
"llm_large": llm_large | JsonOutputParser(),
"input_category": "",
"resultFlag": False
}
try:
# 调用异步图并注册stream_mode
async for event in compiled_graph.astream(context, stream_mode="messages"):
# 这里yield给StreamingResponse
yield event[0].content
except Exception as e:
print(f"Error during graph execution: {e}")
traceback.print_exc()
5.1 代码解读
GraphStateGraph
:继承自StateGraph
,在构造函数里定义所有节点和它们之间的关系:process_input
:用户输入预处理节点fill_requirements
:需求处理节点fill_design_plan
:设计方案节点fill_code_generated
:代码生成节点chitchat
:闲聊节点
- 工作流分支:如果
input_category
(需求类别)是“需求描述”,就进入fill_requirements
;若是“设计方案”,则进入fill_design_plan
,等等。 langchain_stream
:将用户输入、会话上下文(包括需求、设计文档、代码草稿等)组装成一个context
,然后调用compiled_graph.astream()
基于上下文来分发处理逻辑。yield event[0].content
:把LangChain节点执行结果一条条地推到前端。
要点:
- 这个“分层+编排”的方式很重要,进一步强化了对话上下文如何在数据库里被逐步更新,也让开发者可以更好地追踪每一步的状态。
- 在真实场景中,需要在
process_input
里对用户输入做更加细化的分析和分类,比如“需求修改”、“需求查询”、“需求合并”等。在后续的节点中也做类似的工作。
6. 举例:需求节点的处理逻辑
这一部分最能体现AI如何扮演需求分析师的身份。我们来看一个简化版示例:
import json
from typing import Dict, Any
from langchain_core.prompts.chat import ChatPromptTemplate
import traceback
from agents.CommonAIAgent import classify, setResultFlag
from utils.CloneUtils import cloneStructure
from langchain_core.messages.ai import AIMessage
# 假设某个需求的初始结构定义,包含功能概述、用例、表单等。
requirement_example = {
"功能概述":"模块描述",
"用例":[
{
"用例描述":"",
"用例编号":"",
"业务流程":"",
"业务规则":"",
"数据校验":"",
"涉及表单":"",
"参与角色":""
}
],
"表单": [
{
"表单名称": "表单名称",
"表单字段": [
"字段1",
"字段2",
"字段3"
]
}
]
}
requirement_template = cloneStructure(requirement_example)
async def handler_requirements(state: Dict[str, Any]) -> Dict[str, Any]:
''' 需求节点的核心处理逻辑 '''
try:
user_input = state['user_input']
# 首先用小模型判断:用户想“查询需求” or “优化需求”
input_type = classify(state['llm_normal'], "查询需求,优化需求", user_input)
if input_type == "查询需求":
# 直接输出当前的需求信息
await setResultFlag(state['llm_normal'])
state['messages'].append(AIMessage(content=json.dumps(state['requirements'], ensure_ascii=False)))
return state
# 如果是“优化需求”,我们进一步分:是单个用例的优化?还是整个模块需求的优化?
input_type = classify(state['llm_medium'], "优化整个模块的需求,优化单个用例的需求", user_input)
if input_type == "优化单个用例的需求":
await setResultFlag(state['llm_normal'])
json_result = await state['llm_large'].ainvoke(user_input)
# 这里只是示例:我们拿到最终的需求数据后,可以更新 MongoDB
return state
# 如果没有特定分类,就执行默认的需求合并逻辑
template = ChatPromptTemplate([
("system", """
你是一个资深的系统分析师,请按用户输入:
完善当前的需求。处理过程中的具体要求:
1. 保证需求的严谨性,若有重复或冲突请合并或指出;
2. 将需求细化为有条理的层级;
3. 若有新增用例,请生成唯一的用例编号;
4. 等等...
最终必须严格输出JSON格式:
{
"是否冲突": false,
"需求": {requirement_template},
"完整度": 0.0,
"待补充部分": ""
}
"""),
("human", """现有需求整理结果:{requirements}"""),
("human", """{user_input}"""),
])
prompt = template.format(
requirement_template = requirement_template,
requirement_example = requirement_example,
user_input=user_input,
requirements=state['requirements']
)
await setResultFlag(state['llm_normal'])
json_result = await state['llm_medium'].ainvoke(prompt)
if not json_result['是否冲突']:
state['requirements'] = json_result['需求']
state['requirements_integrity'] = json_result['完整度']
state['requirements_to_fill'] = json_result['待补充部分']
# 同步存储到MongoDB
await save_requirements_to_Mongo(json_result, state['session_id'], state['db'])
except Exception as e:
print(f"Error filling requirements: {e}")
traceback.print_exc()
return state
6.1 代码解读
classify(...)
:一个简单的分类函数,用小模型去判断当前输入的意图。setResultFlag(...)
:设一个标志位,方便后续在对话输出时进行定制化标题、标签等。prompt
:在系统提示(system
)中给出了详尽的需求合并与冲突检查要求,并强制输出一个JSON格式。这是LangChain中非常常见的做法,让AI回答更加结构化。json_result = await state['llm_medium'].ainvoke(prompt)
:把拼装好的提示词给大模型调用,得到一个JSON形式的输出,然后解析并更新到全局的state['requirements']
中。await save_requirements_to_Mongo(...)
:同样是一个异步API,把需求更新落地到MongoDB。
价值:
- 用对话替代繁琐的文档撰写:当产品经理提了一个新需求,用自然语言描述,就能自动更新到需求文档里。
- 自动检查冲突:让大模型在合并需求时检测冲突与冗余,节省大量沟通成本。
- 轻松查询:支持用户查询需求现状,随时掌握项目进度。
6.2 设计节点和代码生成节点
- 设计节点和代码生成节点的实现与需求节点非常相似,核心区别在提示词(Prompt)的不同,以及在数据库中存储的文档结构不同。
- 在“设计节点”,我们会让模型输出模块的类图、接口说明、关键算法描述等信息;在“代码生成节点”,我们会让模型根据设计方案进一步完善或直接生成具体的Python/Java等语言的实现代码,甚至自动解析出模块的依赖。
7. 让AI实现从需求到设计到代码的全过程
- 分层回复:通过“闲聊/需求/设计/实现”等关键节点的get/set,可以让AI不断迭代这几个文档。
- 多模型配合:场景简单时,用
llm_normal
去做快速分类,减少开销;要做复杂的方案设计时,切到llm_medium
甚至llm_large
,保证上下文足够大。 - 动态Prompt:基于LangChain的PromptTemplate机制,灵活封装信息,以json结构的方式输出、解析,保证机器与机器之间的“沟通”可控可查。
- 版本管理:在数据库中用
session_id
、timestamp
来记录每一次需求或设计更新的版本,让开发者追溯历史变得容易。
8. 总结与展望
通过这些例子可以看到,“AI+工作流编排” 的魅力在于让软件开发过程不再“割裂”。在这个平台里,需求一旦更新,就能迅速触发设计或代码的同步更新;而工程师需要做的,是和AI“对话”,从而将人类的创造力和大模型强大的文本处理能力结合起来。
8.1 我们为什么说这是“回归开发本质”?
- 开发的核心是需求:团队花更多精力在“需求梳理”上,而不是写重复的文档或在代码仓库里到处搜寻。
- 全流程统一:需求、方案、实现、文档始终存储在同一个系统,且由LangChain来帮助我们“串”起来。
- 可扩展性:当新一代大模型出现时,只要在
initialize_llm()
里替换一下模型地址即可享受新特性。
8.2 推荐的技术栈与下一步演进
- 前端可用React/Vue/Flutter等,对接
/stream
接口,实现Stream流式聊天; - 数据库目前示例中是MongoDB,也可替换为PostgreSQL等;
- 模型选用Ollama大模型或者OpenAI、Cohere等,取决于私有化程度与文本处理需求;
- 业务扩展:让AI再生成自动化测试用例、CI/CD脚本,甚至自动化部署,也是一种进一步的尝试。
9. 结语
回想起当初那个“需求文档缺失”导致的项目延期的日子,如果当时就有像现在这样基于LangChain的AI智能体,或许我们可以把时间花在真正的需求分析和业务价值上,而不是让每个人都疲于撰写和合并各种文档。
希望这篇文章能给所有全栈开发者带来一点启发——哪怕你不打算做完整的平台,也可以借鉴工作流编排+多模型配合+数据库存储的思路,把对话驱动的AI能力应用到日常的软件开发当中。
让我们用AI把繁琐的需求到代码的过程变得简单、高效,同时也更具可追溯性。