在 Delphi 中构建 AI 聊天机器人:对话记忆与流式输出

· 组件

快速回答:聊天机器人就是聊天补全加上记忆。sgcWebSockets 提供了一个专用的 TsgcAIChat 组件,它替你保存完整的消息历史,所以每一轮都会连同到目前为止的对话一起发送,模型就能在上下文中作答。拖入组件,设置 API 密钥和模型,然后调用 Chat 获取一个回复,或调用 ChatStream 获取实时的、逐 token 的打字效果。清空对话只需一次 ClearHistory 调用。

如果你已经从 Delphi 调用过 LLM,你大概注意到一件令人沮丧的事:问一个追问,模型表现得好像从没和你说过话。这不是 bug,这就是一次性补全的工作方式。把它变成一个真正的对话助手,是玩具和聊天机器人之间的区别,而它归结为一个想法:记忆

一次性补全 vs 聊天机器人

LLM API 是无状态的。每个请求都是独立的,所以模型只知道你在那一个请求中放入的内容。一次性调用发送单个提示词:

// User: "What is the capital of France?"  ->  "Paris."
// User: "And its population?"             ->  "Population of what?"

第二个问题失败了,因为服务器没有保留第一个问题的任何东西。聊天机器人通过每次重新发送整个对话来解决这个问题:系统消息、每一个用户轮次,以及每一个助手回复,按顺序排列。模型读取历史,看到“它的”指的是 Paris,于是正确作答。你不需要数据库或会话服务器,只需要一个随对话增长的消息列表。唯一真正的工作是构建和维护那个列表,而这正是聊天机器人组件替你做的。

TsgcAIChat 组件:记忆替你打理

与其手动接线一个消息数组,不如把一个 TsgcAIChat 拖到你的窗体上。它在内部拥有对话历史,自动追加每条用户消息和每条助手回复,并在每次调用时发送累积的上下文。你设置提供商、API 密钥和模型,然后只需调用 Chat

uses
  sgcAI, sgcAI_Chat;

var
  Bot: TsgcAIChat;
begin
  Bot := TsgcAIChat.Create(nil);
  Bot.Provider := aicpOpenAI;
  Bot.ChatOptions.ApiKey := 'sk-...';
  Bot.ChatOptions.Model  := 'gpt-4o-mini';
  Bot.SystemMessage := 'You are a concise assistant for Delphi developers.';

  // Each call adds to the same conversation:
  ShowMessage(Bot.Chat('What is the capital of France?'));  // "Paris."
  ShowMessage(Bot.Chat('And its population?'));             // answers about Paris
end;

因为组件记住了第一轮,追问就直接奏效。SystemMessage 设定助手的人设,并随每个请求一起包含进去。当你想要一段全新的对话时,调用 Bot.ClearHistory;要检查或持久化已经说过的内容,Bot.GetHistory 返回消息列表。你也可以用 MaxHistoryMessages 给记忆设上限,使一段长聊天不会无限增长(较早的轮次会被自动裁剪)。

同一个组件与 sgcWebSockets 支持的每个提供商对话。把 Provider 切换为 aicpAnthropicaicpGeminiaicpDeepSeekaicpOllamaaicpGrokaicpMistral,更改模型名称,你聊天机器人代码的其余部分保持原样。见 ChatBot 组件页面AI & LLM 组件中心

流式输出实时回复

等上好几秒才看到完整答案出现,感觉很慢。真正的聊天机器人会流式输出回复,让词语在生成时就显示出来,也就是那种熟悉的打字效果。TsgcAIChat 通过 ChatStream 加上 OnChatStream 事件暴露这一点,后者会在每个文本块到达时触发。

Bot.OnChatStream := BotChatStream;
Bot.ChatStream('Explain WebSockets in two sentences.');

procedure TForm1.BotChatStream(Sender: TObject; const aChunk: string;
  var Cancel: Boolean);
begin
  Memo1.Text := Memo1.Text + aChunk;  // append each token as it arrives
  // set Cancel := True to stop the response early
end;

这些文本块在底层通过 Server-Sent Events 增量交付,但你从不接触 SSE 的底层管道。当流结束时,完整的助手回复会像一次非流式调用那样被加入历史,所以下一轮仍拥有完整上下文。Cancel 参数让你能实现一个“停止生成”按钮。还有 OnChatMessage 用于获取最终组装好的消息,以及 OnChatError 用于暴露任何 API 失败。

一个完整的语音聊天机器人

如果你想让助手能听会说,TsgcAIOpenAIChatBot 组件包装了整个循环:它捕获麦克风音频,用 Whisper 转写它,把文本发送给 Chat Completions,并通过文本转语音提供商把答案说回来。接入一个音频录制器和一个文本转语音引擎,设置密钥,然后调用 Start

uses
  sgcAI, sgcAI_OpenAI_Audio_ChatBot,
  sgcAI_AudioRecorder_MCI, sgcAI_TextToSpeech_System;

var
  ChatBot: TsgcAIOpenAIChatBot;
begin
  ChatBot := TsgcAIOpenAIChatBot.Create(nil);
  ChatBot.OpenAIOptions.ApiKey := 'sk-...';
  ChatBot.AudioRecorder := TsgcAudioRecorderMCI.Create(nil);
  ChatBot.TextToSpeech  := TsgcTextToSpeechSystem.Create(nil);
  ChatBot.OnChatCompletion := ChatBotChatCompletion;

  ChatBot.Start;                          // begin listening; Stop ends it
  ChatBot.ChatAsUser('Tell me a joke');   // or push a turn programmatically
end;

OnChatCompletion 事件给你每个回复的角色和内容,而 OnTranscription 让你能在内容被发送前检查或编辑所听到的话。这与 TsgcAIChat 是同一个对话思路,只是两端都加上了音频。

更愿意自己管理列表?

你不一定要使用聊天机器人组件。如果你想要完全的控制,保留你自己的一份 { role, content } 消息列表,并在每次调用时用 TsgcHTTP_API_OpenAI._CreateChatCompletion 发送它。追加用户消息,发送数组,然后在下一轮之前把助手的回复追加回同一个列表。这正是 TsgcAIChat 在内部做的记账工作,所以大多数人让组件来处理它。底层 API 在 Delphi 中的 OpenAI API 教程和 OpenAI 组件页面上有介绍。

开始使用

这里的一切都随 sgcWebSockets 一起提供。获取免费试用版,把一个 TsgcAIChat 拖到窗体上,设置 API 密钥和模型,你只需几行代码就能拥有一个能回答追问、具备上下文感知的聊天机器人。准备好时,加上 ChatStream 实现实时打字效果。

有疑问、反馈,或需要帮你把它接入你的应用?联系我们 — 你会收到来自代码编写者本人的回复。

相关内容