Skip to content

Delphi与大型语言模型应用:从理论到实践

大型语言模型(LLM)如ChatGPT、Claude和Llama等正在彻底改变软件开发和用户交互方式。本文将探讨如何在Delphi应用中集成和利用这些强大的AI模型,从理论基础到实际应用案例。

大型语言模型简介

大型语言模型是基于Transformer架构的深度学习模型,通过大规模文本数据训练而成。它们具有以下特点:

  1. 理解和生成自然语言:能够理解用户输入并生成连贯、相关的文本
  2. 上下文学习:能够在对话中保持上下文连贯性
  3. 知识库:包含训练数据中的广泛知识
  4. 任务多样性:可用于文本生成、翻译、摘要、问答等多种任务

为什么在Delphi应用中集成LLM?

将LLM集成到Delphi应用中可以带来以下优势:

  1. 增强用户交互:提供自然语言界面,简化复杂操作
  2. 自动化内容生成:自动生成报告、描述、电子邮件等内容
  3. 智能辅助功能:提供上下文相关的建议和帮助
  4. 数据分析和洞察:从非结构化数据中提取见解
  5. 个性化体验:根据用户偏好和行为定制内容和功能

技术准备

在开始集成LLM之前,你需要准备以下内容:

  1. Delphi开发环境:建议使用Delphi 10.4或更高版本
  2. API密钥:如OpenAI API、Anthropic API或其他LLM服务提供商的密钥
  3. REST客户端组件:用于API调用,如Indy、REST Debugger或TRESTClient
  4. JSON处理库:如System.JSON或其他第三方JSON库
  5. 基础NLP知识:了解提示工程(Prompt Engineering)的基本概念

基础:创建通用LLM客户端

首先,我们创建一个通用的LLM客户端类,支持不同的模型提供商:

unit LLMClient;

interface

uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient,
  System.JSON, System.Generics.Collections;

type
  TLLMProvider = (lpOpenAI, lpAnthropic, lpGoogle);

  TLLMMessage = record
    Role: string;
    Content: string;
    constructor Create(const ARole, AContent: string);
  end;

  TLLMClient = class
  private
    FApiKey: string;
    FProvider: TLLMProvider;
    FModel: string;
    FMessages: TList<TLLMMessage>;
    FTemperature: Double;
    FMaxTokens: Integer;
    FHttpClient: THTTPClient;

    function GetEndpoint: string;
    function BuildRequestBody: TJSONObject;
    function ParseResponse(const ResponseContent: string): string;
  public
    constructor Create(const ApiKey: string; Provider: TLLMProvider);
    destructor Destroy; override;

    function SendMessage(const UserMessage: string): string;
    procedure ClearConversation;

    property Provider: TLLMProvider read FProvider write FProvider;
    property Model: string read FModel write FModel;
    property Temperature: Double read FTemperature write FTemperature;
    property MaxTokens: Integer read FMaxTokens write FMaxTokens;
  end;

implementation

{ TLLMMessage }

constructor TLLMMessage.Create(const ARole, AContent: string);
begin
  Role := ARole;
  Content := AContent;
end;

{ TLLMClient }

constructor TLLMClient.Create(const ApiKey: string; Provider: TLLMProvider);
begin
  inherited Create;
  FApiKey := ApiKey;
  FProvider := Provider;
  FMessages := TList<TLLMMessage>.Create;
  FHttpClient := THTTPClient.Create;

  // 设置默认值
  FTemperature := 0.7;
  FMaxTokens := 1000;

  // 根据提供商设置默认模型
  case Provider of
    lpOpenAI: FModel := 'gpt-3.5-turbo';
    lpAnthropic: FModel := 'claude-2';
    lpGoogle: FModel := 'gemini-pro';
  end;
end;

destructor TLLMClient.Destroy;
begin
  FMessages.Free;
  FHttpClient.Free;
  inherited;
end;

function TLLMClient.GetEndpoint: string;
begin
  case FProvider of
    lpOpenAI: Result := 'https://api.openai.com/v1/chat/completions';
    lpAnthropic: Result := 'https://api.anthropic.com/v1/messages';
    lpGoogle: Result := 'https://generativelanguage.googleapis.com/v1beta/models/' + FModel + ':generateContent';
  else
    raise Exception.Create('不支持的LLM提供商');
  end;
end;

function TLLMClient.BuildRequestBody: TJSONObject;
var
  RequestObj, MessageObj: TJSONObject;
  MessagesArray: TJSONArray;
  Message: TLLMMessage;
