颠覆传统开发:用LangChain实现极致‘对话式’软件构建

—— 让软件开发回归需求本质

在日常的开发工作中,你是否遇到过这样的痛点:需求文档缺失或混乱,设计方案实现代码脱节,原本该花在需求分析上的时间,不得不去弥补文档缺失带来的沟通成本?又或者,团队想升级AI能力,却苦于对大模型的维护成本高?
今天给大家分享一套基于 LangChain + FastAPI + Ollama + MongoDB全生命周期AI开发平台思路,帮助我们通过自然语言聊天的方式,以模块为单位,快速完成需求管理、设计方案生成、代码实现等软件开发流程。

1. 背景与初衷

几个月前,我们团队在一次大型软件项目中遭遇了「需求变更」的泥潭。需求沟通和文档管理极其混乱,大家都在讨论“这个功能的需求点在哪里”、“为什么设计方案不符合业务期望”、“到底是哪一段代码实现了这个需求”……最终导致项目严重拖期。
后来我们逐渐意识到,一个全流程的统一管理平台,能够帮助我们把需求设计代码过程文档串联起来,并且最好能有AI能力来辅助整理、归纳、补充。于是我们萌生了一个想法:让AI以智能体(Agent)的形式成为团队可信赖的小伙伴,在内部提供一个统一的HTTP接口,按照需求、设计、代码、测试用例等关键节点进行“对话驱动式”的开发。

2. 平台整体思路

在开发平台中,我们将 LangChain 作为核心“大脑”,它可以灵活地接入不同的大模型(LLM)。我们会在后端使用 FastAPI 提供统一的HTTP接口,而数据库选用 MongoDB 存储需求文档、设计方案、代码片段以及会话上下文。为了充分利用大模型的“上下文记忆”能力,我们需要在会话过程中保存大量中间信息。

平台的优势主要体现在以下几个方面:

  1. 灵活切换大模型:通过将LangChain的LLM抽象集成进来,我们可以随时替换不同供应商的模型,或切换到不同规模的参数模型,让平台的AI能力与前沿技术保持同步。
  2. 全流程管理软件开发:从需求源头开始,通过预先设计好的对话流,自动生成功能点、测试用例、设计文档、以及最关键的代码实现,让开发回归对需求本质的思考。
  3. 强大的文本理解:借助大模型对文本知识进行分析、理解、整理、索引,工程师不用再为编写、补全大段的文档或测试用例耗费精力。
  4. 自动生成过程文档:这一功能让每一次需求-设计-实现的过程都能被自动沉淀为文档,避免后期同样的问题反复出现,大大提升协作效率。

接下来,通过一段有趣的“代码之旅”,来看看关键的核心实现流程。


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 代码解读

  1. app = FastAPI():初始化FastAPI应用。
  2. @app.post("/stream"):对外暴露的接口,前端只要POST到/stream,并带上一个prompt(用户输入)以及session_id(会话上下文标识)即可开始与AI对话。
  3. 依赖注入
    • db=Depends(get_database): 将数据库MongoDB的句柄传递给这个路由函数。
    • llm=Depends(get_llm): 将大模型实例(或元组)通过依赖注入的方式传进来。
  4. StreamingResponse:返回一个流式响应,保证前端可以边发送(或等待)数据边展示结果,提升交互体验。
  5. 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 代码解读

  1. ChatOllama:来自 langchain_ollama,这是一个将Ollama模型封装进LangChain接口的适配器类。
  2. initialize_llm():异步函数,在服务启动时调用,将三个规模配置不同的模型实例化。
  3. @app.on_event("startup"):通过FastAPI提供的事件机制,我们让服务启动时就自动完成大模型加载,避免运行过程中重复初始化。
  4. temperature=0.1:控制大模型的采样温度,让回答更偏向确定性。
  5. num_ctx:表示上下文长度(token数量)。一般情况下需求分析用2048就足够,但当我们进行大篇幅的需求或代码生成时,会用更大的num_ctx
  6. 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 代码解读

  1. GraphStateGraph:继承自 StateGraph,在构造函数里定义所有节点和它们之间的关系:
    • process_input:用户输入预处理节点
    • fill_requirements:需求处理节点
    • fill_design_plan:设计方案节点
    • fill_code_generated:代码生成节点
    • chitchat:闲聊节点
  2. 工作流分支:如果input_category(需求类别)是“需求描述”,就进入fill_requirements;若是“设计方案”,则进入fill_design_plan,等等。
  3. langchain_stream:将用户输入会话上下文(包括需求、设计文档、代码草稿等)组装成一个 context,然后调用 compiled_graph.astream() 基于上下文来分发处理逻辑。
  4. 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 代码解读

  1. classify(...):一个简单的分类函数,用小模型去判断当前输入的意图。
  2. setResultFlag(...):设一个标志位,方便后续在对话输出时进行定制化标题、标签等。
  3. prompt:在系统提示(system)中给出了详尽的需求合并与冲突检查要求,并强制输出一个JSON格式。这是LangChain中非常常见的做法,让AI回答更加结构化。
  4. json_result = await state['llm_medium'].ainvoke(prompt):把拼装好的提示词给大模型调用,得到一个JSON形式的输出,然后解析并更新到全局的state['requirements']中。
  5. await save_requirements_to_Mongo(...):同样是一个异步API,把需求更新落地到MongoDB。

价值

  1. 用对话替代繁琐的文档撰写:当产品经理提了一个新需求,用自然语言描述,就能自动更新到需求文档里。
  2. 自动检查冲突:让大模型在合并需求时检测冲突与冗余,节省大量沟通成本。
  3. 轻松查询:支持用户查询需求现状,随时掌握项目进度。

6.2 设计节点和代码生成节点

  • 设计节点代码生成节点的实现与需求节点非常相似,核心区别在提示词(Prompt)的不同,以及在数据库中存储的文档结构不同。
  • 在“设计节点”,我们会让模型输出模块的类图、接口说明、关键算法描述等信息;在“代码生成节点”,我们会让模型根据设计方案进一步完善或直接生成具体的Python/Java等语言的实现代码,甚至自动解析出模块的依赖。

7. 让AI实现从需求到设计到代码的全过程

  1. 分层回复:通过“闲聊/需求/设计/实现”等关键节点的get/set,可以让AI不断迭代这几个文档。
  2. 多模型配合:场景简单时,用llm_normal去做快速分类,减少开销;要做复杂的方案设计时,切到llm_medium甚至llm_large,保证上下文足够大。
  3. 动态Prompt:基于LangChain的PromptTemplate机制,灵活封装信息,以json结构的方式输出、解析,保证机器与机器之间的“沟通”可控可查。
  4. 版本管理:在数据库中用session_idtimestamp来记录每一次需求或设计更新的版本,让开发者追溯历史变得容易。

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把繁琐的需求到代码的过程变得简单、高效,同时也更具可追溯性。

猜你喜欢

转载自blog.csdn.net/qqxdh/article/details/145745170
今日推荐