【自学AI】09 让 AI 记住你说过的话——多轮对话与会话管理实战
上一篇,我们完成了简历分析功能:用户上传简历,AI 返回一份结构化的分析报告。 功能跑通了,感觉不错。
但有个问题,我当时没有提—— AI 其实什么都不记得。
开头:AI 的”失忆症”
我们来还原一个真实的场景。
用户上传了简历,AI 分析完了,返回了报告。 用户看了一眼,觉得有些地方不明白,于是继续问:
用户:你说的"改进空间"是什么意思?能详细说一下吗?
AI:抱歉,我没有看到之前的对话内容。 请问您能把简历重新发一遍吗?这就是没有对话历史的 AI。
它看到的只是”你说的改进空间是什么意思”这一句话。 它不知道之前分析了什么,不知道”改进空间”指的是哪些内容,只能让用户重新发一遍。
这就像你和一个朋友聊了半小时, 转头问他”你刚才说的那个问题怎么解决?” 他说:“不好意思,我们聊过吗?”
这样的体验,用一个词形容:割裂。
用户明明刚刚和 AI 聊过,转头就被当成陌生人。 这在真实的面试系统里是不可接受的—— 用户需要和 AI 进行多轮对话,简历分析是第一步, 后面还有追问、优化建议、模拟面试…… 每一步都需要 AI 记住之前说过什么。
这一篇,我们就来解决这个问题: 怎样管理对话历史,让 AI 记住之前的对话。
一、为什么需要对话历史?
问题场景
先把问题说清楚。假设用户和 AI 进行了这样的对话:
用户:请分析一下我的简历。 我叫昇哥,工作 8 年,技术栈是 Vue全家桶熟练掌握、React全家桶熟悉 ... JS全栈开发
AI:根据你的简历,我发现了几个亮点: 1. 工作年限符合要求 2. 技术栈覆盖全面 但也有一些改进空间:缺少大型项目经验,架构设计经历较少。
用户:你说的改进空间,能详细说一下吗?现在,AI 要回答最后这个问题。
如果 AI 没有对话历史,它看到的只是:
用户:你说的改进空间,能详细说一下吗?它不知道”改进空间”是什么,不知道之前分析了什么简历, 只能回答”请问您指的是哪方面的改进空间?“——完全没有意义。
解决思路
解决办法其实很简单:每次调用 AI 时,把之前的对话历史一起发过去。
第一次调用: AI 看到:[用户的简历分析请求] AI 返回:[分析报告]
第二次调用: AI 看到:[用户的简历分析请求] + [AI 的分析报告] + [用户的追问] AI 返回:[基于完整上下文的详细回答]AI 之所以能理解”你说的改进空间”,是因为它看到了完整的对话过程。
问题的本质只有一句话: AI 没有天然的记忆, 所谓”记住”,是我们帮它把历史背在身上,每次一起带过去。
核心思想只有一句话:保存对话历史,每次调用 AI 时一起发送。
二、消息的数据结构
在动手写代码之前,先把数据结构搞清楚。
Message 接口
在 LangChain 和大多数 AI 框架里,对话历史由一条一条的”消息”组成。 每条消息有两个字段:谁说的,和说了什么。
interface Message { role: 'system' | 'user' | 'assistant'; content: string;}role 有三个取值:
| role | 含义 | 例子 |
|---|---|---|
system | 系统消息,给 AI 的角色定义 | ”你是一个资深的 Java 面试官,有 15 年经验” |
user | 用户说的话 | ”请分析一下我的简历” |
assistant | AI 的回答 | ”根据你的简历,我发现了几个亮点…” |
为什么恰好是这三个角色? 因为一段对话里只有三种声音: 规则制定者(system)、提问者(user)、回答者(assistant)。 三者缺一不可——没有 system,AI 不知道自己是谁;没有历史的 user/assistant,AI 不知道聊过什么。
对话历史是一个数组
一次完整的对话,就是把所有消息按顺序放在数组里:
const conversationHistory: Message[] = [ { role: 'system', content: '你是一个资深的简历分析官,有 15 年的经验...', }, { role: 'user', content: '请分析一下我的简历。我叫昇哥,工作 8 年...', }, { role: 'assistant', content: '根据你的简历,我发现了几个亮点:...', }, { role: 'user', content: '你说的改进空间,能详细说一下吗?', }, // 下一次调用 AI 时,把上面整个数组都发过去];⚠️ 关键点:不是只发最新的那条消息,而是整个数组都要发给 AI。 用户的第二个问题只有一句话,但 AI 能理解它,是因为看到了前面所有的上下文。
三、代码实现
明确了思路和数据结构,现在一步一步写代码。
Step 1:定义类型
创建 src/ai/interfaces/message.interface.ts:
export interface Message { role: 'system' | 'user' | 'assistant'; content: string;}
export interface SessionData { sessionId: string; userId: string; position: string; messages: Message[]; createdAt: Date; lastActivityAt: Date;}SessionData 是一个完整的会话对象,包含:
sessionId:会话唯一标识,用于区分不同用户的不同对话messages:这次会话的所有消息历史lastActivityAt:最后活跃时间,用于清理过期会话
Step 2:安装依赖
我们需要 uuid 来生成唯一的会话 ID:
pnpm add uuid@13.0.0Step 3:创建 SessionManager
这是本篇最核心的服务。创建 src/ai/services/session.manager.ts:
import { Injectable, Logger } from '@nestjs/common';import { v4 as uuidv4 } from 'uuid';import { Message, SessionData } from '../interfaces/message.interface';
@Injectable()export class SessionManager { private readonly logger = new Logger(SessionManager.name);
// 用 Map 存储所有会话,key 是 sessionId // 注意:这是内存存储,服务器重启会丢失 // 生产环境应该持久化到数据库(下一篇会讲) private sessions: Map<string, SessionData> = new Map();
/** * 创建新会话 * * @param userId 用户 ID * @param position 面试职位 * @param systemMessage AI 的角色定义(System Message) * @returns 新会话的 sessionId */ createSession( userId: string, position: string, systemMessage: string, ): string { const sessionId = uuidv4();
const session: SessionData = { sessionId, userId, position, messages: [ { role: 'system', content: systemMessage, }, ], createdAt: new Date(), lastActivityAt: new Date(), };
this.sessions.set(sessionId, session); this.logger.log(`创建会话: ${sessionId},用户: ${userId}`);
return sessionId; }
/** * 向会话中添加一条消息 * * @param sessionId 会话 ID * @param role 消息角色 * @param content 消息内容 */ addMessage( sessionId: string, role: 'user' | 'assistant', content: string, ): void { const session = this.sessions.get(sessionId);
if (!session) { throw new Error(`会话不存在: ${sessionId}`); }
session.messages.push({ role, content }); session.lastActivityAt = new Date(); // 更新最后活跃时间 }
/** * 获取会话的完整对话历史 * * @param sessionId 会话 ID * @returns Message 数组 */ getHistory(sessionId: string): Message[] { const session = this.sessions.get(sessionId); return session?.messages || []; }
/** * 获取最近的 N 条消息(用于控制 Token 消耗) * * 为什么需要这个方法? * 对话越长,每次发给 AI 的 token 就越多,成本越高。 * 所以我们只保留最近的几条消息,旧的消息可以丢弃。 * * 但有一个例外:System Message(第一条)必须始终保留。 * 因为它定义了 AI 的角色,丢掉它 AI 就不知道自己是谁了。 * * @param sessionId 会话 ID * @param count 保留最近几条消息(不含 System Message) */ getRecentMessages(sessionId: string, count: number = 10): Message[] { const history = this.getHistory(sessionId);
if (history.length === 0) { return []; }
// System Message 是第一条,必须保留 const systemMessage = history[0];
// 取最近 count 条消息 const recentMessages = history.slice(-count);
// 如果最近的消息里已经包含了 System Message,直接返回 if (recentMessages[0]?.role === 'system') { return recentMessages; }
// 否则,在最前面加上 System Message return [systemMessage, ...recentMessages]; }
/** * 结束会话,从内存中删除 * * @param sessionId 会话 ID */ endSession(sessionId: string): void { if (this.sessions.has(sessionId)) { this.sessions.delete(sessionId); this.logger.log(`结束会话: ${sessionId}`); } }
/** * 清理超过 1 小时未活动的过期会话 * * 在生产环境中,应该用 @Cron 装饰器定期调用这个方法, * 防止内存无限增长。 */ cleanupExpiredSessions(): void { const now = new Date(); const expirationTime = 60 * 60 * 1000; // 1 小时
for (const [sessionId, session] of this.sessions.entries()) { if (now.getTime() - session.lastActivityAt.getTime() > expirationTime) { this.sessions.delete(sessionId); this.logger.warn(`清理过期会话: ${sessionId}`); } } }}这里有几个细节值得注意:
getRecentMessages 里为什么要特殊处理 System Message?
System Message 是 AI 的”人设”——“你是一个资深的 Java 面试官”。 如果这条消息被截掉了,AI 就不知道自己的角色,回答会变得很奇怪。 所以不管截取多少条历史消息,System Message 必须始终在第一位。
你可以把 System Message 理解成 AI 的”出厂设置”。 历史消息可以截断,出厂设置不能丢。
为什么用 Map 而不是数组?
Map 的查找是 O(1),用 sessionId 直接取到会话,不需要遍历。
对话系统里每次收到消息都要查找会话,性能很重要。
Step 4:更新 AIModule
把 SessionManager 加入 AI 模块,让其他模块可以注入它:
import { Module } from '@nestjs/common';import { AIModelFactory } from './services/ai-model.factory';import { SessionManager } from './services/session.manager';
@Module({ providers: [AIModelFactory, SessionManager], exports: [AIModelFactory, SessionManager], // 两个都导出})export class AIModule {}Step 5:提取 Prompt 定义
把所有 Prompt 集中到一个文件里管理。创建(或更新)src/interview/prompts/resume-analysis.prompts.ts:
/** * 简历分析的 System Message * 定义 AI 的角色,根据职位动态生成 */export const RESUME_ANALYSIS_SYSTEM_MESSAGE = (position: string): string => { return `你是一个资深的 ${position} 面试官,有 15 年的招聘经验。你能快速从简历中识别候选人的核心能力。`;};
/** * 简历分析的主 Prompt * 用于第一次分析简历,返回结构化的 JSON 报告 */export const RESUME_ANALYSIS_PROMPT = `你已经拥有以下信息,要求你进行分析:
## 简历内容
{resume_content}
## 岗位要求
{job_description}
## 分析要求
1. 提取候选人的: - 工作年限 - 主要技能 - 最近工作经历 - 教育背景
2. 评估匹配度(0-100)
3. 识别优势和不足
## 输出格式(JSON)
{{ "years_of_experience": 数字, "skills": ["技能1", "技能2"], "recent_position": "最近的职位", "education": "学历", "match_score": 数字(0-100), "strengths": ["优势1", "优势2"], "gaps": ["缺陷1", "缺陷2"], "summary": "1-2 句总结"}}`;
/** * 多轮对话继续的 Prompt * 用于后续追问,基于已有的对话历史回答 */export const CONVERSATION_CONTINUATION_PROMPT = `基于以下对话历史,请回答最后一个问题。
对话历史:{history}
请给出清晰、有逻辑的回答。`;Step 6:创建 ResumeAnalysisService
把简历分析的 Chain 逻辑单独提取成一个服务。
创建 src/interview/services/resume-analysis.service.ts:
import { Injectable, Logger } from '@nestjs/common';import { PromptTemplate } from '@langchain/core/prompts';import { JsonOutputParser } from '@langchain/core/output_parsers';import { AIModelFactory } from '../../ai/services/ai-model.factory';import { RESUME_ANALYSIS_PROMPT } from '../prompts/resume-analysis.prompts';
@Injectable()export class ResumeAnalysisService { private readonly logger = new Logger(ResumeAnalysisService.name);
constructor(private aiModelFactory: AIModelFactory) {}
async analyze(resumeContent: string, jobDescription: string): Promise<any> { const prompt = PromptTemplate.fromTemplate(RESUME_ANALYSIS_PROMPT); const model = this.aiModelFactory.createDefaultModel(); const parser = new JsonOutputParser();
const chain = prompt.pipe(model).pipe(parser);
try { this.logger.log('开始分析简历...');
const result = await chain.invoke({ resume_content: resumeContent, job_description: jobDescription, });
this.logger.log('简历分析完成'); return result; } catch (error) { this.logger.error('简历分析失败:', error); throw error; } }}Step 7:创建 ConversationContinuationService
为多轮对话创建专门的服务。
创建 src/interview/services/conversation-continuation.service.ts:
import { Injectable, Logger } from '@nestjs/common';import { PromptTemplate } from '@langchain/core/prompts';import { AIModelFactory } from '../../ai/services/ai-model.factory';import { Message } from '../../ai/interfaces/message.interface';import { CONVERSATION_CONTINUATION_PROMPT } from '../prompts/resume-analysis.prompts';
@Injectable()export class ConversationContinuationService { private readonly logger = new Logger(ConversationContinuationService.name);
constructor(private aiModelFactory: AIModelFactory) {}
/** * 基于对话历史继续对话 * * @param history 当前会话的消息历史(Message 数组) * @returns AI 的回答文本 */ async continue(history: Message[]): Promise<string> { const prompt = PromptTemplate.fromTemplate(CONVERSATION_CONTINUATION_PROMPT); const model = this.aiModelFactory.createDefaultModel();
// 注意:这里不需要 JsonOutputParser // 多轮对话的回答是自然语言,不是结构化 JSON const chain = prompt.pipe(model);
try { this.logger.log(`继续对话,历史消息数: ${history.length}`);
const response = await chain.invoke({ // 把 Message 数组转成文本格式,发给 AI history: history.map((m) => `${m.role}: ${m.content}`).join('\n\n'), });
const aiResponse = response.content as string;
this.logger.log('对话继续完成'); return aiResponse; } catch (error) { this.logger.error('继续对话失败:', error); throw error; } }}Step 8:更新 InterviewService
现在 InterviewService 变得非常干净——
它只负责会话管理和流程编排,不关心具体的 AI 调用细节:
import { Injectable, Logger } from '@nestjs/common';import { SessionManager } from '../../ai/services/session.manager';import { AIModelFactory } from '../../ai/services/ai-model.factory';import { ResumeAnalysisService } from './resume-analysis.service';import { ConversationContinuationService } from './conversation-continuation.service';import { RESUME_ANALYSIS_SYSTEM_MESSAGE } from '../prompts/resume-analysis.prompts';
@Injectable()export class InterviewService { private readonly logger = new Logger(InterviewService.name);
constructor( private sessionManager: SessionManager, private aiModelFactory: AIModelFactory, private resumeAnalysisService: ResumeAnalysisService, private conversationContinuationService: ConversationContinuationService, ) {}
/** * 分析简历(第一轮对话) * 创建新会话,调用 AI 分析,保存历史 */ async analyzeResume( userId: string, position: string, resumeContent: string, jobDescription: string, ) { try { // 第一步:创建新会话,写入 System Message const systemMessage = RESUME_ANALYSIS_SYSTEM_MESSAGE(position); const sessionId = this.sessionManager.createSession( userId, position, systemMessage, );
this.logger.log(`创建会话: ${sessionId}`);
// 第二步:调用简历分析服务 const result = await this.resumeAnalysisService.analyze( resumeContent, jobDescription, );
// 第三步:把这轮对话保存到会话历史 this.sessionManager.addMessage( sessionId, 'user', `简历内容:${resumeContent}`, ); this.sessionManager.addMessage( sessionId, 'assistant', JSON.stringify(result), );
this.logger.log(`简历分析完成,sessionId: ${sessionId}`);
return { sessionId, analysis: result }; } catch (error) { this.logger.error(`分析简历失败: ${error}`); throw error; } }
/** * 继续对话(多轮) * 基于已有会话,追加新消息,调用 AI 回答 */ async continueConversation( sessionId: string, userQuestion: string, ): Promise<string> { try { // 第一步:把用户的新问题加入历史 this.sessionManager.addMessage(sessionId, 'user', userQuestion);
// 第二步:取最近 10 条消息(含 System Message) const history = this.sessionManager.getRecentMessages(sessionId, 10);
this.logger.log( `继续对话,sessionId: ${sessionId},历史消息数: ${history.length}`, );
// 第三步:调用对话继续服务 const aiResponse = await this.conversationContinuationService.continue(history);
// 第四步:把 AI 的回答也保存到历史 this.sessionManager.addMessage(sessionId, 'assistant', aiResponse);
this.logger.log(`对话继续完成,sessionId: ${sessionId}`);
return aiResponse; } catch (error) { this.logger.error(`继续对话失败: ${error}`); throw error; } }}Step 9:更新 Module 和 Controller
更新 interview.module.ts,注册新增的服务:
import { Module } from '@nestjs/common';import { AIModule } from '../ai/ai.module';import { InterviewController } from './interview.controller';import { InterviewService } from './services/interview.service';import { ResumeAnalysisService } from './services/resume-analysis.service';import { ConversationContinuationService } from './services/conversation-continuation.service';
@Module({ imports: [AIModule], providers: [ InterviewService, ResumeAnalysisService, ConversationContinuationService, ], controllers: [InterviewController],})export class InterviewModule {}更新 interview.controller.ts,添加两个接口:
// 接口 1:分析简历(需要登录)@Post('/analyze-resume')@UseGuards(JwtAuthGuard)async analyzeResume( @Body() body: { position: string; resume: string; jobDescription: string }, @Request() req: any,) { const result = await this.interviewService.analyzeResume( req.user.userId, body.position, body.resume, body.jobDescription, );
return { code: 200, data: result };}
// 接口 2:继续对话(多轮)@Post('/continue-conversation')@UseGuards(JwtAuthGuard)async continueConversation( @Body() body: { sessionId: string; question: string },) { const result = await this.interviewService.continueConversation( body.sessionId, body.question, );
return { code: 200, data: { response: result } };}四、测试接口
代码写完了,来验证一下 AI 是否真的记住了上下文。
第一步:登录,获取 Token
curl -X POST http://localhost:3000/user/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "123456" }'从返回结果里取出 token 字段,后面要用。
第二步:分析简历,创建会话
curl -X POST http://localhost:3000/interview/analyze-resume \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 替换成你的token" \ -d '{ "position": "Java 后端开发工程师", "resume": "姓名:昇哥\n工作年限:8年\n技术栈:熟练掌握Vue全家桶,熟悉React全家桶,\n最近工作:高级前端开发\n主要项目:电商系统、报价系统\n教育背景:计算机本科,普通二本", "jobDescription": "职位:AI Agent开发\n工作年限:3-5年\n技能要求:TypeScript, React, Vue, MySQL, Redis\n岗位职责:设计高并发,高性能,交互流畅的系统 ..." }'返回结果里有一个 sessionId,把它记下来,下一步要用:
{ "code": 200, "data": { "sessionId": "3f4d27a6-c7eb-40c3-995f-220c2543fed1", "analysis": { "years_of_experience": 5, "skills": ["Java", "Spring Boot", "MySQL", "Redis"], "match_score": 85, "strengths": ["技术栈高度匹配", "有大型项目经验"], "gaps": ["缺少消息队列经验", "未提及架构设计经历"], "summary": "候选人技术栈与岗位匹配度高,建议进入技术面试。" } }}第三步:继续追问,验证 AI 的记忆
curl -X POST http://localhost:3000/interview/continue-conversation \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 替换成你的token" \ -d '{ "sessionId": "3f4d27a6-c7eb-40c3-995f-220c2543fed1", "question": "请问我的名字是什么?我有几年工作经验?" }'如果一切正常,AI 会回答:
{ "code": 200, "data": { "response": "根据您之前提供的简历,您的名字是昇哥,拥有 8 年的工作经验,主要技术栈包括 Vue全家桶,React全家桶 。。。 balabala 。" }}
AI 记住了。你可以继续追问,它会一直记得这次会话里发生的所有对话。
五、架构优势(选读)
你可能会问:为什么要拆成这么多层?直接在 InterviewService 里写 Prompt 和 Chain 不行吗?
可以,但会有问题。
代码能跑,不代表代码好。 好的代码,是三个月后的你看到,不会骂自己的代码。
这里列出 5 个好处,你感受一下。
优势 1:关注点分离
现在每一层只做一件事:
Prompt 定义(prompts/) ↓AI 调用逻辑(ResumeAnalysisService / ConversationContinuationService) ↓会话管理 + 流程编排(InterviewService) ↓HTTP 接口(InterviewController)改 Prompt 只动 prompts/ 文件,改 AI 调用逻辑只动对应的 Service,改接口只动 Controller。互不干扰。
优势 2:易于扩展
将来要加新功能(比如编程题分析),只需要:
// 1. 新建 coding-question.prompts.ts// 2. 新建 CodingQuestionService// 3. 在 InterviewService 里调用它
async analyzeCodingQuestion(code: string, language: string) { const result = await this.codingQuestionService.analyze(code, language); // 保存到会话历史... return result;}现有的代码一行不用改。
优势 3:易于测试
每一层都可以单独测试,用 mock 替换依赖:
// 测试 InterviewService 时,不需要真实调用 AIjest.spyOn(resumeAnalysisService, 'analyze').mockResolvedValue({ years_of_experience: 5, match_score: 85, // ...});
const result = await interviewService.analyzeResume(...);expect(result.sessionId).toBeDefined(); // 只测会话逻辑,不测 AI优势 4:易于修改 Prompt
想让 AI 多输出一个”缺少的技能”字段?只改 prompts 文件:
// 只改这一个文件,其他代码完全不动export const RESUME_ANALYSIS_PROMPT = ` ... ## 输出格式(JSON) {{ ... "missing_skills": ["技能1", "技能2"], // 新增这一行 ... }}`;优势 5:易于切换模型
从 DeepSeek 换成 OpenAI?只改 AIModelFactory 一个文件:
// 改这里,所有 Service 自动用上新模型createDefaultModel(): ChatOpenAI { return new ChatOpenAI({ apiKey: this.configService.get<string>('OPENAI_API_KEY'), model: 'gpt-4o', });}InterviewService、ResumeAnalysisService、ConversationContinuationService 的代码全部不用动。
这 5 个优势,说到底是同一件事: 改动不扩散。 改一个地方,只影响一个地方。 这是好架构最朴素的标准。
六、常见问题
Q1:什么时候应该结束会话?
会话不主动结束,就是内存泄漏的开始。
会话应该在以下情况下结束:
- 用户主动点击”结束面试”
- 超过 1 小时未活动(
cleanupExpiredSessions自动处理) - 服务器重启(内存清空)
// 主动结束this.sessionManager.endSession(sessionId);
// 定期自动清理(配合 @Cron 装饰器使用)@Cron('0 * * * *') // 每小时执行一次handleCleanup() { this.sessionManager.cleanupExpiredSessions();}Q2:一个用户可以同时有多个会话吗?
可以。每个
sessionId是独立的宇宙,互不干扰。
比如用户同时进行”简历分析”和”编程题”两个面试:
// 两个会话完全独立const sessionId1 = sessionManager.createSession(userId, 'Java 开发', msg1);const sessionId2 = sessionManager.createSession(userId, 'Python 开发', msg2);
sessionManager.addMessage(sessionId1, 'user', '...'); // 只影响会话 1sessionManager.addMessage(sessionId2, 'user', '...'); // 只影响会话 2Q3:对话历史应该保存多久?
这是一个安全性和性能的权衡问题,没有标准答案,只有适合你场景的答案。
目前的实现是内存存储,服务器重启就会丢失。 生产环境应该持久化到数据库。保存策略有三种:
| 策略 | 方式 | 优点 | 缺点 |
|---|---|---|---|
| 实时保存 | 每条消息立即写库 | 最安全,不丢数据 | 数据库写入频繁,性能差 |
| 定时保存 | 每 N 条消息写一次 | 折中方案 | 崩溃时可能丢失最近几条 |
| 批量保存 | 会话结束时统一写库 | 性能最好 | 崩溃时整个会话丢失 |
实际项目中,通常用定时保存——每 5-10 条消息写一次库,在安全性和性能之间取得平衡。
Q4:网络中断后如何恢复会话?
用户不应该因为断网就丢失整个面试进度。
从数据库恢复到内存即可:
async reconnectSession(sessionId: string): Promise<SessionData> { // 先查内存 let session = this.sessions.get(sessionId);
if (!session) { // 内存没有,从数据库恢复 session = await this.conversationRepository.findOne({ sessionId });
if (!session) { throw new Error(`会话不存在: ${sessionId}`); }
// 恢复到内存,后续操作走内存 this.sessions.set(sessionId, session); }
return session;}用户无论何时重连,都能接着之前的对话继续。
结尾
这篇文章我们学了四件事:
- ✅ 为什么需要对话历史:AI 没有天然的记忆,需要我们把历史消息一起发给它
- ✅ 消息的数据结构:
role+content,三种角色,整个数组都要发送 - ✅ SessionManager 的实现:创建会话、追加消息、获取历史、清理过期
- ✅ 分层架构的价值:每一层只做一件事,改 Prompt 不影响业务,换模型不影响接口
现在我们的系统可以进行真正的多轮对话了。
但细心的你可能已经发现了一个新问题:
对话越来越长,Token 消耗越来越多。
第 1 轮对话,发给 AI 的是 1 条消息。 第 10 轮对话,发给 AI 的是 10 条消息。 第 20 轮对话,发给 AI 的是 20 条消息。
每一轮的成本,都是前一轮的累加。 一次完整的面试对话下来,Token 消耗可能是第 1 轮的 10-20 倍。
这不只是成本问题—— 每个模型都有 Token 上限,超过了直接报错。 而且对话越长,AI 越容易”忘记”开头说的事—— 记得太多,反而记不住重要的。
下一篇,我们来解决这个问题: 如何在不丢失关键信息的前提下,控制对话历史的长度。
昇哥 · 2026年3月 全栈开发 × AI 学习途中,把踩过的坑写下来
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!