begin
  RequestObj := TJSONObject.Create;

  try
    case FProvider of
      lpOpenAI:
        begin
          RequestObj.AddPair('model', FModel);
          RequestObj.AddPair('temperature', TJSONNumber.Create(FTemperature));
          RequestObj.AddPair('max_tokens', TJSONNumber.Create(FMaxTokens));

          MessagesArray := TJSONArray.Create;
          for Message in FMessages do
          begin
            MessageObj := TJSONObject.Create;
            MessageObj.AddPair('role', Message.Role);
            MessageObj.AddPair('content', Message.Content);
            MessagesArray.AddElement(MessageObj);
          end;

          RequestObj.AddPair('messages', MessagesArray);
        end;

      lpAnthropic:
        begin
          RequestObj.AddPair('model', FModel);
          RequestObj.AddPair('temperature', TJSONNumber.Create(FTemperature));
          RequestObj.AddPair('max_tokens', TJSONNumber.Create(FMaxTokens));

          MessagesArray := TJSONArray.Create;
          for Message in FMessages do
          begin
            MessageObj := TJSONObject.Create;
            MessageObj.AddPair('role', Message.Role);
            MessageObj.AddPair('content', Message.Content);
            MessagesArray.AddElement(MessageObj);
          end;

          RequestObj.AddPair('messages', MessagesArray);
        end;

      lpGoogle:
        begin
          RequestObj.AddPair('temperature', TJSONNumber.Create(FTemperature));
          RequestObj.AddPair('maxOutputTokens', TJSONNumber.Create(FMaxTokens));

          MessagesArray := TJSONArray.Create;
          for Message in FMessages do
          begin
            MessageObj := TJSONObject.Create;
            MessageObj.AddPair('role', Message.Role);
            MessageObj.AddPair('parts', TJSONArray.Create(TJSONObject.Create
              .AddPair('text', Message.Content)));
            MessagesArray.AddElement(MessageObj);
          end;

          RequestObj.AddPair('contents', MessagesArray);
        end;
    end;

    Result := RequestObj;
  except
    RequestObj.Free;
    raise;
  end;
end;

function TLLMClient.ParseResponse(const ResponseContent: string): string;
var
  ResponseObj: TJSONObject;
  ChoicesArray: TJSONArray;
begin
  Result := '';
  ResponseObj := TJSONObject.ParseJSONValue(ResponseContent) as TJSONObject;

  try
    case FProvider of
      lpOpenAI:
        begin
          ChoicesArray := ResponseObj.GetValue('choices') as TJSONArray;
          if (ChoicesArray <> nil) and (ChoicesArray.Count > 0) then
            Result := (ChoicesArray.Items[0] as TJSONObject)
              .GetValue<TJSONObject>('message')
              .GetValue<string>('content');
        end;

      lpAnthropic:
        begin
          if ResponseObj.GetValue('content') <> nil then
            Result := (ResponseObj.GetValue<TJSONArray>('content').Items[0] as TJSONObject)
              .GetValue<string>('text');
        end;

      lpGoogle:
        begin
          if ResponseObj.GetValue('candidates') <> nil then
            Result := ((ResponseObj.GetValue<TJSONArray>('candidates').Items[0] as TJSONObject)
              .GetValue<TJSONArray>('content').Items[0] as TJSONObject)
              .GetValue<TJSONArray>('parts').Items[0].GetValue<string>('text');
        end;
    end;
  finally
    ResponseObj.Free;
  end;
end;

function TLLMClient.SendMessage(const UserMessage: string): string;
var
  URL: string;
  RequestBody: TJSONObject;
  Response: IHTTPResponse;
  ResponseContent: string;
begin
  Result := '';
  URL := GetEndpoint;

  // 添加用户消息到历史
  FMessages.Add(TLLMMessage.Create('user', UserMessage));

  RequestBody := BuildRequestBody;
  try
    // 设置请求头
    FHttpClient.CustomHeaders.Clear;
    FHttpClient.CustomHeaders['Content-Type'] := 'application/json';

    case FProvider of
      lpOpenAI:
        FHttpClient.CustomHeaders['Authorization'] := 'Bearer ' + FApiKey;

      lpAnthropic:
        FHttpClient.CustomHeaders['x-api-key'] := FApiKey;

      lpGoogle:
        URL := URL + '?key=' + FApiKey;
    end;

    // 发送请求
    Response := FHttpClient.Post(URL, TStringStream.Create(RequestBody.ToJSON), nil);
    ResponseContent := Response.ContentAsString;

    // 解析响应
    if Response.StatusCode = 200 then
    begin
      Result := ParseResponse(ResponseContent);

      // 添加AI回复到历史
      FMessages.Add(TLLMMessage.Create('assistant', Result));
    end
    else
      raise Exception.CreateFmt('API错误: %d - %s', [Response.StatusCode, ResponseContent]);
  finally
    RequestBody.Free;
  end;
