图片来源网络,侵权联系删。

系列文章
文章目录
- [1. 从Web表单向导到AI工作流](#1. 从Web表单向导到AI工作流)
- [2. 多步骤确认流程设计(Web状态管理思维迁移)](#2. 多步骤确认流程设计(Web状态管理思维迁移))
-
- [2.1 对话状态机:从Vue Pinia到Agent状态图](#2.1 对话状态机:从Vue Pinia到Agent状态图)
- [2.2 关键设计模式:对话守卫](#2.2 关键设计模式:对话守卫)
- [3. 基于模板的内容生成技巧(Web模板引擎进化论)](#3. 基于模板的内容生成技巧(Web模板引擎进化论))
-
- [3.1 模板设计:从Thymeleaf到提示词工程](#3.1 模板设计:从Thymeleaf到提示词工程)
- [3.2 模板注入防御:XSS到Prompt注入](#3.2 模板注入防御:XSS到Prompt注入)
- [3.3 动态模板组合:微前端到Agent组合](#3.3 动态模板组合:微前端到Agent组合)
- [4. 状态管理与用户交互策略](#4. 状态管理与用户交互策略)
-
- [4.1 状态持久化:从Session到对话快照](#4.1 状态持久化:从Session到对话快照)
- [4.2 交互策略:WebSocket vs 轮询](#4.2 交互策略:WebSocket vs 轮询)
- [4.3 中断恢复:从页面刷新到对话续传](#4.3 中断恢复:从页面刷新到对话续传)
- [5. 实战:项目初始化向导Skill开发](#5. 实战:项目初始化向导Skill开发)
-
- [5.1 需求定义与架构设计](#5.1 需求定义与架构设计)
- [5.2 后端核心实现(Spring Boot 3.2)](#5.2 后端核心实现(Spring Boot 3.2))
- [5.3 前端交互实现(Vue3 + Element Plus)](#5.3 前端交互实现(Vue3 + Element Plus))
- [5.4 本地测试与部署](#5.4 本地测试与部署)
- [6. 常见问题与解决方案](#6. 常见问题与解决方案)
-
- [6.1 状态不一致:当Agent与前端状态不同步](#6.1 状态不一致:当Agent与前端状态不同步)
- [6.2 模板生成失败:LLM返回无效代码](#6.2 模板生成失败:LLM返回无效代码)
- [6.3 资源竞争:大模型占用过多内存](#6.3 资源竞争:大模型占用过多内存)
- [7. 总结与学习路径](#7. 总结与学习路径)
-
- [7.1 Web开发者转型心法](#7.1 Web开发者转型心法)
- [7.2 能力进阶路线图](#7.2 能力进阶路线图)

1. 从Web表单向导到AI工作流
在Web开发中,我们早已熟悉「多步骤表单」的设计模式:用户注册向导、电商结算流程、项目初始化配置...这些场景的核心是渐进式信息收集 与状态保持 。当Web开发者转型AI应用时,向导式Agent工作流正是这一模式的智能升级版------它将复杂任务拆解为可管理的步骤,通过自然语言交互引导用户完成目标,就像把传统表单的"下一步/上一步"按钮,进化为智能对话的上下文感知导航。
想象这个典型场景:
产品经理要求为内部工具添加"AI项目初始化"功能。传统方案需要设计5页配置表单,用户常因信息过载中途放弃;而采用向导式Agent,用户只需自然对话:"我想创建一个Spring Boot + Vue3的电商项目,需要用户认证和支付模块",Agent会像资深架构师一样逐步确认技术栈、目录结构、依赖配置,最终生成完整项目骨架。
本文将揭示Web工程化思维在高级Agent设计中的核心价值:
- 用状态机管理对话流程(类比Vue Router导航守卫)
- 用模板引擎生成代码(类比Thymeleaf动态渲染)
- 用WebSocket实现实时交互(类比SSE服务端推送)
- 用资源隔离保障稳定性(类比Docker容器限制)
让复杂AI工作流回归Web本质:可预测 > 可中断 > 可恢复。你不需要重学AI理论,只需把熟悉的Web模式迁移到智能层。

2. 多步骤确认流程设计(Web状态管理思维迁移)
2.1 对话状态机:从Vue Pinia到Agent状态图
在Web前端,我们用Pinia/Vuex管理表单状态:
javascript
// Vue3 + Pinia 示例(项目初始化向导)
const useProjectStore = defineStore('project', {
state: () => ({
step: 0, // 当前步骤
formData: {
projectName: '',
techStack: { backend: 'spring', frontend: 'vue' },
modules: ['auth', 'payment'],
confirm: false
},
history: [] // 操作历史
}),
actions: {
nextStep() {
if (this.validateCurrentStep()) this.step++;
},
prevStep() {
if (this.step > 0) this.step--;
},
validateCurrentStep() {
// 步骤校验逻辑
}
}
});
Agent状态机设计原则:
- 状态原子化:每个对话步骤对应一个状态(类比Vue组件)
- 转换条件化:用户输入触发状态迁移(类比事件总线)
- 历史可追溯:完整记录交互轨迹(类比Vuex时间旅行)
用户确认技术栈
用户选择模块
用户确认目录
用户最终确认
用户修改技术栈
TechSelection
ModuleConfig
DirStructure
Confirmation
Result
状态数据:
-
backend: string
-
frontend: string
-
database: string
2.2 关键设计模式:对话守卫
在Spring Boot中,我们用AOP实现API守卫:
java
// Web开发中的参数校验(类比对话守卫)
@Aspect
@Component
public class ValidationAspect {
@Before("@annotation(ValidStep)")
public void validateProjectStep(JoinPoint joinPoint) {
ProjectContext context = (ProjectContext) joinPoint.getArgs()[0];
if (context.getCurrentStep() == 1 &&
!isValidTechStack(context.getTechStack())) {
throw new InvalidStepException("技术栈配置无效");
}
}
}
向导式Agent守卫实现:
java
// skills/project-wizard/state/StepGuard.java
public class StepGuard {
private final Map<Integer, Predicate<WizardContext>> guards = Map.of(
1, ctx -> isValidTechStack(ctx.getTechStack()), // 技术栈校验
2, ctx -> !ctx.getModules().isEmpty(), // 模块选择校验
3, ctx -> ctx.getDirStructure() != null // 目录结构校验
);
public boolean canProceed(int currentStep, WizardContext context) {
return guards.getOrDefault(currentStep, ctx -> true).test(context);
}
private boolean isValidTechStack(TechStack stack) {
// 业务规则:Spring Boot需搭配特定数据库
return !(stack.backend().equals("spring") &&
!List.of("mysql", "postgresql").contains(stack.database()));
}
}
💡 核心洞见 :对话守卫 = API中间件。就像Spring Security过滤非法请求,StepGuard拦截无效对话状态,确保工作流始终处于有效上下文。
StepGuard StateMachine AgentController User StepGuard StateMachine AgentController User "我想创建电商项目" processInput(context) validateStep(0, context) true 返回技术栈选项 "请选择后端框架:1.Spring Boot 2.Node.js" "选1" processInput(context) validateStep(1, context) 技术栈有效 → true 返回模块选项 "需要哪些模块?1.用户认证 2.支付 3.商品管理"
3. 基于模板的内容生成技巧(Web模板引擎进化论)
3.1 模板设计:从Thymeleaf到提示词工程
在Web开发中,我们用Thymeleaf动态生成HTML:
html
<!-- 传统Web模板 (Thymeleaf) -->
<div th:each="module : ${modules}">
<h3 th:text="${module.name}"></h3>
<p th:text="${module.description}"></p>
<div th:if="${module.requiresAuth}">需要认证</div>
</div>
Agent提示词模板设计:
# 角色:资深全栈架构师
# 任务:生成{projectType}项目的{fileName}文件
# 约束:
- 严格使用{techStack}技术栈
- 模块依赖:{modules}
- 禁止假设未提供的配置
# 用户需求
{userRequirements}
# 目录结构上下文
{dirStructure}
# 生成规则
1. 如果包含{authModule},必须实现JWT认证
2. 支付模块需预留Stripe/PayPal接口
3. 遵循{techStack}最佳实践
# 输出格式
```{fileType}
// 自动生成,勿手动修改
{content}
```
3.2 模板注入防御:XSS到Prompt注入
Web开发经验:
java
// Spring Boot 防XSS
public String sanitizeInput(String input) {
return input.replaceAll("<script>", "<script>");
}
Agent安全实践:
java
// 提示词模板安全注入
public String renderPrompt(TemplateContext context) {
// 1. 消毒用户输入
String safeRequirements = PromptSanitizer.sanitize(context.userRequirements());
// 2. 限制上下文长度
String truncatedStructure = context.dirStructure().substring(0, 1000);
// 3. 使用参数化模板(避免字符串拼接)
return templateEngine.render("project_file_template", Map.of(
"projectType", context.projectType(),
"techStack", context.techStack(),
"modules", safeJoin(context.modules()), // 安全拼接
"userRequirements", safeRequirements,
"dirStructure", truncatedStructure
));
}
// 模拟Thymeleaf的模板引擎
@Component
public class PromptTemplateEngine {
private final Jinjava jinjava = new Jinjava();
public String render(String templateName, Map<String, Object> context) {
// 1. 从资源加载模板
String template = resourceLoader.getResource(
"classpath:templates/" + templateName + ".jinja"
).getContentAsString(StandardCharsets.UTF_8);
// 2. 应用安全策略
context.put("sanitize", (Function<String, String>) PromptSanitizer::sanitize);
// 3. 渲染(自动转义)
return jinjava.render(template, context);
}
}
3.3 动态模板组合:微前端到Agent组合
Web架构类比:
javascript
// Vue3微前端动态加载
const loadModule = async (moduleName) => {
if (moduleName === 'payment') {
return import('@/modules/payment/Payment.vue');
}
// ...其他模块
};
Agent模板组合策略:
java
// skills/project-wizard/template/TemplateComposer.java
public class TemplateComposer {
private final Map<String, String> moduleTemplates = Map.of(
"auth", "templates/auth_module.jinja",
"payment", "templates/payment_module.jinja",
"product", "templates/product_module.jinja"
);
public String composeProjectTemplate(List<String> selectedModules) {
// 1. 基础模板
StringBuilder fullTemplate = new StringBuilder(
resourceLoader.load("templates/base_project.jinja")
);
// 2. 动态插入模块模板(类比微前端注册)
selectedModules.forEach(module -> {
if (moduleTemplates.containsKey(module)) {
fullTemplate.append("\n\n")
.append("<!-- 模块: ").append(module).append(" -->\n")
.append(resourceLoader.load(moduleTemplates.get(module)));
}
});
return fullTemplate.toString();
}
}
🔐 安全黄金法则 :提示词 = 可执行代码。就像绝不信任前端输入,永远消毒用户数据、限制上下文长度、使用参数化模板------任何LLM输入都应视为潜在攻击向量。

4. 状态管理与用户交互策略
4.1 状态持久化:从Session到对话快照
Web开发方案:
java
// Spring Session 管理
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
Agent状态持久化:
java
// skills/project-wizard/state/WizardStateRepository.java
@Repository
public class WizardStateRepository {
// 1. 两级缓存(内存+Redis)
private final Cache<String, WizardContext> memoryCache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
private final RedisTemplate<String, WizardContext> redisTemplate;
public void saveState(String sessionId, WizardContext context) {
// 2. 序列化敏感数据
WizardContext safeContext = context.toBuilder()
.accessToken(null) // 移除敏感信息
.build();
// 3. 写入两级缓存
memoryCache.put(sessionId, safeContext);
redisTemplate.opsForValue().set(
"wizard:" + sessionId,
safeContext,
Duration.ofMinutes(30)
);
}
public WizardContext loadState(String sessionId) {
// 4. 优先从内存加载
WizardContext context = memoryCache.getIfPresent(sessionId);
if (context == null) {
context = redisTemplate.opsForValue().get("wizard:" + sessionId);
if (context != null) memoryCache.put(sessionId, context);
}
return context;
}
}
4.2 交互策略:WebSocket vs 轮询
技术选型对比:
| 交互方式 | 延迟 | 服务器压力 | 适用场景 | Web类比 |
|---|---|---|---|---|
| WebSocket | <100ms | 高 | 实时预览、大文件生成 | 聊天应用 |
| SSE | ~500ms | 中 | 进度通知、日志流 | 构建日志 |
| 轮询 | 2-5s | 低 | 简单确认、小任务 | 传统AJAX |
WebSocket实现(Spring Boot + Vue3):
java
// 后端:WebSocket状态推送
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/wizard-ws")
.setAllowedOriginPatterns("*")
.withSockJS(); // 降级支持
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
// 状态变更时推送
@MessageMapping("/project/wizard")
@SendTo("/topic/wizard/{sessionId}")
public WizardUpdate handleInput(@DestinationVariable String sessionId,
@Payload UserInput input) {
WizardContext context = stateService.processInput(sessionId, input.text());
return new WizardUpdate(context.getCurrentStep(), context.getPreview());
}
vue
<!-- 前端:Vue3 + WebSocket响应式处理 -->
<script setup>
import { ref, onMounted } from 'vue';
const ws = ref(null);
const currentStep = ref(0);
const previewContent = ref('');
onMounted(() => {
// 1. 建立连接(自动降级到SockJS)
ws.value = new SockJS('/wizard-ws');
const stompClient = Stomp.over(ws.value);
// 2. 订阅状态更新
stompClient.connect({}, () => {
stompClient.subscribe(`/topic/wizard/${sessionId}`, (message) => {
const update = JSON.parse(message.body);
currentStep.value = update.step;
previewContent.value = update.preview; // 响应式更新预览
});
});
// 3. 发送用户输入
const sendInput = (text) => {
stompClient.send('/app/project/wizard', {}, JSON.stringify({ text }));
};
// 暴露方法
defineExpose({ sendInput });
});
</script>
4.3 中断恢复:从页面刷新到对话续传
Web开发痛点 :
用户填写5页表单时意外刷新页面,所有数据丢失...
Agent优雅恢复方案:
java
// skills/project-wizard/recovery/RecoveryService.java
@Service
public class RecoveryService {
public RecoveryOption checkRecovery(String userId) {
// 1. 检查未完成会话
List<WizardSession> incompleteSessions = sessionRepo
.findByUserIdAndStatus(userId, SessionStatus.IN_PROGRESS)
.stream()
.filter(s -> Duration.between(s.lastActive(), Instant.now()).toMinutes() < 30)
.collect(Collectors.toList());
if (incompleteSessions.isEmpty()) return RecoveryOption.NONE;
// 2. 生成恢复选项
return new RecoveryOption(
incompleteSessions.get(0).getSessionId(),
incompleteSessions.get(0).getProjectName(),
incompleteSessions.get(0).getCurrentStep()
);
}
public WizardContext resumeSession(String sessionId) {
// 3. 附加恢复上下文
WizardContext context = stateRepo.load(sessionId);
context.setRecovered(true);
context.setRecoveryMessage("检测到上次未完成的项目配置,是否继续?");
return context;
}
}
// 前端展示恢复选项
<div v-if="recoveryOption" class="recovery-banner">
<el-alert type="info" :closable="false">
<template #title>
<div class="flex justify-between">
<span>发现未完成的「{{ recoveryOption.projectName }}」项目</span>
<div>
<el-button type="primary" @click="resumeSession">继续配置</el-button>
<el-button @click="discardSession">重新开始</el-button>
</div>
</div>
</template>
</el-alert>
</div>
♻️ 恢复设计原则 :状态快照 + 意图识别。就像Web表单的localStorage自动保存,Agent在每次状态变更时创建快照,结合NLU识别用户"继续上次操作"等意图,实现无感恢复。

5. 实战:项目初始化向导Skill开发
5.1 需求定义与架构设计
业务场景 :
内部开发者平台需要智能项目生成器,支持:
- 5步向导流程(技术栈→模块→目录→配置→确认)
- 实时代码预览
- 中断后30分钟内可恢复
- 生成标准Maven/Gradle项目
技术架构:
WebSocket
持久化
加载
调用
返回
输出
Vue3前端
Spring Boot Agent
状态机引擎
模板渲染服务
LLM服务
Redis 状态存储
templates/ 目录
百炼平台
生成的代码
Zip项目包
5.2 后端核心实现(Spring Boot 3.2)
1. 状态机配置:
java
// config/WizardStateMachineConfig.java
@Configuration
@EnableStateMachineFactory
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
states
.withStates()
.initial(States.TECH_SELECTION)
.states(EnumSet.allOf(States.class))
.end(States.COMPLETE);
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.TECH_SELECTION).target(States.MODULE_CONFIG)
.event(Events.SELECT_TECH)
.guard(techGuard())
.and()
.withExternal()
.source(States.MODULE_CONFIG).target(States.DIR_STRUCTURE)
.event(Events.SELECT_MODULES)
.and()
.withExternal()
.source(States.DIR_STRUCTURE).target(States.CONFIRMATION)
.event(Events.CONFIRM_STRUCTURE)
.and()
.withExternal()
.source(States.CONFIRMATION).target(States.COMPLETE)
.event(Events.CONFIRM_PROJECT);
}
@Bean
public Guard<States, Events> techGuard() {
return context -> {
WizardContext ctx = context.getExtendedState().get("context", WizardContext.class);
return stepGuard.canProceed(1, ctx); // 复用守卫逻辑
};
}
}
2. 模板渲染服务:
java
// service/TemplateRenderService.java
@Service
@RequiredArgsConstructor
public class TemplateRenderService {
private final PromptTemplateEngine templateEngine;
private final LlmService llmService;
public String generateFileContent(WizardContext context, String filePath) {
// 1. 根据文件路径确定模板
String templateName = getTemplateForFile(filePath);
// 2. 渲染提示词
String prompt = templateEngine.render(templateName, Map.of(
"techStack", context.getTechStack(),
"modules", context.getModules(),
"dirStructure", context.getDirStructure(),
"userRequirements", context.getRequirements()
));
// 3. 调用LLM(带重试和熔断)
return llmService.generateWithRetry(prompt, 3, () ->
defaultFallbackContent(filePath, context)
);
}
private String defaultFallbackContent(String filePath, WizardContext context) {
// 4. 降级策略:返回基础模板
if (filePath.endsWith("pom.xml")) {
return defaultPomTemplate(context.getProjectName());
}
return "// 生成失败,请重试";
}
}
3. 项目生成控制器:
java
// controller/ProjectWizardController.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/skills/project-wizard")
public class ProjectWizardController {
private final StateMachineService stateMachineService;
private final TemplateRenderService templateService;
@PostMapping("/input")
public ResponseEntity<WizardResponse> processInput(
@RequestHeader("X-Session-ID") String sessionId,
@RequestBody UserInput input
) {
// 1. 加载上下文
WizardContext context = stateMachineService.loadContext(sessionId);
// 2. 处理输入(状态机驱动)
WizardContext updatedContext = stateMachineService.processInput(context, input.text());
// 3. 生成预览(仅在目录结构步骤后)
String preview = null;
if (updatedContext.getCurrentStep() >= 3) {
preview = templateService.generatePreview(updatedContext);
}
// 4. 保存状态
stateMachineService.saveContext(sessionId, updatedContext);
return ResponseEntity.ok(new WizardResponse(
updatedContext.getCurrentStep(),
getStepPrompt(updatedContext.getCurrentStep()),
preview,
updatedContext.isComplete()
));
}
@GetMapping("/download")
public void downloadProject(
@RequestHeader("X-Session-ID") String sessionId,
HttpServletResponse response
) throws IOException {
// 5. 生成完整项目
ProjectArchive archive = projectGenerator.generateFullProject(sessionId);
// 6. 流式下载(避免大内存占用)
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=" + archive.getFileName());
try (OutputStream out = response.getOutputStream()) {
archive.writeTo(out);
out.flush();
}
}
}
5.3 前端交互实现(Vue3 + Element Plus)
html
<!-- views/ProjectWizard.vue -->
<template>
<div class="wizard-container">
<!-- 恢复提示 -->
<RecoveryBanner v-if="recoveryOption" @resume="resumeSession" @discard="startNew" />
<el-steps :active="currentStep" finish-status="success" class="step-bar">
<el-step title="技术栈" description="选择框架组合" />
<el-step title="功能模块" description="勾选所需功能" />
<el-step title="目录结构" description="确认项目布局" />
<el-step title="最终确认" description="检查配置细节" />
</el-steps>
<div class="step-content">
<!-- 动态步骤组件 -->
<component :is="currentStepComponent"
:context="wizardContext"
@next="handleNext"
@prev="handlePrev"
@select="handleSelect" />
<!-- 实时预览面板 -->
<el-aside v-if="previewContent" class="preview-panel">
<el-tabs v-model="activePreview">
<el-tab-pane label="目录结构" name="structure">
<DirectoryTree :structure="wizardContext.dirStructure" />
</el-tab-pane>
<el-tab-pane label="关键文件" name="files">
<CodePreview :content="previewContent" :language="previewLanguage" />
</el-tab-pane>
</el-tabs>
</el-aside>
</div>
<div class="action-footer">
<el-button v-if="currentStep > 0" @click="handlePrev">上一步</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">
{{ currentStep === 3 ? '生成项目' : '下一步' }}
</el-button>
<el-button @click="saveDraft">保存草稿</el-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import TechSelection from './steps/TechSelection.vue';
import ModuleConfig from './steps/ModuleConfig.vue';
// ...其他步骤组件
const router = useRouter();
const sessionId = ref(localStorage.getItem('wizard_session') || generateSessionId());
const currentStep = ref(0);
const wizardContext = ref(initialContext());
const previewContent = ref('');
const loading = ref(false);
const wsClient = ref(null);
const recoveryOption = ref(null);
// 动态组件映射
const stepComponents = [TechSelection, ModuleConfig, DirStructure, Confirmation];
const currentStepComponent = computed(() => stepComponents[currentStep.value]);
// WebSocket初始化
const initWebSocket = () => {
wsClient.value = new SockJS('/wizard-ws');
const stompClient = Stomp.over(wsClient.value);
stompClient.connect({}, () => {
// 订阅状态更新
stompClient.subscribe(`/topic/wizard/${sessionId.value}`, (message) => {
const update = JSON.parse(message.body);
currentStep.value = update.currentStep;
previewContent.value = update.preview;
wizardContext.value = update.context;
});
// 恢复会话
if (recoveryOption.value) {
stompClient.send('/app/project/resume', {}, JSON.stringify({
sessionId: recoveryOption.value.sessionId
}));
}
});
};
// 用户输入处理
const handleSubmit = async () => {
loading.value = true;
try {
const userInput = getCurrentStepInput(); // 从子组件获取输入
// 通过WebSocket发送(保持连接状态)
wsClient.value.send('/app/project/input', {}, JSON.stringify({
sessionId: sessionId.value,
text: userInput
}));
// 本地状态同步(优化体验)
if (currentStep.value < 3) {
currentStep.value++;
}
} catch (error) {
ElMessage.error('操作失败: ' + error.message);
} finally {
loading.value = false;
}
};
// 生成项目
const generateProject = async () => {
const response = await fetch(`/skills/project-wizard/download`, {
headers: { 'X-Session-ID': sessionId.value }
});
// 流式下载
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = wizardContext.value.projectName + '.zip';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
};
onMounted(async () => {
// 1. 检查恢复选项
recoveryOption.value = await checkRecovery();
// 2. 初始化WebSocket
initWebSocket();
// 3. 保存会话ID
localStorage.setItem('wizard_session', sessionId.value);
});
onUnmounted(() => {
wsClient.value?.disconnect();
localStorage.removeItem('wizard_session');
});
</script>
5.4 本地测试与部署
1. 快速启动:
bash
# 后端
skill-creator serve project-wizard --profile dev
# 前端
cd frontend
VUE_APP_API_BASE=http://localhost:8080 npm run dev
2. 端到端测试:
bash
# 测试向导流程
skill-creator test project-wizard \
--scenario "创建Spring Boot+Vue3电商项目" \
--input "技术栈:spring+vue,模块:auth+payment"
# 验证输出
skill-creator verify project-wizard \
--output_contains "src/main/java/com/example/auth" \
--output_contains "package.json"
3. 生产部署:
yaml
# docker-compose.yml
services:
wizard-backend:
image: project-wizard:1.0
ports:
- "8080:8080"
environment:
- SPRING_REDIS_HOST=redis
- LLAMA_API_KEY=${LLAMA_API_KEY}
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M # 限制LLM内存占用
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
redis-data:
🚀 部署关键点 :资源隔离 + 弹性伸缩。就像Web服务配置Tomcat线程池,为LLM任务设置独立资源限额;使用K8s HPA根据WebSocket连接数自动扩缩容,避免大模型请求阻塞常规API。

6. 常见问题与解决方案
6.1 状态不一致:当Agent与前端状态不同步
症状 :
用户点击"下一步"后,前端显示步骤2,但Agent返回步骤1的内容
根因分析:
- WebSocket消息乱序(网络延迟)
- 前端本地状态与服务端状态不同步
解决方案:
java
// 后端:添加序列号防乱序
public record WizardUpdate(
long sequenceId, // 唯一递增ID
int currentStep,
String preview
) {}
// 前端:丢弃旧消息
const lastSequenceId = ref(0);
stompClient.subscribe(`/topic/wizard/${sessionId}`, (message) => {
const update = JSON.parse(message.body);
if (update.sequenceId <= lastSequenceId.value) return; // 丢弃旧消息
lastSequenceId.value = update.sequenceId;
// 更新状态
});
6.2 模板生成失败:LLM返回无效代码
防御策略(三层保障):
java
public String generateWithRetry(String prompt, int maxRetries, Supplier<String> fallback) {
int attempt = 0;
while (attempt < maxRetries) {
try {
String content = llmService.call(prompt);
// 1. 语法校验(类比前端表单校验)
if (content.contains("```java") && !isValidJava(content)) {
throw new InvalidCodeException("Java语法错误");
}
// 2. 安全校验(防恶意代码)
if (containsDangerousPattern(content)) {
throw new SecurityException("检测到危险模式");
}
return content; // 有效内容
} catch (Exception e) {
attempt++;
log.warn("生成失败, 尝试 {}/{}", attempt, maxRetries, e);
}
}
// 3. 降级策略(返回基础模板)
return fallback.get();
}
// 语法校验工具(复用Web技术栈)
private boolean isValidJava(String code) {
// 使用ECJ编译器校验(轻量级)
CompilerOptions options = new CompilerOptions();
BatchCompiler.compile(code, null, null, null, options);
return options.problemList.isEmpty();
}
6.3 资源竞争:大模型占用过多内存
监控指标:
yaml
# prometheus-metrics.yml
metrics:
- name: llm_processing_time
type: histogram
help: "LLM生成耗时(毫秒)"
- name: active_wizard_sessions
type: gauge
help: "活跃向导会话数"
- name: llm_memory_usage
type: gauge
help: "LLM进程内存占用(MB)"
动态降级方案:
java
// 服务注册中心监听
@EventListener
public void onInstanceStatusChange(InstanceEvent event) {
if (event.isOverloaded()) {
// 1. 降低生成质量
llmConfig.setTemperature(0.3); // 从0.7降为0.3
// 2. 缩短上下文
llmConfig.setMaxTokens(500); // 从2000降为500
// 3. 启用队列
enableRequestQueue();
}
}
// 前端友好提示
资源优化对比:
| 策略 | 内存占用 | 生成质量 | 适用场景 |
|---|---|---|---|
| 全量上下文 | 1.8GB | ★★★★★ | 首次生成关键文件 |
| 截断上下文(500token) | 600MB | ★★★☆☆ | 预览/次要文件 |
| 降级模型 | 300MB | ★★☆☆☆ | 高峰期兜底 |
⚖️ 资源平衡法则 :质量弹性 > 资源刚性。就像Web服务在高负载时降级图片质量,Agent应在资源紧张时动态调整生成质量,而非直接拒绝服务。

7. 总结与学习路径
7.1 Web开发者转型心法
| 传统Web能力 | Agent高级模式增强 | 迁移关键点 |
|---|---|---|
| 表单状态管理 | 多轮对话状态机 | 用状态图替代if-else链 |
| 模板渲染 | 动态提示词组合 | 安全消毒 > 美观排版 |
| WebSocket通信 | 实时Agent交互 | 消息序列化 > 二进制优化 |
| 服务降级 | LLM质量弹性 | 体验连续性 > 绝对质量 |
7.2 能力进阶路线图
LLM内存/并发控制 Web服务+Agent协同 工作流编排 微调领域模型 掌握Spring StateMachine 模板设计与安全 基础巩固(2周) 基础巩固(2周) 掌握Spring StateMachine 状态机原理 状态机原理 模板设计与安全 提示词工程 提示词工程 能力突破(3-4周) 能力突破(3-4周) LLM内存/并发控制 资源优化 资源优化 Web服务+Agent协同 混合架构 混合架构 高级实战(4周+) 高级实战(4周+) 工作流编排 多Agent协作 多Agent协作 微调领域模型 领域定制 领域定制 Web开发者Agent能力成长路径
🌟 终极心法 :Agent不是替代者,而是增强者。你的核心价值从来不是写提示词,而是:
- 用状态机驯服不确定性
- 用模板引擎保障一致性
- 用资源隔离守护稳定性
从这个项目初始化向导开始,你已掌握构建企业级Agent工作流的钥匙。下一站:将多个向导式Skill串联为智能DevOps流水线,让AI真正成为Web开发者的"智能副驾驶"。
