3.2 消息历史存储

概述

本节实现对话历史的存储和管理,为多轮对话提供基础。

数据结构

Message 接口

1
2
3
4
5
6
interface Message {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
}

type ConversationHistory = Message[];

ConversationManager 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export class ConversationManager {
private messages: Message[] = [];
private systemPrompt: string;

constructor(systemPrompt: string) {
this.systemPrompt = systemPrompt;
this.messages.push({
role: 'system',
content: systemPrompt
});
}

addMessage(role: Message['role'], content: string): void {
this.messages.push({ role, content });
}

getMessages(): Message[] {
return [...this.messages]; // 返回副本
}

getLastN(n: number): Message[] {
return this.messages.slice(-n);
}

clear(): void {
this.messages = [{
role: 'system',
content: this.systemPrompt
}];
}

get length(): number {
return this.messages.length;
}
}

集成到 Agent

更新后的 Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { ConversationManager } from './conversation.js';
import { LLMClient } from './llm/client.js';

export class Agent {
private llm: LLMClient;
private conversation: ConversationManager;

constructor(llm: LLMClient, systemPrompt: string) {
this.llm = llm;
this.conversation = new ConversationManager(systemPrompt);
}

async process(userInput: string): Promise<string> {
// 添加用户消息
this.conversation.addMessage('user', userInput);

// 获取完整历史
const messages = this.conversation.getMessages();

// 调用 LLM
const response = await this.llm.chat(messages);

// 添加助手消息
this.conversation.addMessage('assistant', response);

return response;
}

getHistory(): Message[] {
return this.conversation.getMessages();
}

clearHistory(): void {
this.conversation.clear();
}
}

更新 LLM Client

1
2
3
4
5
6
7
8
9
10
11
12
export class LLMClient {
// ...

async chat(messages: Message[]): Promise<string> {
const response = await this.client.chat.completions.create({
model: this.model,
messages: messages // 接受完整消息历史
});

return response.choices[0].message.content || '';
}
}

使用示例

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
const llm = new LLMClient(config);
const agent = new Agent(llm, '你是一个友好的助手。');

// 第一轮
let response = await agent.process('你好!');
console.log('助手:', response);

// 第二轮(记住第一轮)
response = await agent.process('你刚才说了什么?');
console.log('助手:', response);

// 查看历史
console.log('对话历史:', agent.getHistory());

清除历史

1
agent.clearHistory();

上下文窗口管理

Token 计数

1
2
3
4
5
function estimateTokens(messages: Message[]): number {
const text = JSON.stringify(messages);
// 粗略估计:4 字符 ≈ 1 token
return Math.ceil(text.length / 4);
}

自动裁剪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export class ConversationManager {
private messages: Message[] = [];
private readonly maxTokens: number = 8000;

addMessage(role: Message['role'], content: string): void {
this.messages.push({ role, content });

// 检查是否超过限制
while (this.estimateTokens() > this.maxTokens && this.messages.length > 1) {
// 删除最旧的非系统消息
const firstUserIndex = this.messages.findIndex(m => m.role !== 'system');
if (firstUserIndex > 0) {
this.messages.splice(firstUserIndex, 1);
} else {
break;
}
}
}

private estimateTokens(): number {
return Math.ceil(JSON.stringify(this.messages).length / 4);
}
}

完整示例

交互式多轮对话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import readline from 'readline';
import { Agent } from './agent.js';
import { LLMClient } from './llm/client.js';
import { loadConfig } from './config.js';

async function main() {
const config = loadConfig();
const llm = new LLMClient(config.llm);
const agent = new Agent(llm, '你是一个友好的 AI 助手。');

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

console.log('=== 多轮对话 AI 助手 ===');
console.log('命令: /clear 清除历史, /exit 退出\n');

while (true) {
const userInput = await question('你: ');

if (userInput === '/exit') {
break;
}

if (userInput === '/clear') {
agent.clearHistory();
console.log('对话历史已清除\n');
continue;
}

const response = await agent.process(userInput);
console.log('助手:', response);
console.log();
}

rl.close();
}

function question(prompt: string): Promise<string> {
return new Promise((resolve) => {
rl.question(prompt, (answer) => resolve(answer));
});
}

main();

输出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== 多轮对话 AI 助手 ===
命令: /clear 清除历史, /exit 退出

你: 你好,我叫张三。
助手: 你好张三!很高兴认识你。有什么我可以帮助你的吗?

你: 我叫什么名字?
助手: 你叫张三。你刚才告诉我的。

你: /clear
对话历史已清除

你: 我叫什么名字?
助手: 抱歉,我不知道你的名字。我们能重新认识一下吗?

总结

Step 1 添加了对话历史管理:

  • ConversationManager - 管理消息历史
  • 上下文传递 - 将历史传给 LLM
  • 窗口管理 - 自动裁剪超长历史
  • 命令支持 - /clear, /exit

下一节将实现完整的多轮对话。

导航

上一篇: 3.1 上下文的重要性

下一篇: 3.3 多轮对话实现