end;

procedure TLLMClient.ClearConversation;
begin
  FMessages.Clear;
end;

end.

中级:提示工程与上下文管理

提示工程是使用LLM的关键技术,通过精心设计的提示可以引导模型生成更好的结果:

unit PromptEngineering;

interface

uses
  System.SysUtils, System.Classes, System.Generics.Collections;

type
  TPromptTemplate = class
  private
    FTemplate: string;
    FVariables: TDictionary<string, string>;
  public
    constructor Create(const Template: string);
    destructor Destroy; override;

    procedure SetVariable(const Name, Value: string);
    function Render: string;
  end;

  TConversationManager = class
  private
    FSystemPrompt: string;
    FConversationHistory: TList<TPair<string, string>>;
    FMaxHistoryLength: Integer;
  public
    constructor Create(const SystemPrompt: string);
    destructor Destroy; override;

    procedure AddExchange(const UserMessage, AIResponse: string);
    function GetFormattedConversation: string;
    procedure ClearHistory;

    property SystemPrompt: string read FSystemPrompt write FSystemPrompt;
    property MaxHistoryLength: Integer read FMaxHistoryLength write FMaxHistoryLength;
  end;

implementation

{ TPromptTemplate }

constructor TPromptTemplate.Create(const Template: string);
begin
  inherited Create;
  FTemplate := Template;
  FVariables := TDictionary<string, string>.Create;
end;

destructor TPromptTemplate.Destroy;
begin
  FVariables.Free;
  inherited;
end;

procedure TPromptTemplate.SetVariable(const Name, Value: string);
begin
  FVariables.AddOrSetValue(Name, Value);
end;

function TPromptTemplate.Render: string;
var
  Result: string;
  Pair: TPair<string, string>;
begin
  Result := FTemplate;

  for Pair in FVariables do
    Result := StringReplace(Result, '{' + Pair.Key + '}', Pair.Value, [rfReplaceAll]);

  Result := Result;
end;

{ TConversationManager }

constructor TConversationManager.Create(const SystemPrompt: string);
begin
  inherited Create;
  FSystemPrompt := SystemPrompt;
  FConversationHistory := TList<TPair<string, string>>.Create;
  FMaxHistoryLength := 10; // 默认保留最近10轮对话
end;

destructor TConversationManager.Destroy;
begin
  FConversationHistory.Free;
  inherited;
end;

procedure TConversationManager.AddExchange(const UserMessage, AIResponse: string);
begin
  FConversationHistory.Add(TPair<string, string>.Create(UserMessage, AIResponse));

  // 如果历史记录超过最大长度,删除最早的记录
  while FConversationHistory.Count > FMaxHistoryLength do
    FConversationHistory.Delete(0);
end;

function TConversationManager.GetFormattedConversation: string;
var
  SB: TStringBuilder;
  Exchange: TPair<string, string>;
begin
  SB := TStringBuilder.Create;
  try
    // 添加系统提示
    if not FSystemPrompt.IsEmpty then
    begin
      SB.AppendLine('系统: ' + FSystemPrompt);
      SB.AppendLine;
    end;

    // 添加对话历史
    for Exchange in FConversationHistory do
    begin
      SB.AppendLine('用户: ' + Exchange.Key);
      SB.AppendLine('助手: ' + Exchange.Value);
      SB.AppendLine;
    end;

    Result := SB.ToString;
  finally
    SB.Free;
  end;
end;

procedure TConversationManager.ClearHistory;
begin
  FConversationHistory.Clear;
end;

end.

高级:创建特定领域的LLM应用

以下是如何创建特定领域的LLM应用,以代码助手为例:

unit CodeAssistant;

interface

uses
  System.SysUtils, System.Classes, LLMClient, PromptEngineering;

type
  TCodeAssistantMode = (camExplain, camRefactor, camGenerate, camDebug);

  TCodeAssistant = class
  private
    FLLMClient: TLLMClient;
    FPromptTemplates: TDictionary<TCodeAssistantMode, TPromptTemplate>;

    function GetModePrompt(Mode: TCodeAssistantMode; const Code, Language, AdditionalContext: string): string;
  public
    constructor Create(const ApiKey: string);
    destructor Destroy; override;

    function ExplainCode(const Code, Language: string): string;
    function RefactorCode(const Code, Language, Requirements: string): string;
    function GenerateCode(const Description, Language, Requirements: string): string;
    function DebugCode(const Code, Language, ErrorMessage: string): string;
  end;

