AI代码生成平台
liyupi/yu-ai-code-mother项目目录
项目简介
AI代码生成平台支持通过自然语言生成 HTML 页面、多文件网页和 Vue 工程。用户可以预览生成结果,选中页面元素继续提出修改要求,并将应用构建发布为可分享的网站。
如何写进简历
页匠 · 品牌活动页面生成平台 | 场景化示例
项目描述:基于 AI 代码生成平台,面向品牌运营,将新品卖点、主视觉与活动时间生成展示页,支持对话改稿、预览和发布。
技术栈:Java、Spring Boot、LangChain4j、Vue 3、MySQL、Redis
主要工作:
- 分类型生成与流式交互:基于
LangChain4j按产物类型路由HTML、多文件与Vue生成链路,将模型文本和文件工具调用统一转换为SSE事件,支持活动页流式生成与连续修改。 - 应用上下文与服务复用:以
appId关联活动页与对话,复用MySQL历史、Redis窗口记忆和Caffeine服务缓存,支持连续改稿及服务实例重建后加载近期对话。 - 真实构建与产物发布:利用现有部署链路,对
Vue活动页执行依赖安装和生产构建,检查命令结果与dist后发布产物,以deployKey关联访问地址和部署记录。
面试题与回答
大模型流式输出与工具调用之间如何保持状态一致?
展开收起
核心回答
AI 代码生成平台把普通代码文本流和带工具调用的
Vue工程生成分开处理。HTML、多文件模式边展示边收集文本,流结束后统一解析并保存;Vue模式使用LangChain4j的TokenStream,分别处理文本片段、工具请求和工具执行回调,再转换成页面可读内容,通过SSE推送。这个版本的关键是:部分工具请求只驱动展示,真正执行发生在完整模型响应形成之后。 工具结果按原请求写入模型记忆,再继续调用模型。展示层按工具 ID 避免重复提示,但这还不是完整的执行幂等机制;流结束也不代表工程一定构建成功。
聊天请求经过哪些层
AppController接收应用 ID 和用户消息,调用AppServiceImpl.chatToGenCode();服务层校验应用与用户,先记录用户消息,再进入AiCodeGeneratorFacade.generateAndSaveCodeStream(),按生成类型选择对应的LangChain4j服务。这里的聊天链路直接进入模型和工具循环,不需要经过另一个LangGraph4j工作流。HTML和多文件模式返回Flux<String>。门面类收集全部文本,在正常结束时调用代码解析器和文件保存器;简单流处理器再收集展示文本,写入聊天记录。这条路线没有把每个文本片段当成一次文件写入。Vue模式则把三类回调转换成内部JSON消息:AI_RESPONSE携带模型文本,TOOL_REQUEST携带工具请求信息,TOOL_EXECUTED携带执行请求和返回结果。随后交给JsonMessageStreamHandler,处理器把它们转成展示字符串,控制器再以{"d": "展示片段"}包装成SSE数据,并在正常结束后追加done事件。浏览器接收的不是这三类原始工具消息的直接透传。部分工具回调为什么不会直接执行
项目内包含自定义的
dev/langchain4j实现,回调语义需要沿这层看。在OpenAiStreamingChatModel中,工具片段到达时会保留调用 ID、名称并追加参数;传给onPartialToolExecutionRequest()的arguments是本次参数片段,不是已经拼好的完整参数。对应实现说明了这一点。模型响应收集完成后,
AiServiceStreamingResponseHandler.onCompleteResponse()读取完整AiMessage中的工具请求,逐个交给ToolExecutor。执行返回后,先把对应的ToolExecutionResultMessage加入记忆,再触发onToolExecuted(),并带着更新后的消息请求下一轮模型;模型不再要求调用工具时,才回调应用层的最终完成处理。完整请求执行分支与部分回调是两条不同职责。所以,参数分成多块时,应用层只是不断获知“这个工具正在形成请求”,不会由展示处理器拿半段参数重复执行工具。
展示状态与真实执行还有哪些区别
JsonMessageStreamHandler在一次流处理内维护seenToolIds。首次看到某个工具 ID 时,返回该工具的调用提示,后续同 ID 的请求片段返回空字符串,避免页面反复展示同一条提示。这个集合控制的是本次流内的提示去重,不能证明跨请求重试时工具只执行一次。收到
TOOL_EXECUTED后,处理器解析请求参数,调用工具的generateToolExecutedResult()生成展示内容,并追加到聊天记录缓冲区。虽然内部消息包含实际result,当前展示分支没有读取它,而是依据参数格式化内容。例如写文件工具会根据文件路径和内容生成代码块;工具方法即使返回“文件写入失败”,该展示分支也没有据此明确标成失败。处理器和写文件工具共同体现了这个区别。最终完成状态也有类似边界:
Vue的完成回调调用buildProject(),但没有检查其布尔返回值,随后仍结束流;HTML和多文件模式的解析保存异常也只记录日志。因此,当前done可以表示响应流正常走完,不能据此确认文件保存或构建一定成功。进一步完善时,应把工具实际返回结果中的成功或失败传给展示层,构建失败也要进入明确的失败状态,再决定是否发送业务完成事件。这样模型记忆、页面提示与工程产物才能依据同一份执行事实更新,而不是都依据一段“看起来成功”的文字。
多轮对话记忆如何分级管理,并设计缓存策略?
展开收起
核心回答
平台为同一个应用的连续修改保留三类对象:
MySQL保存展示用对话历史,Redis支撑模型使用的消息窗口,Caffeine缓存应用的 AI 服务实例。窗口按appId定位,配置最多20条消息;服务实例按应用与生成类型复用,减少重复创建和历史装载。用户继续修改同一应用时,模型结合当前窗口与新要求生成或调整代码。服务实例需要重新创建时,再从数据库取最近历史回装窗口。因此,历史是可回看的记录,窗口是本轮模型上下文,实例缓存是运行对象的生命周期管理,不能把三者都叫作长期记忆。
一次请求与历史回装怎样连接
聊天入口先检查应用归属,将本轮用户消息写入数据库,再通过
AiCodeGeneratorServiceFactory获取服务。工厂使用appId + codeGenType作为Caffeine键,缓存未命中时才创建实例。创建时构造
MessageWindowChatMemory:以appId为 ID,接入RedisChatMemoryStore,设置maxMessages(20)。这里是20条消息,不是20轮对话,也不是 Token 上限。随后调用loadChatHistoryToMemory(),按应用查询最近20条数据库历史;查到记录后反转为时间正序,先清空当前窗口,再把用户、AI 展示消息分别转成UserMessage、AiMessage加入。缓存命中时不会每轮重做这段装载。服务工厂和历史回装方法展示了这个顺序。数据库回装恢复的是这些展示消息,不是先前完整的工具调用对象、调用 ID 和
ToolExecutionResultMessage轨迹。对继续改代码的任务,历史可以说明用户之前提出过什么,当前项目文件仍是判断代码现状的依据;不能仅凭一段旧回答认定某个文件已经修改成功。缓存策略与连续修改的取舍
Caffeine配置最多1000个服务实例,并设置写入后30分钟、访问后10分钟的过期策略;它控制服务对象的复用,不负责提取用户偏好。Redis消息存储另有 TTL 配置,数据库历史不会因为本地实例被淘汰而自动删除。例如用户先要求生成表单,再要求“提交时显示加载状态”,近期窗口能保留这次修改的背景;较早的具体实现已经移出窗口时,应按需读取当前文件或相关历史,而不是无限扩大窗口。消息数窗口简单直接,但单条代码很长时仍可能超出模型预算,后续可评估 Token 窗口或保留关键修改摘要。
同一应用的请求会复用相应记忆和代码目录,实例缓存不等于请求互斥。若允许并发修改,需要另行设计应用级串行或版本检查;当前配置本身没有保证这些语义。
参考资料
- 国内:JavaGuide:Agent 记忆系统:参考工作记忆、跨会话事实与记忆读写生命周期,组织通用分级,并解释保存聊天记录不等于完成长期记忆提取。
- 国内:zero2Agent:记忆与上下文:采用状态、模型上下文与持久记录的区分,解释回装历史和恢复完整执行轨迹是不同目标。
- 国内:WeThinkIn:记忆与上下文工程:参考作用域、版本和派生副本的管理,说明缓存键与失效策略为何需要对应具体主体和数据。
- 国内:阿里云百炼:长期记忆:借鉴从对话提取特定事实、按记忆主体召回的方式,说明长期偏好与普通消息窗口、对象缓存的区别。
- 国外:LangChain4j:Chat Memory:依据 Memory 与 History、消息窗口与存储接口的区别,解释模型可见消息和完整展示历史,以及消息数限制的含义。
- 国外:Caffeine:Eviction:依据容量、写入过期和访问过期策略,解释服务实例缓存的生命周期与窗口、历史保留策略为何分开。
如何约束并验证 AI 生成代码的质量?
展开收起
核心回答
这个平台要把生成完成、构建成功、部署完成和需求满足分开判断。实际单体 App 链路中,模型流结束后可以发出
SSE done,但这不能证明文件、构建和业务功能都正确;Vue 应用的正式部署入口会重新构建,检查返回值和dist,通过后才复制产物并更新部署信息。构建能发现依赖、语法和打包问题,不能证明页面已满足用户需求。进一步完善时,应从需求提取可执行的页面验收条件,将构建和功能验证分别记录,失败后再反馈具体证据修正;这些验收能力需要另外接入。
为什么流结束不能当作质量通过
AppController在内容流正常完成后追加done事件,它表达的是这次响应结束。Vue 生成分支在模型完成回调中调用VueProjectBuilder.buildProject(),但没有检查其布尔返回值,随后便完成流。因此,即使构建器返回false,前端仍可能收到正常结束事件。HTML/MULTI_FILE 分支则在收集完整文本后解析、保存代码;保存异常目前只记录日志,也可能不改变流的正常结束状态。生成片段已经展示给用户,与文件已经成功保存,是两个不同结果。生成门面和响应结束事件可以对照这两层状态。
正式部署用哪些实际条件放行
deployApp()先检查应用及归属、定位生成目录;Vue 类型调用构建器,成功后才使用构建产物发布:VueProjectBuilder检查项目目录和package.json,依次执行npm install、npm run build。命令超时、异常或非零退出码都会返回失败;最后检查dist存在且是目录。- 部署入口检查
buildSuccess,为false就抛出构建失败;随后再次检查dist是否存在,将复制来源切换为该目录。 - 把产物复制到部署目录,更新
deployKey和部署时间,再返回访问地址。HTML/MULTI_FILE 类型按已有静态文件部署,不经过 Vue 构建分支。
构建器判断命令与产物,部署服务决定失败是否阻断发布。相同的构建函数,在调用方忽略返回值与据此拒绝发布时,形成的交付保证不同。
这里还有产物与记录的一致性边界:构建器没有显式清理旧
dist或校验产物的生成版本;复制文件与更新部署记录又是分步操作,记录更新失败不会自动撤回已复制的文件。进一步完善时,可以为构建产物增加版本标识,让发布目录切换与部署记录指向同一版本;不能只凭目录存在就确认这次发布内容正确。模型质检和需求验收放在哪里
仓库另外提供
LangGraph4j工作流实验,将生成、模型质检和项目构建组织成节点;它没有接入上述 App 聊天主路径。实验中的质检节点在调用异常时设置为通过,构建节点异常时会返回原代码目录,因此不能把节点名称当成主应用已有的质量门禁。如果后续把模型质检接入正式链路,可以用它发现可能的问题,但仍由真实构建和独立用例确认结果。例如需求要求表单有姓名、邮箱校验及提交后的反馈,即使
npm run build成功,缺少邮箱校验仍应判为需求未完成。可按这些条件操作页面、检查实际结果,并把失败步骤、预期和实际表现交给生成环节修正,再重跑失败用例及相关回归。这类需求验收是进一步设计。发布判断应读取明确的构建和验收结果,避免用“模型已停止”“返回了一个目录”或“另一个模型说通过”替代它们。
参考资料
- 国内:zero2Agent:工程化实战——代码质量与测试:参考生成约束、独立检查和失败回归,解释构建可运行仍不足以证明业务需求满足。
- 国内:JavaGuide:单元测试到底是什么?应该怎么做?:采用局部测试、集成和端到端验证的分工,说明构建、代码测试与完整页面行为各能发现什么问题。
- 国内:AgentGuide:如何落地一个可写进简历的 Agent 项目:借鉴先定义成功标准、再记录预期和实际结果的做法,组织本文提出的页面需求验收。
- 国内:阿里云云效:Node.js 测试构建:参考代码扫描、单元测试与构建产物分别配置和报告的方式,解释质量检查不能合并为一个笼统的成功标志。
- 国外:GitHub:Review AI-generated code:采用功能检查、需求一致性和 AI 常见错误的审查方法,说明模型评价不能替代独立验证。
- 国外:Vite:Building for Production:依据生产构建生成可供静态托管的产物这一职责,区分打包、发布和业务验收;本项目实际命令与放行条件以构建器和部署服务为准。