Unity集成LLM与向量数据库:脚本架构解析与DuckDB知识库优化实践

发布时间:2026/8/9 17:26:10
Unity集成LLM与向量数据库:脚本架构解析与DuckDB知识库优化实践 1. 项目概述当Unity遇上LLM与向量数据库最近在折腾一个挺有意思的项目把大语言模型LLM集成到Unity里用的框架是LLMUnity。做到第五期遇到了两个核心的“坎儿”一个是项目里那些脚本的继承关系像一团乱麻不梳理清楚根本没法进行后续的扩展和维护另一个是关于知识库的随着内容越来越多之前那种简单的内存存储或者文件搜索越来越力不从心我开始琢磨用DuckDB这个轻量级数据库来给它升个级特别是想利用它的向量搜索能力。这不仅仅是换个存储工具更是对整个智能体知识检索架构的一次重新思考。如果你也在Unity里搞AI应用或者对如何高效管理、检索非结构化数据比如文本、对话记录感兴趣那接下来的内容可能会对你很有帮助。我们将深入代码结构并探讨一个更优雅的数据解决方案。2. 核心需求与挑战解析2.1 为何要理清LLMUnity的脚本继承关系LLMUnity作为一个将LLM能力引入Unity的框架其内部结构为了支持对话、工具调用、记忆等复杂功能必然会设计出一套相对复杂的类层次结构。对于使用者来说尤其是当你需要自定义行为、扩展功能或者仅仅是调试一个诡异的问题时如果不理解这些脚本C#类之间的继承与组合关系就会像在迷宫里乱撞。主要痛点体现在自定义扩展困难你想创建一个新的“工具”Tool让AI能调用你游戏里的特定功能。如果不清楚基础的Tool类、LLMClient基类是如何工作的继承哪个类、重写哪个方法都会成为难题。调试成本高昂当对话逻辑出现异常或者消息流没有按预期传递时你需要知道调用栈经过了哪些类的哪些方法。清晰的继承关系图是快速定位问题的路线图。理解框架设计思想通过继承关系你能看出框架作者是如何抽象不同LLM提供商如OpenAI、Claude本地模型的接口如何管理对话状态这能提升你使用框架的“内力”而不仅仅是调用API。2.2 为何选择DuckDB升级知识库在LLM应用中知识库Knowledge Base用于存储和检索额外的信息以增强模型的回答能力。LLMUnity本身可能提供了一些基础的搜索方式比如基于关键词的全文匹配或在内存中进行简单的向量相似度计算。但当知识库规模增长到成千上万条文档时这些方法就会暴露出瓶颈性能问题在Unity的主线程中进行大规模的向量计算或复杂查询会导致游戏帧率下降体验卡顿。功能单一简单的内存搜索往往只支持最基础的相似度匹配缺乏高效的过滤、聚合、多条件查询等数据库级操作。数据管理不便知识的增删改查、版本管理、持久化到磁盘用文件或内存对象手动管理会非常繁琐且易出错。DuckDB的优势正好切中这些痛点进程内数据库无需安装和运行独立的数据库服务器直接以库的形式嵌入到Unity项目中部署和分发极其简单。卓越的分析性能尤其擅长复杂的查询和向量化计算这对于需要快速从海量知识中找出最相关片段的场景至关重要。原生支持向量搜索通过其扩展机制如vector扩展或与litefs等集成可以高效地执行余弦相似度等向量运算这是现代知识库检索的核心。轻量级与零管理相比传统的关系型数据库它几乎没有运维开销非常适合游戏这种客户端环境。注意引入DuckDB意味着架构上的变化从“应用内计算”转向“嵌入式数据库查询”。你需要评估项目对安装包大小、运行时内存的额外开销是否敏感。3. LLMUnity核心脚本继承关系深度拆解要理清继承关系最好的方法是结合源码和UML类图思维。由于我无法直接展示LLMUnity的完整源码我将根据其常见设计模式和使用体验重构出其核心类的典型继承与关联结构。请务必对照你手头的LLMUnity版本源码进行验证。3.1 通信层与客户端基类这是与LLM服务端无论是云端API还是本地模型打交道的核心层。BaseLLMClient(抽象基类)职责定义与LLM交互的通用接口。它抽象了发送消息、接收流式响应、处理错误等基本操作。关键方法SendRequestAsync,CreateChatCompletion等。为什么需要它为了支持不同的LLM后端OpenAI GPT, Claude, 本地Llama.cpp等需要一个统一的接口。这是依赖倒置原则的体现高层模块如对话管理器不依赖具体客户端而是依赖这个抽象。OpenAIClient/ClaudeClient/LocalLLMClient(具体实现类)继承关系OpenAIClient : BaseLLMClient职责实现基类定义的接口处理各自API特有的请求格式、认证方式和响应解析。内部细节例如OpenAIClient会处理api-key的携带将对话历史格式化为OpenAI API要求的messages数组而LocalLLMClient可能会通过HTTP或本地管道与一个运行的模型进程通信。实操心得 在调试时如果发现网络请求总是失败首先应该检查的是这些具体客户端类的配置API端点、密钥和日志。一个常见的坑是在Unity WebGL平台下由于浏览器的CORS限制直接访问非同源API会失败。这时LocalLLMClient通过本地代理或配置了正确CORS代理的客户端就显得尤为重要。3.2 对话与消息管理这一层管理对话的上下文、状态和消息流。Conversation或DialogueManager类职责维护一个对话会话Session。它持有BaseLLMClient的实例负责组织用户输入、系统指令、历史消息并调用客户端获取AI回复。关键属性ListMessage messageHistory,SystemPrompt。关键方法SendMessageAsync,ResetConversation。与客户端的关系通常是组合Composition关系即Conversation内部有一个BaseLLMClient成员。这样可以在运行时切换不同的客户端。Message类职责封装单条消息的数据结构。通常包含角色user,assistant,system、内容和可能的元数据。继承关系通常比较简单但框架可能会扩展出ToolCallMessage、FunctionCallMessage等来支持复杂交互。这些扩展类很可能继承自一个基础的Message类。继承关系示例推测BaseMessage ├── TextMessage (role, content) ├── SystemMessage : TextMessage ├── ToolCallMessage (tool_call_id, function_name, arguments) └── ToolResultMessage (tool_call_id, content)这种结构允许对话管理器统一处理不同类型的消息同时在需要时能访问特殊字段。3.3 工具Tools与函数调用Function Calling系统这是LLMUnity实现“AI能操作游戏世界”的关键。Tool(抽象基类或接口)职责定义一个可供AI调用的工具。它描述了工具的名称、描述、参数schema符合JSON Schema格式。关键方法GetSchema(),ExecuteAsync(Dictionarystring, object parameters)。为什么设计成基类/接口为了允许开发者轻松创建自定义工具。任何你想让AI执行的操作如“打开宝箱”、“查询玩家状态”都可以封装成一个Tool的实现类。ToolRegistry或ToolManager类职责集中注册和管理所有可用的Tool实例。在对话开始时它会将注册的所有工具的描述信息作为“系统提示”的一部分或通过特定API告知LLM。与Conversation的关系Conversation会引用ToolRegistry。当LLM的回复中包含了工具调用请求时Conversation会从ToolRegistry中找到对应的Tool实例并执行。实操步骤创建一个自定义工具定义工具类新建一个C#脚本继承自Tool基类假设基类名为ToolBase。public class OpenChestTool : ToolBase { public override string Name open_chest; public override string Description 打开一个指定的宝箱; // 定义参数宝箱ID public override JObject GetParametersSchema() { return new JObject { [type] object, [properties] new JObject { [chestId] new JObject { [type] string, [description] 宝箱的唯一标识符 } }, [required] new JArray { chestId } }; } // 实现执行逻辑 public override async TaskJToken ExecuteAsync(JObject parameters) { string chestId parameters[chestId]?.ToString(); // 在这里编写实际打开宝箱的游戏逻辑 bool success GameManager.Instance.OpenChest(chestId); return new JObject { [result] success ? 宝箱已打开获得金币 : 打开宝箱失败。 }; } }注册工具在游戏初始化时如Awake方法中将工具实例添加到ToolRegistry。void Start() { var registry FindObjectOfTypeToolRegistry(); registry.RegisterTool(new OpenChestTool()); }注意事项参数Schema要精确LLM依赖于你提供的Schema来生成正确的调用参数。描述不清会导致LLM传错参数。执行方法要安全ExecuteAsync内是AI直接触发的游戏逻辑务必做好参数校验和错误处理防止AI的“胡言乱语”导致游戏崩溃或产生意外行为。异步处理工具执行可能是耗时的如等待一个网络请求因此设计成async方法很重要避免阻塞主线程。3.4 记忆Memory与知识库Knowledge Base抽象层这是连接我们第二个主题——DuckDB升级——的关键层。IMemory或IKnowledgeBase(接口)职责定义存储和检索信息的契约。这是面向接口编程的典型应用为后续替换不同的存储后端如内存、DuckDB、其他向量数据库提供了可能。关键方法StoreAsync(string key, object value),RetrieveAsync(string key),SearchAsync(string query, int topK)对于向量知识库。SimpleMemory(默认实现)实现关系SimpleMemory : IMemory职责一个基于Dictionarystring, object的简单内存实现。用于快速原型开发或存储临时会话状态。局限性数据易失游戏重启即丢失检索能力弱通常只是键值查找不支持复杂语义搜索。VectorKnowledgeBase(我们即将用DuckDB增强的目标)实现关系VectorKnowledgeBase : IKnowledgeBase职责存储文本文档及其对应的向量嵌入Embedding并提供基于向量相似度的语义搜索功能。原有问题LLMUnity自带的向量知识库实现可能在内存中计算相似度或者使用非常基础的本地文件存储在数据量大时面临性能和功能瓶颈。继承与组合关系图简化BaseLLMClient |-- OpenAIClient BaseLLMClient |-- LocalLLMClient Conversation o-- BaseLLMClient (持有客户端) Conversation o-- ToolRegistry (持有工具注册表) Conversation o-- IMemory (持有记忆/知识库接口) ToolRegistry *-- Tool (管理多个工具) IMemory |-- SimpleMemory IMemory |-- VectorKnowledgeBase (计划中DuckDBBackedKnowledgeBase) Tool |-- OpenChestTool (开发者自定义) Tool |-- GetWeatherTool理解这张关系图你就掌握了LLMUnity的“骨架”。接下来我们就要给这个骨架的“记忆”部分换上更强大的引擎——DuckDB。4. 用DuckDB重构向量知识库设计与实现4.1 为什么是DuckDB横向对比与选型思考在考虑升级知识库时我们有几个候选SQLite通过扩展支持向量、专门的向量数据库如Chroma、Weaviate以及DuckDB。特性SQLite 向量扩展专用向量数据库 (如Chroma)DuckDB部署复杂度低SQLite已是Unity常用插件中高需单独部署服务或集成客户端库极低单文件或动态库嵌入查询性能一般扩展的向量搜索优化有限高为向量搜索专门优化非常高列式存储与向量化执行引擎功能丰富度标准SQL依赖扩展专注于向量操作其他功能弱强大的分析型SQL支持复杂过滤、聚合Unity集成度好有成熟插件差可能需要处理网络通信或复杂的本地依赖较好需集成C库或使用C#包装器学习/开发成本低熟悉SQL即可中需学习新的API和概念中需学习DuckDB特有SQL函数和性能调优选择DuckDB的核心理由性能与功能平衡它在提供接近专业向量数据库的搜索性能的同时保留了完整SQL的灵活性。例如你可以轻松写出这样的查询“找出与用户问题最相似的5条知识且这些知识的创建时间在最近一个月内并属于‘武器说明’类别”。这在纯向量数据库中可能需要进行多次查询和客户端合并。进程内零运维对于单机版游戏或应用无需管理外部服务极大简化了部署和调试。活跃的社区与扩展DuckDB的vector扩展正在快速发展对向量运算的支持越来越好。4.2 数据库表结构设计我们需要设计表来存储知识条目文档及其向量。一个经典的设计如下knowledge表id(INTEGER PRIMARY KEY): 主键。content(TEXT): 知识的原始文本内容。embedding(FLOAT[] 或 BLOB): 文本内容通过嵌入模型如text-embedding-3-small计算得到的向量。DuckDB的vector扩展提供了VECTOR(FLOAT, 维度)类型来高效存储。metadata(JSON): 一个JSON字段用于存储类别、标签、来源、创建时间等任意元数据。这利用了DuckDB优秀的JSON支持。created_at(TIMESTAMP): 创建时间用于排序和过滤。创建表的SQL示例-- 假设已加载 vector 扩展 INSTALL vector; LOAD vector; CREATE TABLE knowledge ( id INTEGER PRIMARY KEY, content TEXT NOT NULL, embedding VECTOR(FLOAT, 1536), -- 以OpenAI text-embedding-3-small的1536维为例 metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 为了加速向量相似度搜索可以创建向量索引如果扩展支持 -- CREATE INDEX idx_embedding ON knowledge USING ivfflat (embedding) WITH (lists 100);注意向量索引的创建需要权衡。它能极大加速搜索但会增加数据插入和存储开销。对于知识库频繁更新的场景需要测试索引维护的成本。初期数据量不大时可以暂不创建索引DuckDB的全表扫描在向量化引擎下也可能很快。4.3 实现 DuckDBBackedKnowledgeBase 类现在我们来创建LLMUnity的IKnowledgeBase接口的DuckDB实现。第一步项目集成DuckDB对于Unity通常需要通过其包管理器UPM或直接导入DLL的方式集成DuckDB的C#客户端库如DuckDB.NET或libduckdb的C#包装器。确保你的目标平台Windows、Mac、Linux、甚至WebGL有可用的原生库。第二步实现核心类using System; using System.Collections.Generic; using System.Threading.Tasks; using DuckDB.NET.Data; // 使用 DuckDB.NET 库为例 using Newtonsoft.Json.Linq; public class DuckDBKnowledgeBase : IKnowledgeBase { private readonly string _dbPath; private readonly int _embeddingDimension; private DuckDBConnection _connection; public DuckDBKnowledgeBase(string dbPath knowledge.db, int embeddingDimension 1536) { _dbPath dbPath; _embeddingDimension embeddingDimension; } public async Task InitializeAsync() { // 建立数据库连接 _connection new DuckDBConnection($Data Source{_dbPath};); await _connection.OpenAsync(); // 确保表存在 using (var cmd _connection.CreateCommand()) { cmd.CommandText CREATE TABLE IF NOT EXISTS knowledge ( id INTEGER PRIMARY KEY, content TEXT NOT NULL, embedding VECTOR(FLOAT, dim), metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); cmd.Parameters.Add(new DuckDBParameter(dim, _embeddingDimension)); await cmd.ExecuteNonQueryAsync(); } } // 存储知识需要先调用嵌入模型API将文本转为向量 public async Task StoreAsync(string content, JObject metadata null) { // 1. 调用嵌入模型获取向量 (这里需要你实现或调用一个EmbeddingService) float[] embeddingVector await EmbeddingService.GetEmbeddingAsync(content); // 2. 将向量数组转换为DuckDB接受的格式例如逗号分隔的字符串或参数化 string vectorString string.Join(,, embeddingVector); using (var cmd _connection.CreateCommand()) { cmd.CommandText INSERT INTO knowledge (content, embedding, metadata) VALUES (content, embedding, metadata); cmd.Parameters.Add(new DuckDBParameter(content, content)); // 注意DuckDB.NET对VECTOR类型的支持可能需要特定处理以下为示例 // 实际可能需要使用 cmd.Parameters.AddWithValue(embedding, DuckDBType.Vector, embeddingVector); cmd.Parameters.Add(new DuckDBParameter(embedding, $[{vectorString}])); // 示例性写法 cmd.Parameters.Add(new DuckDBParameter(metadata, metadata?.ToString())); await cmd.ExecuteNonQueryAsync(); } } // 语义搜索核心功能 public async TaskListKnowledgeItem SearchAsync(string query, int topK 5, JObject filter null) { // 1. 将查询文本也转换为向量 float[] queryVector await EmbeddingService.GetEmbeddingAsync(query); string queryVectorStr string.Join(,, queryVector); // 2. 构建SQL查询使用向量余弦相似度函数 string sql SELECT id, content, metadata, -- 计算余弦相似度值越大越相似 1 - (embedding - queryVec) as similarity FROM knowledge WHERE 11; // 3. 动态添加元数据过滤条件示例过滤特定类别 if (filter ! null filter[category] ! null) { sql AND metadata-category category; } sql ORDER BY similarity DESC LIMIT topK; var results new ListKnowledgeItem(); using (var cmd _connection.CreateCommand()) { cmd.CommandText sql; cmd.Parameters.Add(new DuckDBParameter(queryVec, $[{queryVectorStr}])); cmd.Parameters.Add(new DuckDBParameter(topK, topK)); if (filter ! null filter[category] ! null) { cmd.Parameters.Add(new DuckDBParameter(category, filter[category].ToString())); } using (var reader await cmd.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { results.Add(new KnowledgeItem { Id reader.GetInt32(0), Content reader.GetString(1), Metadata JObject.Parse(reader.GetString(2)), Similarity reader.GetDouble(3) }); } } } return results; } // 其他接口方法实现按需更新、删除等 public async Task UpdateAsync(int id, string content null, JObject metadata null) { /* ... */ } public async Task DeleteAsync(int id) { /* ... */ } public void Dispose() { _connection?.Close(); _connection?.Dispose(); } } public class KnowledgeItem { public int Id { get; set; } public string Content { get; set; } public JObject Metadata { get; set; } public double Similarity { get; set; } // 相似度得分 }关键点解析嵌入向量化StoreAsync和SearchAsync都需要调用一个外部的EmbeddingService。这可以是一个封装了OpenAI Embeddings API、本地SentenceTransformers模型或其他嵌入模型的组件。这是整个系统的核心预处理步骤。向量相似度计算SQL中的embedding - queryVec是DuckDB向量扩展提供的运算符通常表示欧氏距离L2。为了得到余弦相似度我们使用了转换1 - (embedding - queryVec)前提是向量已归一化。更准确的做法是使用cosine_similarity(embedding, queryVec)函数如果扩展提供。元数据过滤利用DuckDB对JSON的原生支持metadata-category我们可以轻松实现基于元数据的过滤这是纯向量数据库有时需要额外工作才能实现的功能。连接管理DuckDBConnection是轻量级的但最好在知识库生命周期内保持打开或使用连接池。注意在Unity退出时妥善关闭和释放连接。4.4 在LLMUnity中集成新的知识库最后一步是将我们新的DuckDBKnowledgeBase注入到LLMUnity的框架中。替换依赖在初始化你的AI对话管理器或Agent的地方不再使用默认的SimpleMemory或旧的向量知识库而是实例化我们的新类。public class MyAIManager : MonoBehaviour { private Conversation _conversation; private DuckDBKnowledgeBase _knowledgeBase; async void Start() { // 1. 初始化知识库 _knowledgeBase new DuckDBKnowledgeBase(my_game_knowledge.db); await _knowledgeBase.InitializeAsync(); // 2. 可选预加载一些知识 // await _knowledgeBase.StoreAsync(...); // 3. 创建Conversation并传入知识库 var llmClient new OpenAIClient(apiKey: your_key); _conversation new Conversation(llmClient) { // 假设Conversation的构造函数或属性可以接受IKnowledgeBase KnowledgeBase _knowledgeBase }; // 4. 设置系统提示告诉AI可以使用知识库 _conversation.SystemPrompt 你是一个游戏助手可以参考以下知识库信息来回答问题...; } void OnDestroy() { _knowledgeBase?.Dispose(); } }设计检索增强生成RAG流程在发送用户消息给LLM之前先调用_knowledgeBase.SearchAsync(userQuery)获取最相关的知识片段。然后将这些片段作为上下文与用户问题一起组装成最终的提示词Prompt发送给LLM。public async Taskstring GetAIResponseWithKnowledge(string userQuery) { // 1. 检索相关知识 var relevantKnowledge await _knowledgeBase.SearchAsync(userQuery, topK: 3); string knowledgeContext string.Join(\n, relevantKnowledge.Select(k k.Content)); // 2. 构建增强后的用户消息 string augmentedQuery $ 请参考以下信息 {knowledgeContext} 用户问题{userQuery} ; // 3. 发送给Conversation var response await _conversation.SendMessageAsync(augmentedQuery); return response; }5. 性能优化与常见问题排查5.1 DuckDB性能调优要点向量索引当知识条数超过数万时务必测试并创建向量索引如ivfflat。创建索引的lists参数需要权衡查询速度和精度。CREATE INDEX idx_knowledge_embedding ON knowledge USING ivfflat (embedding) WITH (lists 100);注意索引是在表已有数据上创建的。对于频繁批量插入的新知识库可以先插入数据最后再一次性创建索引。连接与事务对于批量插入大量知识使用单个事务可以极大提升速度。using (var transaction _connection.BeginTransaction()) { // 循环执行多个Insert命令 foreach (var item in knowledgeList) { // ... 插入逻辑 } transaction.Commit(); }合理使用PRAGMADuckDB提供了很多PRAGMA指令来调整性能例如设置内存限制、线程数等适合在Unity中根据目标平台调整。PRAGMA memory_limit2GB; -- 限制内存使用 PRAGMA threads4; -- 设置查询使用的线程数5.2 集成与运行时常见问题Unity平台兼容性问题DuckDB的本地库.dll,.dylib,.so可能不兼容所有Unity目标平台尤其是WebGL和移动端iOS/Android。排查首先确认你使用的DuckDB C#库是否提供了对应平台的预编译原生库。对于WebGL由于限制较多可能需要寻找纯C#实现的向量计算库作为备选或者将知识库查询放到后端服务器。解决针对桌面平台Win/Mac/Linux通常问题不大。对于移动端需要寻找为移动平台编译的DuckDB库或者考虑使用SQLite通过sqlite-vss扩展作为替代方案。向量维度不匹配问题SearchAsync返回的结果完全不相关或报错。排查检查存储的embedding维度是否与表定义VECTOR(FLOAT, 1536)中的维度一致。确保EmbeddingService生成的向量维度是固定的。解决在初始化DuckDBKnowledgeBase时传入正确的维度参数。所有存入和查询的向量必须保持同一维度。相似度计算不准确问题检索到的知识似乎与问题不匹配。排查首先检查嵌入模型本身的质量。尝试用一些标准句子测试。确认是否使用了正确的相似度度量。余弦相似度要求向量是归一化的长度为1。检查你的EmbeddingService输出是否已归一化或者DuckDB的-运算符在你使用的扩展中具体代表什么距离。解决在SQL中显式使用cosine_similarity函数如果可用或者在将向量存入数据库前在C#端先进行归一化处理。数据库文件权限与路径问题在Unity编辑器下运行正常打包后报错“无法打开数据库文件”。排查打包后Application.dataPath等路径会发生变化且玩家可能没有写入某些目录的权限。解决使用Application.persistentDataPath作为数据库文件的存储路径这个路径在所有平台上都是应用可写的位置。string dbPath Path.Combine(Application.persistentDataPath, knowledge.db); var knowledgeBase new DuckDBKnowledgeBase(dbPath);异步操作与Unity生命周期问题在场景切换或游戏退出时进行数据库操作可能导致对象已销毁而数据库连接未正确关闭引发错误。解决确保所有异步数据库操作ExecuteNonQueryAsync,ExecuteReaderAsync都有正确的CancellationToken支持并在OnDestroy或OnApplicationQuit中调用Dispose方法等待未完成的任务或直接取消它们。6. 扩展思考从知识库到智能体记忆系统将知识库升级为DuckDB不仅仅是换了一个存储后端。它为我们打开了更广阔的想象空间可以将这个系统扩展为智能体Agent的长期记忆。对话历史存储除了静态的游戏知识还可以将每一轮高质量的对话QA向量化后存入DuckDB。这样AI就能“记住”之前和玩家讨论过什么实现跨会话的连续性。记忆检索与摘要当对话历史很长时我们可以利用向量检索快速找到与当前话题最相关的历史片段而不是将全部历史都塞入有限的上下文窗口。更进一步可以定期对历史记忆进行自动摘要生成更浓缩的“长期记忆”存入知识库。元数据的力量充分利用metadataJSON字段。你可以为每条知识打上typefact,dialogue,player_preference、importance评分、access_count访问次数等标签。检索时不仅可以基于语义相似度还可以结合这些元数据进行加权排序实现更智能的记忆唤起。例如一个增强的搜索SQL可能长这样SELECT content, (0.7 * cosine_similarity(embedding, queryVec) 0.3 * (metadata-importance)::FLOAT) as combined_score FROM memory WHERE metadata-type IN (fact, dialogue) AND created_at DATE_SUB(NOW(), INTERVAL 30 DAY) -- 最近30天的记忆 ORDER BY combined_score DESC LIMIT 5;这个查询综合了语义相关性70%权重和记忆的重要性评分30%权重并且只检索特定类型和近期记忆模拟了人类记忆的某些特性。通过这次对LLMUnity脚本继承关系的梳理以及用DuckDB重构知识库的实践我们不仅解决了眼前的具体问题更重要的是掌握了一套方法如何深入理解一个框架的内部结构以及如何根据实际需求引入更强大的基础设施来提升应用的能力上限。在AI与游戏结合的路上这种“深度定制”的能力会越来越重要。