implementation

{ TCodeAssistant }

constructor TCodeAssistant.Create(const ApiKey: string);
begin
  inherited Create;
  FLLMClient := TLLMClient.Create(ApiKey, lpOpenAI);
  FLLMClient.Model := 'gpt-4';
  FLLMClient.Temperature := 0.3; // 较低的温度,更确定性的输出

  // 初始化提示模板
  FPromptTemplates := TDictionary<TCodeAssistantMode, TPromptTemplate>.Create;

  // 解释代码模板
  FPromptTemplates.Add(camExplain, TPromptTemplate.Create(
    '请详细解释以下{language}代码的功能和工作原理,包括关键算法、数据结构和设计模式。' + sLineBreak +
    '使用通俗易懂的语言,并指出代码中的重要部分。' + sLineBreak + sLineBreak +
    '```{language}' + sLineBreak +
    '{code}' + sLineBreak +
    '```'));

  // 重构代码模板
  FPromptTemplates.Add(camRefactor, TPromptTemplate.Create(
    '请重构以下{language}代码,使其更加清晰、高效和易于维护。' + sLineBreak +
    '重构要求:{requirements}' + sLineBreak +
    '请保持代码的功能不变,并解释你所做的改变。' + sLineBreak + sLineBreak +
    '```{language}' + sLineBreak +
    '{code}' + sLineBreak +
    '```'));

  // 生成代码模板
  FPromptTemplates.Add(camGenerate, TPromptTemplate.Create(
    '请根据以下描述,生成{language}代码:' + sLineBreak +
    '{code}' + sLineBreak + sLineBreak +
    '要求:{requirements}' + sLineBreak +
    '请提供完整、可运行的代码,并添加必要的注释。'));

  // 调试代码模板
  FPromptTemplates.Add(camDebug, TPromptTemplate.Create(
    '请帮我调试以下{language}代码,找出并修复错误:' + sLineBreak +
    '```{language}' + sLineBreak +
    '{code}' + sLineBreak +
    '```' + sLineBreak + sLineBreak +
    '错误信息:{requirements}' + sLineBreak +
    '请解释错误原因,并提供修复后的代码。'));
end;

destructor TCodeAssistant.Destroy;
begin
  FLLMClient.Free;

  for var Template in FPromptTemplates.Values do
    Template.Free;
  FPromptTemplates.Free;

  inherited;
end;

function TCodeAssistant.GetModePrompt(Mode: TCodeAssistantMode; const Code, Language, AdditionalContext: string): string;
var
  Template: TPromptTemplate;
begin
  if FPromptTemplates.TryGetValue(Mode, Template) then
  begin
    Template.SetVariable('code', Code);
    Template.SetVariable('language', Language);
    Template.SetVariable('requirements', AdditionalContext);
    Result := Template.Render;
  end
  else
    Result := '';
end;

function TCodeAssistant.ExplainCode(const Code, Language: string): string;
begin
  FLLMClient.ClearConversation;
  Result := FLLMClient.SendMessage(GetModePrompt(camExplain, Code, Language, ''));
end;

function TCodeAssistant.RefactorCode(const Code, Language, Requirements: string): string;
begin
  FLLMClient.ClearConversation;
  Result := FLLMClient.SendMessage(GetModePrompt(camRefactor, Code, Language, Requirements));
end;

function TCodeAssistant.GenerateCode(const Description, Language, Requirements: string): string;
begin
  FLLMClient.ClearConversation;
  Result := FLLMClient.SendMessage(GetModePrompt(camGenerate, Description, Language, Requirements));
end;

function TCodeAssistant.DebugCode(const Code, Language, ErrorMessage: string): string;
begin
  FLLMClient.ClearConversation;
  Result := FLLMClient.SendMessage(GetModePrompt(camDebug, Code, Language, ErrorMessage));
end;

end.

实际应用案例

1. 智能文档生成器

// 使用LLM自动生成软件文档
procedure TDocumentationGenerator.GenerateDocumentation(const SourceCode, Language: string);
var
  LLMClient: TLLMClient;
  Prompt, Response: string;
