VFP AI 插件开发花絮14:RAG.dll 帮助文档V2
RAG.DLL 完整帮助文档
版本: 2.0
最后更新: 2026年4月11日
适用范围: RAG.DLL(多线程 COM 组件)
1. 概述
RAG.MTServer 是一个 OLE Public 类(继承自 Session),被编译为 多线程 COM DLL(RAG.DLL)。它为各种 COM 客户端(VFP、C#、VB.NET、X#、Python、Delphi 等)提供了与 大语言模型后端服务(如 AnythingLLM)交互的统一接口。
该类封装了以下核心功能:
-
后端服务的生命周期管理(启动、停止检测、状态监控) -
知识库工作区管理(获取工作区列表、预热) -
向量检索(不生成回答,仅搜索相似文档) -
完整的会话管理(创建、切换、保存、加载、删除、列表、清空) -
对话能力(支持上下文,记录 Token 使用量) -
错误处理与日志记录
ProgID:RAG.MTServer
线程模型: 多线程单元(MTA) – 支持多线程并发调用(但需注意内部对象的线程安全性,见“注意事项”)。
2. 注册
-
打开命令行:以管理员身份打开命令提示符(cmd)。
-
运行注册命令:使用
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 |
|
|
"AnythingLLM"(不区分大小写),预留 "RAGFLOW" |
tcApiKey |
|
|
|
tcExePath |
|
|
C:\AnythingLLM\AnythingLLM.exe) |
tcServerURL |
|
|
http://localhost:3001) |
tlLog |
|
|
.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)。后续的 Warmup、VectorSearch 等方法需要传入 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 对象,具有 cSessionId、nMessageCount 等属性)。
说明:
-
创建一个新的聊天会话,并自动将其设置为当前会话。 -
会话 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.。
说明: 可用于读取当前会话的属性(如 cSessionId、Name、nMessageCount 等)。
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": [ ... ]
}
说明: 核心对话方法。内部执行以下步骤:
-
检查服务是否运行、配置是否完整。 -
若没有当前会话,自动创建一个默认会话(名称 "默认会话")。 -
将用户消息( ChatMessage对象)添加到本地会话历史。 -
调用 AnythingLLM API POST /api/v1/workspace/{slug}/chat。 -
解析响应,创建助手消息并添加到本地会话,记录 Token 使用量。 -
返回完整响应 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. 错误代码定义
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
其他 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. 版本历史
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
9. 技术支持
如有问题,请联系开发者:
作者: xinjie
文档日期: 2026年4月11日
夜雨聆风