乐于分享
好东西不私藏

VFP AI 插件开发花絮14:RAG.dll 帮助文档V2

VFP AI 插件开发花絮14:RAG.dll 帮助文档V2

RAG.DLL 完整帮助文档

版本: 2.0
最后更新: 2026年4月11日
适用范围: RAG.DLL(多线程 COM 组件)


1. 概述

RAG.MTServer 是一个 OLE Public 类(继承自 Session),被编译为 多线程 COM DLLRAG.DLL)。它为各种 COM 客户端(VFP、C#、VB.NET、X#、Python、Delphi 等)提供了与 大语言模型后端服务(如 AnythingLLM)交互的统一接口。

该类封装了以下核心功能:

  • 后端服务的生命周期管理(启动、停止检测、状态监控)
  • 知识库工作区管理(获取工作区列表、预热)
  • 向量检索(不生成回答,仅搜索相似文档)
  • 完整的会话管理(创建、切换、保存、加载、删除、列表、清空)
  • 对话能力(支持上下文,记录 Token 使用量)
  • 错误处理与日志记录

ProgID:RAG.MTServer

线程模型: 多线程单元(MTA) – 支持多线程并发调用(但需注意内部对象的线程安全性,见“注意事项”)。

2. 注册

  1. 打开命令行:以管理员身份打开命令提示符(cmd)。

  2. 运行注册命令:使用 ‎regsvr32 工具注册 DLL。导航到 DLL 所在目录,执行:

regsvr32 RAG.dll

3. 方法详细说明

3.1 Set – 初始化后端配置

语法:
Object.Set(tcServerName As String, tcApiKey As String, tcExePath As String, tcServerURL As String, tlLog As Logical) As Logical

参数:

参数
类型
必选
说明
tcServerName
String
后端名称,目前仅支持 ‎"AnythingLLM"(不区分大小写),预留 ‎"RAGFLOW"
tcApiKey
String
API 密钥
tcExePath
String
后端可执行文件的完整路径(如 ‎C:\AnythingLLM\AnythingLLM.exe
tcServerURL
String
API 基础 URL(如 ‎http://localhost:3001
tlLog
Logical
是否启用日志记录(默认 ‎.T.

返回值:.T. 表示初始化调用成功(实际配置错误会在后续方法调用时体现)。

说明: 必须在使用任何其他方法之前调用 Set。该方法根据 tcServerName 创建对应的后端管理器,并保存配置信息。

示例:

loMT.Set("AnythingLLM", "sk-xxxx", "C:\AnythingLLM\AnythingLLM.exe", "http://localhost:3001", .T.)

3.2 IsRunning – 检查服务是否运行

语法:
Object.IsRunning() As Logical

返回值:.T. 服务正在运行且可访问;.F. 未运行或连接失败。

说明: 发送 GET /api/ping 请求检测服务状态。


3.3 Start – 启动后端服务

语法:
Object.Start(tnTimeoutSeconds As Number) As Logical

参数:

  • tnTimeoutSeconds – 可选,等待服务就绪的超时秒数(默认 30)。

返回值:.T. 启动成功;.F. 失败(可通过 GetStatus 获取错误原因)。

说明:

  • 若服务已在运行,直接返回 ‎.T.
  • 启动后会调用 ‎WaitForReady 循环检测服务响应,直至超时。

3.4 ListAllWorkSpaces – 获取所有工作区

语法:
Object.ListAllWorkSpaces() As Object

返回值:

  • 字符串 – 如果只有一个工作区,返回该工作区的 ‎slug(如 ‎"default")。
  • COM 对象(JSON) – 如果有多个工作区,返回 JSON 对象,结构为:

    {
      "workspaces": [
        { "name": "Workspace1", "slug": "slug1", ... },
        { "name": "Workspace2", "slug": "slug2", ... }
      ]
    }
  • .NULL. – 请求失败(网络错误、无工作区等)。

说明: 用于获取可用的工作区标识符(slug)。后续的 WarmupVectorSearch 等方法需要传入 slug。

注意: 若没有任何工作区,API 可能返回空数组,此时访问 workspaces[0] 会出错。调用前请确保至少创建一个工作区。


3.5 Warmup – 预热知识库

语法:
Object.Warmup(tcWorkspaceSlug As String, tcApiKey As String) As Logical

参数:

  • tcWorkspaceSlug – 可选,工作区 slug(默认使用 ‎Set 配置的)。
  • tcApiKey – 可选,API 密钥(默认使用 ‎Set 配置的)。

返回值:.T. 预热成功;.F. 失败。

说明: 发送一个虚拟查询("warmup"topN=1),将知识库的向量数据加载到内存,从而加快后续真实的向量检索和对话的响应速度。


3.6 VectorSearch – 纯向量检索

语法:
Object.VectorSearch(tcQuery As String, tnTopN As Integer, tcWorkspaceSlug As String, tcApiKey As String) As Object

参数:

  • tcQuery – 必需,查询文本。
  • tnTopN – 可选,返回最相似文档片段的数量(默认 5)。
  • tcWorkspaceSlug – 可选,工作区 slug。
  • tcApiKey – 可选,API 密钥。

返回值: JSON 对象(包含检索结果),失败返回 .NULL.

返回 JSON 结构示例:

{
  "results": [
    { "text": "文档片段内容...", "score": 0.85, "metadata": {...} },
    ...
  ]
}

说明: 不调用 LLM 生成回答,仅从知识库中检索与查询最相似的文档片段。适用于需要获取原始参考资料而不需要对话的场景。


3.7 CreateSession – 创建新会话

语法:
Object.CreateSession(tcName As String, tcThreadSlug As String) As Object

参数:

  • tcName – 可选,会话显示名称。
  • tcThreadSlug – 可选,线程标识(用于区分同一用户的多个对话分支)。

返回值:ChatSession 对象(COM 对象,具有 cSessionIdnMessageCount 等属性)。

说明:

  • 创建一个新的聊天会话,并自动将其设置为当前会话。
  • 会话 ID 自动生成(格式:‎YYYYMMDD + SYS(2015),例如 ‎20260407_7FR1DI1WR)。
  • 新会话被添加到内存缓存中,可通过 ‎SaveCurrentSession 持久化到磁盘。
  • 后续的 ‎SendMessage / ‎SendMessageText 将针对该会话。

3.8 SwitchSession – 切换当前会话

语法:
Object.SwitchSession(tcSessionId As String) As Logical

参数:

  • tcSessionId – 必需,要切换到的会话 ID。

返回值:.T. 切换成功;.F. 会话不存在或加载失败。

说明:

  • 如果指定会话已在内存缓存中,直接切换。
  • 如果不在内存中,会尝试从磁盘文件加载该会话(调用 ‎LoadSession)。
  • 切换后,后续的消息操作将针对新会话。

3.9 GetCurrentSession – 获取当前会话对象

语法:
Object.GetCurrentSession() As Object

返回值: 当前 ChatSession 对象,若没有当前会话则返回 .NULL.

说明: 可用于读取当前会话的属性(如 cSessionIdNamenMessageCount 等)。


3.10 SaveCurrentSession – 保存当前会话

语法:
Object.SaveCurrentSession() As Logical

返回值:.T. 保存成功;.F. 失败。

说明:

  • 将当前会话序列化为 JSON 文件。
  • 文件命名规则:‎<会话ID>.json
  • 存储目录:默认为当前工作目录下的 ‎AnythingLLM_Session 文件夹(若不存在会自动创建)。
  • 保存内容包括:会话元数据、所有消息(角色、内容、时间戳、Token 使用量等)。

3.11 LoadSession – 加载指定会话

语法:
Object.LoadSession(tcSessionId As String) As Object

参数:

  • tcSessionId – 必需,会话 ID。

返回值: 加载的 ChatSession 对象,失败返回 .NULL.

说明:

  • 从磁盘 JSON 文件加载指定会话,并将其设置为当前会话。
  • 加载时会恢复所有消息历史。
  • 加载成功后会将会话加入内存缓存。

3.12 DeleteSession – 删除会话

语法:
Object.DeleteSession(tcSessionId As String) As Logical

参数:

  • tcSessionId – 必需,要删除的会话 ID。

返回值:.T. 删除成功;.F. 失败。

说明:

  • 从内存缓存中移除该会话。
  • 删除对应的磁盘 JSON 文件。
  • 如果删除的正是当前会话,则将当前会话指针设为 ‎.NULL.

3.13 ListSessions – 列出所有会话

语法:
Object.ListSessions() As Object

返回值: VFP Collection 对象(COM 可枚举),包含所有已保存会话的 完整 ChatSession 对象(包括全部消息)。

说明:

  • 遍历 ‎AnythingLLM_Session 目录下的所有 ‎.json 文件,加载每个会话的完整内容。
  • 性能警告: 当会话数量多或单个会话消息量大时,此方法会消耗较多内存和时间。建议仅在管理界面使用,避免频繁调用。

3.14 ClearCurrentSession – 清空当前会话消息

语法:
Object.ClearCurrentSession()

说明: 清空当前会话中的所有消息(保留会话 ID、名称、创建时间等元数据)。常用于开始新的对话主题但希望保持同一会话标识。


3.15 SendMessage – 发送消息(完整响应)

语法:
Object.SendMessage(tcMessage As String, tcMode As String, tcSessionID As String) As Object

参数:

  • tcMessage – 必需,用户输入的消息。
  • tcMode – 可选,对话模式(‎"chat" – 普通对话,‎"query" – 仅检索,默认 ‎"chat")。
  • tcSessionID – 可选,传递给 API 的会话 ID(用于 AnythingLLM 内部上下文关联)。若不提供,则使用本地当前会话的 ID。

返回值: JSON 对象,失败返回 .NULL.

返回 JSON 结构示例:

{
  "textResponse": "助手的回答内容...",
  "metrics": {
    "total_tokens": 150,
    "prompt_tokens": 50,
    "completion_tokens": 100
  },
  "sources": [ ... ]
}

说明: 核心对话方法。内部执行以下步骤:

  1. 检查服务是否运行、配置是否完整。
  2. 若没有当前会话,自动创建一个默认会话(名称 ‎"默认会话")。
  3. 将用户消息(‎ChatMessage 对象)添加到本地会话历史。
  4. 调用 AnythingLLM API ‎POST /api/v1/workspace/{slug}/chat
  5. 解析响应,创建助手消息并添加到本地会话,记录 Token 使用量。
  6. 返回完整响应 JSON。

注意:

  • 当前版本未自动将本地会话历史发送给 API,若要实现多轮对话,请在调用时传递相同的 ‎tcSessionID 参数(可使用当前会话的 ‎cSessionId),AnythingLLM 会基于该 ID 维护上下文。
  • 模式 ‎"query" 仅执行检索而不生成回答,返回格式略有不同。

3.16 SendMessageText – 发送消息(仅文本)

语法:
Object.SendMessageText(tcMessage As String, tcMode As String) As String

参数:

  • tcMessage – 必需,用户消息。
  • tcMode – 可选,模式(默认 ‎"chat")。

返回值: 字符串,仅包含助手的文本回复。若失败返回空字符串。

说明: 简化版 SendMessage,内部调用 SendMessage 并提取 textResponse 字段。适用于只需要回复文本的场景。


3.17 GetStatus – 获取服务状态

语法:
Object.GetStatus() As Object

返回值: JSON 对象,包含以下字段:

{
  "isRunning": true,
  "serverUrl": "http://localhost:3001",
  "workspaceConfigured": true,
  "apiKeyConfigured": true,
  "lastError": "",
  "lastErrorCode": 0,
  "hasCurrentSession": true,
  "currentSessionId": "20260411123456789",
  "currentSessionMessages": 5
}

说明: 用于监控和调试,可快速了解服务状态和当前会话信息。


3.18 GetDebugObject – 调试辅助(仅开发环境)

语法:
Object.GetDebugObject() As Object

返回值: 一个新的 MTServer 对象(在 VFP IDE 环境中创建)。

说明:

  • 专用于 COM 调试。当从外部 COM 客户端调用 DLL 时,无法直接使用 VFP 调试器设置断点。
  • 调用此方法会返回一个在 VFP IDE 中创建的对象,从而允许开发者在 VFP 环境中单步跟踪代码。
  • 生产环境不应使用此方法。

典型用法(在客户端代码中):

* VFP 客户端调试
loMT = CREATEOBJECT("RAG.MTServer")
IF _DEBUG
    loMT = loMT.GetDebugObject()
ENDIF

4. 错误代码定义

错误代码
说明
0
无错误
-1
连接失败(服务未启动或网络问题)
-2
启动服务失败(如找不到可执行文件)
-3
启动超时
-4
配置缺失(工作区 Slug 或 API Key 未设置)
-5
服务未运行
-6
向量检索异常
-7
发送消息异常
-9
JSON 解析错误
-10
会话不存在
-11
保存会话失败
-12
加载会话失败
-13
删除会话失败
400
HTTP 400 – 请求格式错误
401
HTTP 401 – API Key 无效
403
HTTP 403 – 无权限
404
HTTP 404 – 资源不存在(如工作区)
429
HTTP 429 – 请求频率过高
500
HTTP 500 – 服务器内部错误
503
HTTP 503 – 服务不可用

其他 HTTP 状态码直接返回该状态码作为错误代码。


5. 使用示例

5.1 VFP 客户端示例

示例 1:基础对话

LOCAL loMT, loStatus, lcSlug, loResponse

* 创建 COM 对象
loMT = CREATEOBJECT("RAG.MTServer")

* 初始化
IF NOT loMT.Set("AnythingLLM", "your-api-key", "C:\AnythingLLM\AnythingLLM.exe", "http://localhost:3001", .T.)
    ? "Set 失败"
    RETURN
ENDIF

* 启动服务
IF NOT loMT.IsRunning()
    IF NOT loMT.Start(30)
        loStatus = loMT.GetStatus()
        ? "启动失败:" + loStatus.lastError
        RETURN
    ENDIF
ENDIF

* 获取工作区 slug
lcSlug = loMT.ListAllWorkSpaces()
DO CASE
    CASE VARTYPE(lcSlug) = "C"
        * 只有一个工作区
    CASE VARTYPE(lcSlug) = "O" AND NOT ISNULL(lcSlug)
        lcSlug = lcSlug.workspaces.Item(0).slug
    OTHERWISE
        ? "获取工作区失败"
        RETURN
ENDCASE

* 创建会话
loMT.CreateSession("测试会话", "")

* 发送消息
loResponse = loMT.SendMessage("VFP 如何调用 COM 对象?", "chat", "")
IF NOT ISNULL(loResponse)
    ? "助手回答:" + loResponse.textResponse
    ? "Token 使用量:" + TRANSFORM(loResponse.metrics.total_tokens)
ELSE
    ? "发送失败:" + loMT.GetStatus().lastError
ENDIF

* 保存会话
loMT.SaveCurrentSession()

示例 2:多轮对话(利用 sessionId)

LOCAL loMT, lcSessionId, loResp1, loResp2

loMT = CREATEOBJECT("RAG.MTServer")
loMT.Set("AnythingLLM", "your-key", "C:\AnythingLLM\AnythingLLM.exe", "http://localhost:3001", .T.)
loMT.Start(30)

* 创建会话并获取其 ID
lcSessionId = loMT.CreateSession("多轮对话", "").cSessionId

* 第一轮
loResp1 = loMT.SendMessage("我叫张三", "chat", lcSessionId)
? loResp1.textResponse

* 第二轮
loResp2 = loMT.SendMessage("我叫什么名字?", "chat", lcSessionId)
? loResp2.textResponse   && 应输出 "张三"

* 保存会话
loMT.SwitchSession(lcSessionId)
loMT.SaveCurrentSession()

示例 3:向量检索

LOCAL loMT, loResult, lnI

loMT = CREATEOBJECT("RAG.MTServer")
loMT.Set("AnythingLLM", "your-key", "C:\AnythingLLM\AnythingLLM.exe", "http://localhost:3001", .T.)
loMT.Start(30)

loResult = loMT.VectorSearch("VFP COM 调试", 5, "", "")
IF NOT ISNULL(loResult)
    FOR lnI = 0 TO loResult.results.Length - 1
        ? "结果 " + TRANSFORM(lnI + 1)
        ? "片段:" + loResult.results.Item(lnI).text
        ? "得分:" + TRANSFORM(loResult.results.Item(lnI).score)
    ENDFOR
ENDIF

示例 4:会话管理(切换、删除、清空)

LOCAL loMT, loSession1, loSession2

loMT = CREATEOBJECT("RAG.MTServer")
loMT.Set("AnythingLLM", "your-key", "C:\AnythingLLM\AnythingLLM.exe", "http://localhost:3001", .T.)
loMT.Start(30)

loSession1 = loMT.CreateSession("会话A", "")
loSession2 = loMT.CreateSession("会话B", "")

* 切换到会话A 并发送消息
loMT.SwitchSession(loSession1.cSessionId)
loMT.SendMessageText("这是会话A的消息")

* 切换到会话B 并发送消息
loMT.SwitchSession(loSession2.cSessionId)
loMT.SendMessageText("这是会话B的消息")

* 删除会话A
loMT.DeleteSession(loSession1.cSessionId)

* 清空当前会话(会话B)的所有消息
loMT.ClearCurrentSession()

* 列出剩余会话
LOCAL loSessions = loMT.ListSessions()
FOR EACH loSess IN loSessions
    ? loSess.Name, loSess.nMessageCount
ENDFOR

6. 注意事项

6.1 线程安全与多线程环境

  • COM 线程模型: DLL 声明为多线程单元(MTA),但内部 VFP 对象并非完全线程安全。
  • 建议: 每个线程创建独立的 ‎MTServer 实例,避免共享同一个对象。不要在多个线程中同时调用同一实例的方法。
  • 服务启动: 多个实例同时调用 ‎Start 可能导致重复启动进程。应在应用层确保服务单例启动(例如使用互斥体)。

6.2 文件并发访问

  • 会话文件(‎AnythingLLM_Session\*.json)可能被多个线程同时读写。建议将会话 ID 与线程绑定,避免跨线程共享会话。
  • 如果确实需要多线程访问同一会话,需自行实现文件锁机制。

6.3 32 位限制

  • VFP COM DLL 是 32 位 进程。调用方(如 C#、X# 应用)必须以 32 位模式 运行,或使用进程外 COM(DLLSurrogate)包装。

6.4 COM 内存管理

  • 客户端使用完 COM 对象后应显式释放:
    • VFP: 将对象变量设为 ‎.NULL.
    • X# / C#: 调用 ‎System.Runtime.InteropServices.Marshal.ReleaseComObject 或将对象设为 ‎null
  • 否则可能导致 DLL 无法卸载,内存泄漏。

6.5 日志文件

  • 默认日志文件为当前工作目录下的 ‎AnythingLLM.log
  • 多线程环境下日志可能交错,但不影响功能。
  • 若以 IIS 或 Windows 服务宿主,请确保进程有写入权限。

6.6 工作区列表为空的处理

  • 调用 ‎ListAllWorkSpaces 前,请确保 AnythingLLM 中至少存在一个工作区。否则 API 返回空数组,访问 ‎Item(0) 会引发错误。

6.7 预热(Warmup)的必要性

  • 首次查询(尤其是大知识库)可能较慢,建议在启动后调用一次 ‎Warmup 将向量加载到内存,可显著提高后续响应速度。

6.8 调试方法

  • 若需调试 DLL 内部代码,可在客户端调用 ‎GetDebugObject() 获取一个在 VFP IDE 中运行的对象,然后在 VFP 中设置断点(需要同时打开 VFP 项目)。

7. 常见问题(FAQ)

Q1:启动服务时一直超时怎么办?
A:检查 cexepath 是否正确,且 AnythingLLM 是否正常启动。可以尝试手动运行 exe 观察输出。另外,首次启动可能因加载模型较慢,适当增加 tnTimeoutSeconds 值(如 60 秒)。

Q2:发送消息返回 .NULL.,如何获取详细错误?
A:调用 GetStatus() 方法,查看 lastError 和 lastErrorCode 字段。

Q3:如何实现多轮对话?
A:在每次调用 SendMessage 时,传递相同的 tcSessionID 参数(例如使用当前会话的 cSessionId)。AnythingLLM 会基于该 ID 自动维护对话历史。

Q4:为什么 ListSessions 很慢?
A:该方法会加载每个会话的完整消息内容。若会话数量多或消息量大,建议仅用于管理界面,或自行实现摘要列表方法。

Q5:能否在 64 位程序中使用?
A:不能直接使用。VFP COM DLL 为 32 位,64 位程序可通过以下方式调用:
 - 编译为 32 位目标平台。
 - 使用进程外 COM(配置 DLLSurrogate)。
 - 使用中间件(如 HTTP 服务)桥接。


8. 版本历史

日期
版本
变更说明
2026.04.01
1.0
初始版本
2026.04.11
2.0
添加会话管理和对话能力

9. 技术支持

如有问题,请联系开发者:
作者: xinjie
文档日期: 2026年4月11日