山东大学创新实训——群面智伴5(优化报告导出、新增工具调用、解决当前项目的部分问题)
一、项目背景
前面的群面系统已经具备了房间创建、AI 候选人自动发言、阶段流转、和 AI 报告与导出的能力。但是当前流程存在几个问题:
1.导出 AI 报告时前端长时间无反馈,由于模拟过程对话过长,导致报告生成 Agent 容易因为上下文截断、工具参数过大或外部知识不足导致结果不稳定
2.用户进入房间后不知道当前讨论题目是什么
3.想插入自己的观点时会和 AI 候选人抢话
4.Agent 回复中的动作描写会被 TTS 生硬朗读
因此本轮工作分为两条线:
-
重构报告生成 Agent 的数据输入、执行流程和工具能力,让复盘报告不再只是能生成,而是更快、更稳、有更充实的外部知识辅助ai生成。
-
补齐四个影响真实使用体验的问题,让房间页和复盘页可以顺畅跑完
二、本轮项目进度概览
本轮主要完成了五个方向的升级:
|
模块 |
改造内容 |
技术价值 |
|---|---|---|
|
房间题目引导 |
新增房间信息聚合接口,前端展示题目、阶段、时长,并用 sessionStorage 兜底 |
解决刷新/直达房间页上下文丢失问题 |
|
用户请求发言 |
引入 |
解决用户发言与 AI Agent 并发冲突 |
|
TTS 文本清洗 |
前后端统一剔除括号动作描写,仅朗读语义内容 |
保留展示原文,同时提升语音体验 |
|
AI 报告导出进度 |
新增 SSE 流式进度、心跳、前端悬浮日志 |
解决长任务“假死”和导出状态不可见 |
|
报告生成 Agent |
构建 InferencePack 压缩上下文,接入 ReAct 工具流、容错导出和 Tavily 全网搜索 |
提升报告证据覆盖、稳定性和分析深度 |
当前代码中的报告链路如下:
用户点击导出 AI 分析报告
↓
ReportManusController /generate/stream 创建 SSE
↓
ReportManusService 写结构化底稿
↓
RoomInferencePackBuilder 构建推理包
↓
ReportManusAgent 多轮 ReAct
├─ 分析 inferencePack
├─ 调用 searchGroupInterviewKnowledge 搜索群面知识
├─ exportMarkdown 容错写入 Markdown
└─ exportPdf / doTerminate
↓
失败时 DirectReportGenerator 单次 LLM 备用
↓
必要时本地模板降级
当前代码已经从功能可用推进到Agent 工程化可控:有上下文压缩、有工具调用约束、有进度可观测、有失败降级。
三、报告生成 Agent 优化一:从原始事件流到分层压缩
3.1 当前项目存在什么问题
原来的报告生成直接把事件流整体喂给 Agent,采取的做法是加载最近八十条事件。
这样有两个问题:
一是早期破冰、观点提出和中段冲突等早期的事件容易因模拟事件流过长而丢失
二是如果把全部发言原文直接塞进 prompt,上下文过长又会造成模型处理缓慢,影响速度和稳定性
3.2 原因是什么
群面数据不是普通聊天记录,它同时需要全局时间线、阶段节奏、关键发言、个人画像和评分信息。简单截断会丢全局,简单全量输入会严重浪费上下文窗口。
3.3 如何处理
我采用的方法是引入InferencePack推理包,把全量事件压缩成多层结构:
|
层次 |
内容 |
作用 |
|---|---|---|
|
|
所有事件的 seq、时间、阶段、发言人、类型、100 字预览 |
保证模型知道每条事件存在 |
|
|
最多 80 条高信息密度事件的完整正文 |
给模型提供可引用证据 |
|
|
每阶段事件数、发言人数、活跃度摘要 |
让模型理解讨论节奏 |
|
|
每个发言人的总字数、首尾/最长发言、highlightSeqs |
支持按人写评价 |
|
|
五维评估或 |
防止模型编造评分 |
通过这样的分层压缩,成功的把原始事件流变成“可推理的数据结构”。
3.4 关键代码
InferencePack 的字段如下,体现了当前的上下文压缩策略:
public class InferencePack {
private int totalEventCount;
private RoomMeta meta;
private List<TimelineEntry> timeline;
private List<EventDetail> highlights;
private List<PhaseDigest> phaseDigests;
private List<SpeakerDigest> speakerDigests;
private Object evaluation;
}
构建入口先计算高亮索引,再复用这个索引填充 highlights 和 speakerDigest:
Set<Integer> selectedIdxSet = computeSelectedIndexes(events);
InferencePack pack = InferencePack.builder()
.roomId(roomId)
.reportType(reportType)
.totalEventCount(events.size())
.meta(buildMeta(ctx, profiles))
.timeline(buildTimeline(events))
.highlights(buildHighlightsFromIndexes(events, selectedIdxSet))
.phaseDigests(buildPhaseDigests(events))
.speakerDigests(buildSpeakerDigests(events, profiles, selectedIdxSet))
.evaluation(buildEvaluationObject(evaluation))
.build();
高亮选择规则不是随机抽样,而是有优先级:
// P1-A:每阶段前 N + 后 N 条 SPEAK 事件
Map<String, List<Integer>> phaseSpeakIndexes = new LinkedHashMap<>();
for (int i = 0; i < events.size(); i++) {
DiscussionEvent e = events.get(i);
if (!"SPEAK".equalsIgnoreCase(e.getEventType())) continue;
String phase = e.getPhase() == null ? "" : e.getPhase();
phaseSpeakIndexes.computeIfAbsent(phase, k -> new ArrayList<>()).add(i);
}
// P1-B:关键互动事件
if (KEY_EVENT_TYPES.contains(e.getEventType().toUpperCase(Locale.ROOT))) {
selected.add(i);
}
// P1-C:长发言,P2:每人至少一条,P3:剩余按长度补足
其中一个关键修复是:阶段首尾高亮只选 SPEAK,避免 ROOM_START、PHASE_CHANGE 这种系统事件占掉高亮名额。
另一个关键修复是 highlightSeqs,让模型在写个人评价时能快速定位该用户的关键证据:
List<Integer> highlightSeqs = idxList.stream()
.filter(selectedIdxSet::contains)
.map(idx -> idx + 1)
.collect(Collectors.toList());
四、报告生成 Agent 优化二:ReAct 主路径与工具调用约束
4.1 存在什么问题
当前报告生成 Agent 最大的不稳定点来自工具调用:模型可能只输出文本不调工具;可能在没有搜索外部知识前直接导出;可能重复读取事件流;也可能提前调用 doTerminate,导致接口看似执行完成但没有生成文件。
4.2 原因是什么
Tool Calling 不是传统函数调用,它依赖模型遵守系统提示。只靠 prompt 约束不够,工程侧还需要显式状态机、工具调用历史检查和纠偏消息。
4.3 如何处理
当前 ReportManusAgent 被设计成多轮 ReAct Agent,maxSteps=6,明确要求:
-
先分析预加载的
inferencePack。 -
至少调用一次
searchGroupInterviewKnowledge。 -
只有
exportFormat=pdf时才调用exportPdf。 -
最后调用
doTerminate。
在 ToolCallManusAgent 中,我加入了工具调用历史检查、重复工具阻断、缺失工具纠偏和导出前知识搜索校验。
4.4 关键代码
Agent 系统提示将报告生成拆成强制多步工作流:
setSystemPrompt("""
You are ReportManus, an expert group-interview replay analyst and coach.
You work as a multi-step ReAct agent with tools — NOT a single-shot completion.
Step 1 — Analyze: Review PreloadedData.
Step 2 — Search: Call searchGroupInterviewKnowledge at least ONCE.
Step 4 — Export: Call exportMarkdown with the FULL Chinese report.
Step 5 — Finish: Call exportPdf ONLY if exportFormat=pdf, then call doTerminate.
Do NOT call exportMarkdown before completing Step 2.
""");
如果模型想在未搜索时导出,代码侧直接阻断:
private boolean shouldBlockExportWithoutKnowledgeSearch(List<AssistantMessage.ToolCall> toolCalls) {
boolean wantsExport = toolCalls.stream()
.anyMatch(call -> markdownToolName.equals(call.name()) || pdfToolName.equals(call.name()));
return wantsExport && !hasToolCallInHistory(SEARCH_KNOWLEDGE_TOOL);
}
如果模型一直重复读事件或评分,也会被阻断,防止 ReAct 空转:
private boolean shouldBlockRepeatedReadTools(List<AssistantMessage.ToolCall> toolCalls) {
long eventCalls = countToolCallInHistory(LOAD_EVENT_STREAM_TOOL);
long evalCalls = countToolCallInHistory(LOAD_EVALUATION_TOOL);
boolean evalReady = isEvaluationReadyInHistory();
boolean onlyReadTools = toolCalls.stream()
.map(AssistantMessage.ToolCall::name)
.allMatch(name -> LOAD_ROOM_CONTEXT_TOOL.equals(name)
|| LOAD_EVENT_STREAM_TOOL.equals(name)
|| LOAD_EVALUATION_TOOL.equals(name));
if (askEventAgain && eventCalls >= MAX_EVENT_STREAM_CALLS) {
return true;
}
return askEvalAgain && !evalReady && evalCalls >= MAX_EVALUATION_CALLS_WHEN_NOT_READY;
}
终止工具也不是无条件生效,必须确认导出已完成:
boolean requirePdf = isPdfRequested();
boolean hasMarkdownExport = hasToolCallInHistory(markdownToolName);
boolean hasPdfExport = hasToolCallInHistory(pdfToolName);
boolean exportSatisfied = requirePdf ? (hasMarkdownExport && hasPdfExport) : hasMarkdownExport;
if (exportSatisfied) {
setState(ManusAgentState.FINISHED);
} else {
getMessageList().add(new UserMessage(
"Do not terminate yet. You must export markdown first, then call doTerminate."));
}
这说明本轮对 Agent 的处理已经不是“写一个 prompt 让它做事”,而是围绕 Agent 行为建立了工程约束。
五、报告生成 Agent 优化四:Tavily 全网搜索工具
5.1 存在什么问题
复盘报告不能只复述本场发言,还应该结合群面评审标准、无领导小组讨论技巧、领导力/协作/表达等通用框架给出建议。
纯 prompt 内置知识可以覆盖一部分,但对于“沉默者如何改进”“过度主导如何收敛”“群面领导力评分标准”等问题,外部知识搜索能让建议更有依据。
5.2 原因是什么
报告生成 Agent 的事实依据必须来自本场 inferencePack,但建议和 coaching 框架可以参考外部最佳实践。原链路没有全网搜索工具,模型只能依赖内置知识。
5.3 如何处理
我新增了 GroupInterviewKnowledgeTool,通过 Tavily Search API 检索群面相关知识,并把它注册进 reportManusTools。当前 ReAct 主路径要求至少调用一次 searchGroupInterviewKnowledge,同时工具描述中明确约束:不得用它查询本次房间数据,本场事实只能来自 inferencePack。
同时,工具在没有配置 API Key 或请求失败时不会抛异常打断报告生成,而是返回降级提示,让 Agent 继续基于内置框架和本场数据完成报告。
5.4 关键代码
工具定义明确区分“外部知识”和“本场事实”:
@Tool(description = "搜索群面(Group Interview / 无领导小组讨论)相关知识,"
+ "包括评审标准、最佳实践、常见失分点、MBTI 角色表现、STAR 法则、领导力技巧等。"
+ "仅在需要引用外部知识来支撑对用户的建议时调用。"
+ "不得用于查询本次面试房间的数据(请使用 inferencePack 数据)。")
public String searchGroupInterviewKnowledge(
@ToolParam(description = "搜索关键词,中文或英文均可。")
String query) {
if (tavilyApiKey == null || tavilyApiKey.isBlank()) {
return buildNoKeyResponse(query);
}
// POST https://api.tavily.com/search
}
请求 Tavily 时只取必要结果,并限制每条内容长度,避免搜索结果反向膨胀上下文:
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("api_key", tavilyApiKey);
requestBody.put("query", query);
requestBody.put("search_depth", "basic");
requestBody.put("include_answer", true);
requestBody.put("max_results", MAX_RESULTS);
解析返回时保留摘要答案、标题、内容摘要和来源:
JsonNode answerNode = root.get("answer");
if (answerNode != null && !answerNode.isNull() && !answerNode.asText().isBlank()) {
sb.append("**摘要答案**:").append(answerNode.asText()).append("\n\n");
}
JsonNode results = root.get("results");
for (JsonNode result : results) {
String title = getTextSafe(result, "title");
String content = getTextSafe(result, "content");
String url = getTextSafe(result, "url");
if (content.length() > MAX_CONTENT_LEN) {
content = content.substring(0, MAX_CONTENT_LEN) + "…";
}
}
工具注册到报告 Agent 的工具集:
@Bean(name = "reportManusTools")
public ToolCallback[] reportManusTools(
MarkdownExportTool markdownExportTool,
PdfExportTool pdfExportTool,
TerminateReportTool terminateReportTool,
GroupInterviewKnowledgeTool groupInterviewKnowledgeTool
) {
return ToolCallbacks.from(
markdownExportTool,
pdfExportTool,
terminateReportTool,
groupInterviewKnowledgeTool
);
}
外部搜索不是直接替代本场数据,而是作为“建议框架”的补充来源;事实归事实,知识归知识,避免报告出现看似专业但脱离本场证据的问题。
六、问题一:房间内缺少题目引导
6.1 存在什么问题
用户从首页创建房间后,选择大方向题目却不清楚具体讨论问题的方向,进入讨论页后只能看到当前阶段和倒计时,看不到完整题目、题目类型、讨论时长等信息。如果刷新页面或直接打开房间 URL,前端也没有稳定的数据来源恢复题目内容。
这个问题会直接影响群面体验:用户不知道讨论背景,只能看 AI 候选人发言推测题目。
6.2 原因是什么
原有房间页依赖创建房间时的页面状态,但房间页本身没有一个“按 roomId 拉取完整房间上下文”的接口。后端虽然在 RoomContext 中保存了 config、questionContent 和 currentPhase,但没有暴露给前端。
6.3 如何处理
我新增了只读聚合接口 GET /api/room/{roomId}/info,从 RoomContext 和 QuestionRepository 汇总房间状态、当前阶段、题目元数据、难度、人数和时长。前端 RoomView 挂载时主动调用该接口,渲染可折叠题目面板;如果接口失败,再读取创建房间时写入的 sessionStorage 作为兜底。
6.4 关键代码
后端接口不是简单返回题目字符串,而是把房间运行态和题库元数据统一打包:
@GetMapping("/room/{roomId}/info")
public Map<String, Object> getRoomInfo(@PathVariable String roomId) {
var context = roomService.getRoomContext(roomId);
if (context == null) {
return Map.of("success", false, "message", "房间不存在");
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("roomId", roomId);
response.put("status", context.getStatus());
if (context.getCurrentPhase() != null) {
response.put("phase", context.getCurrentPhase().name());
response.put("phaseName", context.getCurrentPhase().getName());
}
if (context.getConfig() != null) {
var config = context.getConfig();
response.put("difficulty", config.getDifficulty());
response.put("agentCount", config.getAgentCount());
response.put("duration", config.getDuration());
response.put("targetPosition", config.getTargetPosition());
// 根据 questionId 查询完整题目信息
}
return response;
}
前端房间页则把题目作为房间运行上下文展示,而不是只依赖首页传参:
async function loadRoomInfo() {
const info = await getRoomInfo(roomId)
if (info.success) {
roomInfo.value = {
question: info.question || null,
phaseName: info.phaseName || '',
duration: info.duration || null
}
if (info.phaseName) {
currentPhase.value = info.phaseName
}
return
}
const cached = sessionStorage.getItem(`roomQuestion:${roomId}`)
if (cached) {
roomInfo.value = { question: JSON.parse(cached), phaseName: '', duration: null }
}
}
七、问题二:用户发言与 AI Agent 并发冲突
7.1 存在什么问题
用户想参与讨论时,原来只能直接输入或打断。当前的用户请求发言功能并没有实现,这样会出现两个问题:
一是用户发言和 AI 候选人自动发言可能同时发生,消息顺序混乱;二是前端没有明确状态告诉用户“现在是否轮到你说话”,导致交互像普通聊天室,而不是群面里的发言权流转。
7.2 原因是什么
原有 Agent 循环只根据候选人的发言分数选择下一位发言者,没有“用户独占回合”这个状态。WebSocket 也缺少请求发言的协议,只能接收 speak 或 interrupt。
7.3 如何处理
我在 RoomContext 中增加 userTurnHolder,表示当前持有用户发言权的人。用户点击“请求发言”后,前端发送 request_turn;后端授予发言权后广播 turn_change。在用户回合内,AgentEngine 会暂停候选人裁决循环,并清理当前正在发言状态。用户发送文本后,后端释放回合,广播 userId=agents,AI 候选人恢复自动发言。
7.4 关键代码
房间上下文增加用户回合状态:
/** 当前持有用户独占发言权的 userId,null 表示无 */
private String userTurnHolder;
授予发言权时,不只是写状态,还会暂停 Agent 当前发言:
public boolean grantUserTurn(String roomId, String userId) {
RoomContext context = rooms.get(roomId);
if (context == null || userId == null || userId.isBlank()) {
return false;
}
if (context.getUserTurnHolder() != null && !context.getUserTurnHolder().equals(userId)) {
return false;
}
context.setUserTurnHolder(userId);
agentEngine.pauseAgentsForUser(roomId);
broadcastTurnChange(roomId, userId);
return true;
}
Agent 主循环中加入用户回合检查:
if (roomOps.isUserTurnActive(roomId)) {
Thread.sleep(300);
continue;
}
发言前也二次检查,避免异步延迟导致 Agent 抢在用户前面说话:
if (roomOps.isUserTurnActive(roomId)) {
speakingInProgress.remove(roomId);
currentSpeaker.remove(roomId);
return;
}
WebSocket 协议层新增 request_turn,用户发言后自动释放回合:
case "request_turn" -> handleRequestTurn(wsMessage, roomService);
private void handleSpeak(WebSocketMessage message, RoomOperations roomService) {
boolean isUser = isHumanSpeaker(userId, roomService, roomId);
roomService.processEvent(...);
broadcastToRoom(roomId, ...);
if (isUser) {
roomService.releaseUserTurn(roomId, userId);
}
}
这补上了群面系统里的回合控制协议,让人类用户和 AI Agent 可以共享同一个讨论场;事件流中用户发言边界清晰,有利于 InferencePack 高亮与个人评价。
八、问题三:TTS 会朗读括号里的动作描写
8.1 存在什么问题
Agent 的回复经常包含 (点头)、(思考片刻)、【停顿】 这样的动作描写。展示在聊天气泡里没有问题,但如果 TTS 直接朗读,就会变成“点头、思考片刻、我认为……”,听感很差。
8.2 原因是什么
展示文本和朗读文本共用了同一个 content。而 Agent 输出里的括号动作本质上是舞台提示,不属于用户需要听到的语义内容。
8.3 如何处理
我没有修改原始消息内容,而是在朗读层增加文本清洗工具。后端 Edge TTS 和前端浏览器 TTS 都使用同一类规则:剔除中文圆括号、英文圆括号和中文方括号内的内容,再压缩多余空格。
这样做的好处是:聊天记录和报告证据仍保留完整原文,语音播放只消费适合朗读的文本。
8.4 关键代码
后端 TTS 清洗:
public static String forSpeech(String raw) {
if (raw == null || raw.isBlank()) {
return "";
}
return raw
.replaceAll("([^)]*)", "")
.replaceAll("\\([^)]*\\)", "")
.replaceAll("【[^】]*】", "")
.replaceAll("\\s{2,}", " ")
.trim();
}
Agent 发言时,展示仍用完整 response,TTS 用清洗后的 ttsText:
String ttsText = SpeechTextUtil.forSpeech(response);
if (ttsText.isBlank()) {
ttsText = response;
}
TtsResponse tts = speechService.synthesize(
TtsRequest.builder().text(ttsText).build());
前端手动朗读也复用同样规则:
export function textForSpeech(raw) {
if (!raw || typeof raw !== 'string') return ''
return raw
.replace(/([^)]*)/g, '')
.replace(/\([^)]*\)/g, '')
.replace(/【[^】]*】/g, '')
.replace(/\s{2,}/g, ' ')
.trim()
}
这体现了“展示数据”和“消费数据”的分层:同一条消息可以服务聊天展示、持久化复盘和语音播放,但每个场景拿到的文本形态不同——与报告 Agent 侧“展示原文 / 消费清洗”的分层思路一致。
九、问题四:AI 报告导出没有过程反馈
9.1 存在什么问题
AI 报告生成是长任务,涉及写底稿、构建推理包、调用大模型、执行工具、写文件和可能的 PDF 导出。原来的同步接口会让用户点击后等待几十秒,期间页面没有可信反馈;如果模型卡住或工具失败,用户只能看到最终失败。
9.2 原因是什么
报告生成链路本身是多阶段的,但接口设计是“一次请求、一次响应”。后端没有把 Agent 思考、工具调用和文件写入这些中间态暴露给前端;前端也没有 SSE 消费能力。
9.3 如何处理
我新增了 POST /api/report/manus/generate/stream,后端用 SseEmitter 推送进度事件;ReportManusService 和 ToolCallManusAgent 在关键节点发出 start、facts、agent、llm、tool_call、tool_result、heartbeat、complete 等事件。前端用 fetch + ReadableStream 解析 SSE,并在导出按钮旁展示可关闭的悬浮日志。
9.4 关键代码
后端接口设置 SSE 必要响应头,避免代理缓冲:
@PostMapping(value = "/generate/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter generateStream(@RequestBody GenerateRequest request, HttpServletResponse response) {
response.setHeader("Cache-Control", "no-cache, no-transform");
response.setHeader("X-Accel-Buffering", "no");
response.setHeader("Connection", "keep-alive");
SseEmitter emitter = new SseEmitter(300000L);
reportManusService.generateReportStream(
request.getRoomId(), reportType, format, request.getExtraPrompt(), emitter);
return emitter;
}
服务层启动异步任务,并每 12 秒发送心跳,避免前端误判为卡死:
ScheduledFuture<?> heartbeat = heartbeatScheduler.scheduleAtFixedRate(() -> {
sink.accept(ReportProgressEvent.of("heartbeat", "仍在处理中,请稍候…"));
}, 12, 12, TimeUnit.SECONDS);
sink.accept(ReportProgressEvent.of("start", "开始生成 AI 复盘报告…"));
sink.accept(ReportProgressEvent.of("facts", "正在写入结构化事实底稿…"));
ReportGenerationResult result = generateReport(roomId, reportType, exportFormat, extraPrompt, sink);
Agent 在 think() 和工具选择处主动上报进度:
emitProgress(ReportProgressEvent.builder()
.phase("agent_step")
.message("第 " + getCurrentStep() + "/" + getMaxSteps() + " 步:模型思考中…")
.step(getCurrentStep())
.totalSteps(getMaxSteps())
.timestamp(System.currentTimeMillis())
.build());
emitProgress(ReportProgressEvent.builder()
.phase("llm")
.message("第 " + getCurrentStep() + " 步:正在请求 DashScope 大模型…")
.build());
for (AssistantMessage.ToolCall call : toolCalls) {
emitProgress(ReportProgressEvent.toolCall(getCurrentStep(), getMaxSteps(), call.name()));
}
前端解析 SSE 块,不依赖浏览器原生 EventSource,因此可以用 POST 和鉴权头:
export async function generateManusReportStream(payload, onEvent) {
const response = await fetch('/api/report/manus/generate/stream', {
method: 'POST',
headers,
body: JSON.stringify(payload)
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split(/\r?\n\r?\n/)
buffer = parts.pop() || ''
for (const block of parts) {
dispatchSseBlock(block, onEvent)
}
}
}
这一步的价值是可观测性:用户看到的不再是一个转圈按钮,而是能知道系统正在写底稿、构建 InferencePack、调用 ReAct、执行 Tavily 搜索还是写入导出文件——与第三节至第七节 Agent 改造形成前后端闭环。
十、总结
本周工作把群面系统从“AI 候选人可以自动讨论”推进到“用户可以真实参与、报告可以稳定生成、Agent 行为可以被观察和约束”的阶段。
报告生成 Agent 的优化:解决的是工程能力上的瓶颈:上下文如何压缩、关键事实如何保留、工具如何稳定执行、外部知识如何接入(Tavily)、长任务如何让用户可感知
功能修复:解决的是用户体验上的问题:具体题目不可见、用户无法实际参与、TTS错误读出动作信息、ai导出过程不可视,它们分别向上游补充房间信息、向下游保证事件与展示质量,并为 Agent 报告链路提供可恢复上下文与可观测执行过程。
本轮改造更接近 Agent 工程化:用 InferencePack 管理上下文,用 ReAct 和工具调用实现多步任务,用 Tavily 引入外部知识,用 SSE 暴露执行过程,用容错解析和备用路径保证最终产物能够落盘。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)