begin
  LLMClient := TLLMClient.Create(ConfigManager.GetValue('OPENAI_API_KEY'), lpOpenAI);
  try
    LLMClient.Model := 'gpt-4';
    LLMClient.Temperature := 0.2;

    Prompt := Format(
      '请为以下%s代码生成详细的技术文档,包括:' + sLineBreak +
      '1. 功能概述' + sLineBreak +
      '2. 参数说明' + sLineBreak +
      '3. 返回值说明' + sLineBreak +
      '4. 使用示例' + sLineBreak +
      '5. 注意事项' + sLineBreak +
      '请使用Markdown格式。' + sLineBreak + sLineBreak +
      '```%s' + sLineBreak +
      '%s' + sLineBreak +
      '```',
      [Language, Language, SourceCode]
    );

    Response := LLMClient.SendMessage(Prompt);

    // 保存生成的文档
    SaveDocumentation(Response);

    // 显示结果
    ShowDocumentationPreview(Response);
  finally
    LLMClient.Free;
  end;
end;

2. 智能客户支持系统

// 使用LLM处理客户查询
function TCustomerSupportSystem.ProcessCustomerQuery(const Query: string): string;
var
  LLMClient: TLLMClient;
  KnowledgeBase: string;
  Prompt: string;
begin
  // 加载知识库
  KnowledgeBase := LoadKnowledgeBase;

  LLMClient := TLLMClient.Create(ConfigManager.GetValue('ANTHROPIC_API_KEY'), lpAnthropic);
  try
    LLMClient.Model := 'claude-2';

    Prompt := Format(
      '你是一个客户支持助手。请根据以下知识库信息回答客户的问题。' + sLineBreak +
      '如果知识库中没有相关信息,请礼貌地表示你无法回答,并建议客户联系人工客服。' + sLineBreak + sLineBreak +
      '知识库:' + sLineBreak +
      '%s' + sLineBreak + sLineBreak +
      '客户问题:%s',
      [KnowledgeBase, Query]
    );

    Result := LLMClient.SendMessage(Prompt);

    // 记录查询和回复
    LogCustomerInteraction(Query, Result);
  finally
    LLMClient.Free;
  end;
end;

3. 数据分析助手

// 使用LLM分析数据并生成报告
procedure TDataAnalysisAssistant.GenerateReport(const DataSummary, AnalysisRequirements: string);
var
  LLMClient: TLLMClient;
  Prompt, Report: string;
begin
  LLMClient := TLLMClient.Create(ConfigManager.GetValue('GOOGLE_API_KEY'), lpGoogle);
  try
    LLMClient.Model := 'gemini-pro';

    Prompt := Format(
      '请根据以下数据摘要生成一份详细的分析报告。' + sLineBreak +
      '分析要求:%s' + sLineBreak + sLineBreak +
      '数据摘要:' + sLineBreak +
      '%s' + sLineBreak + sLineBreak +
      '请包含以下内容:' + sLineBreak +
      '1. 执行摘要' + sLineBreak +
      '2. 关键发现' + sLineBreak +
      '3. 详细分析' + sLineBreak +
      '4. 建议' + sLineBreak +
      '5. 结论' + sLineBreak +
      '请使用Markdown格式,并添加适当的图表描述。',
      [AnalysisRequirements, DataSummary]
    );

    Report := LLMClient.SendMessage(Prompt);

    // 保存报告
    SaveReport(Report);

    // 显示报告预览
    ShowReportPreview(Report);
  finally
    LLMClient.Free;
  end;
end;

最佳实践与注意事项

  1. 提示设计:精心设计提示是获得良好结果的关键,包括明确指令、上下文和格式要求
  2. API密钥安全:不要在代码中硬编码API密钥,使用配置文件或环境变量
  3. 错误处理:实现完善的错误处理机制,处理网络问题和API限制
  4. 成本控制:监控API使用情况,实施合理的使用限制
  5. 用户体验:添加加载指示器和取消选项,提升用户体验
  6. 内容过滤:实现内容过滤机制,防止生成不适当的内容
  7. 版本管理:跟踪模型版本,确保应用与模型更新兼容

结论

通过在Delphi应用中集成大型语言模型,我们可以为传统应用注入强大的AI能力,显著提升用户体验和应用功能。从简单的文本生成到复杂的智能助手,LLM为Delphi开发者提供了广阔的创新空间。

随着LLM技术的不断发展和成本的降低,我们可以期待更多创新的LLM应用出现在Delphi开发的软件中。通过持续学习和实践,Delphi开发者可以充分利用这些技术,创建更智能、更有价值的应用程序。


关于作者:付乙,资深Delphi开发者,专注于将现代技术与传统应用相结合,提升软件价值和用户体验。