1. 项目概述当大模型需要“记忆”与“思考”如果你正在尝试将大型语言模型LLM集成到你的业务或项目中大概率会遇到一个核心瓶颈模型本身并不知道你的私有数据。无论是内部的技术文档、客服对话记录还是产品手册这些宝贵的“知识”都沉睡在文件柜或服务器里无法直接被模型调用。传统的微调方法成本高昂、周期长且难以应对知识的快速更新。这正是llmware这个开源项目要解决的核心问题。它不是一个新的大模型而是一个功能强大的“工具箱”和“连接器”旨在让开发者能够轻松地为任何LLM构建具备私有知识库检索与推理能力的智能应用。简单来说llmware让大模型学会了“查资料”和“有依据地回答问题”。它提供了一套从文档解析、向量化存储、语义检索到提示工程编排的完整流水线。你可以把它想象成一个为LLM量身定制的“数字图书馆管理员”它负责将各种格式的文档PDF、Word、PPT、Excel、TXT、HTML等拆解、理解并存入一个高效的索引库当用户提问时它能迅速从海量资料中找到最相关的片段并指导LLM基于这些片段生成准确、可靠的答案同时提供答案的来源引用。这个过程就是当前构建企业级AI应用最主流的技术范式——检索增强生成RAG。我最初接触这个项目是因为需要为一个金融研究团队快速搭建一个能够问答上百份PDF研报的智能助手。当时评估了多个开源框架最终选择llmware是因为它在设计上非常“务实”和“全栈”。它不像一些框架只专注于向量数据库的某一部分而是从实际业务落地出发覆盖了从数据摄入到应用部署的几乎每一个环节并且对多模型的支持非常友好降低了技术选型的纠结成本。2. 核心架构与设计哲学拆解llmware的设计体现了清晰的层次化和模块化思想其核心架构可以概括为“三层两库一中心”。理解这个架构对于高效使用它至关重要。2.1 三层架构清晰的责任边界第一层是数据层Data Layer。这是所有工作的起点负责处理“脏活累活”。llmware内置了强大的解析器Parser能够处理超过15种常见的文档格式。它的聪明之处在于不仅能提取文字还能理解文档结构比如识别标题、段落、列表、表格甚至从扫描的PDF中通过OCR提取文字。这一层输出的是一系列结构化的“文本块”Text Chunk这是后续所有处理的基础。注意文档解析的质量直接决定了RAG应用的上限。llmware在解析时提供了丰富的参数如块大小chunk_size、块重叠chunk_overlap等。对于技术文档较小的块如200-400字符可能更精准对于连贯的叙述性文本较大的块如512-1024字符能保留更多上下文。这需要根据你的数据特性进行实验调整。第二层是知识层Knowledge Layer。这是项目的核心负责将文本块转化为机器可理解、可检索的知识。该层主要做两件事向量化Embedding和索引Indexing。llmware集成了多种开源的嵌入模型如来自Sentence Transformers的all-MiniLM-L6-v2也支持OpenAI、Cohere等商业API。它将文本块转化为高维向量即嵌入然后存储到向量数据库中。项目原生深度集成了Milvus、Pinecone、Redis等主流向量库也支持FAISS和Chroma这类轻量级方案。这一层构建了应用的“长期记忆”。第三层是应用层Application Layer。这一层面向最终的用户交互和业务逻辑。它封装了复杂的检索、重排序Re-ranking和提示Prompt构建过程。开发者通过简单的API调用就能完成“提问-检索相关文本-构造增强提示-调用LLM生成答案-解析输出”的全流程。llmware提供了Query、Prompt等高级抽象让开发者无需关心底层细节就能构建出复杂的多步推理或汇总任务。2.2 两库支持灵活的模型与存储适配“两库”指的是它对大模型库和向量数据库库的广泛支持。这是llmware最大的优势之一提供了极大的灵活性。在模型侧它采用了统一的接口封装。无论是通过API调用的GPT-4、Claude还是本地部署的Llama 2、Mistral、GLM系列模型甚至是Hugging Face上的数千个模型你都可以用几乎相同的方式去调用。这意味着你的应用可以轻松地在不同模型间切换进行成本、性能和效果的对比而无需重写核心业务逻辑。在向量数据库侧它同样提供了统一的VectorStore抽象。你可以根据数据规模、性能要求和运维复杂度选择从本地轻量的FAISS文件到分布式高可用的Milvus集群。这种设计使得技术栈的迁移和扩展变得非常平滑。2.3 一中心思想以“提示资产”为中心“一中心”是llmware一个颇具前瞻性的设计理念提示资产Prompt Assets管理。在传统的RAG开发中提示词Prompt往往以字符串的形式硬编码在代码里难以管理、版本控制和复用。llmware将提示词、相关的上下文文档、模型配置、甚至输出解析规则打包成一个可序列化、可共享的“资产包”。你可以创建一个“合同审查”提示资产其中包含了针对法律文档优化的系统指令、用于提取关键条款的Few-shot示例以及输出JSON格式的解析器。这个资产可以保存在本地或共享给团队其他成员。当需要使用时直接加载该资产传入新的合同文本和问题即可获得结构化的输出。这极大地提升了复杂AI工作流的开发效率和可维护性使得AI能力的沉淀和复用成为可能。3. 从零开始构建你的第一个企业知识库问答机器人理论讲得再多不如亲手搭建一个。下面我将以一个最常见的场景——基于公司内部PDF手册构建问答机器人——为例展示如何使用llmware实现端到端的流程。我们将使用本地嵌入模型和FAISS向量库确保整个过程完全在内部环境中运行。3.1 环境准备与安装首先创建一个干净的Python环境3.8及以上版本然后安装llmware。由于它依赖较多建议使用pip安装。# 创建并激活虚拟环境可选但推荐 python -m venv llmware-env source llmware-env/bin/activate # Linux/Mac # llmware-env\Scripts\activate # Windows # 安装 llmware pip install llmware安装过程会自动获取核心库以及一些基本的文本处理依赖。如果你计划使用特定的嵌入模型如sentence-transformers或本地LLM如通过llama.cpp可能需要额外安装对应的包。llmware采用了按需导入的策略大部分依赖在首次使用时才会被加载。3.2 文档解析与知识库创建假设我们有一个docs文件夹里面存放了若干份产品手册PDF。第一步是将它们“喂”给系统。import os from llmware.library import Library from llmware.parsers import Parser # 1. 创建一个知识库Library可以理解为是一个独立的知识容器 library_name 产品手册知识库 library Library().create_new_library(library_name) # 2. 指定文档路径 doc_folder_path ./docs # 3. 解析并入库 print(f开始解析文件夹: {doc_folder_path}) parsing_results Parser().parse_folder(library, doc_folder_path) print(f解析完成。共处理文件数: {parsing_results[files_processed]}) print(f生成的文本块数: {parsing_results[blocks_created]})这段代码执行后llmware会遍历文件夹内的所有支持格式的文件进行解析、分块并将原始的文本块以及元数据如来源文件、页码存储在一个SQLite数据库中默认路径在./llmware_data/下。此时知识还只是原始文本无法进行语义检索。3.3 向量嵌入与索引构建接下来我们需要为这些文本块创建向量索引。这里选择使用本地的all-MiniLM-L6-v2模型和FAISS数据库它速度快且无需网络。from llmware.embeddings import EmbeddingHandler # 1. 创建嵌入处理器指定使用的嵌入模型和向量数据库 # embedding_model_name 可以是本地模型名也可以是OpenAI等API的模型标识 # vector_db 指定向量数据库类型可选milvus, pinecone, redis, faiss, chroma 等 embedding_handler EmbeddingHandler(library) embedding_model_name all-MiniLM-L6-v2 vector_db faiss # 2. 执行向量化操作 # 这一步可能会花费一些时间取决于文档数量和模型速度 print(开始生成向量嵌入并创建索引...) embedding_results embedding_handler.create_new_embedding(embedding_model_name, vector_dbvector_db) print(f向量化完成。状态: {embedding_results[status]}) print(f使用的模型: {embedding_results[embedding_model]}) print(f向量数据库: {embedding_results[vector_db]}) print(f索引的块数量: {embedding_results[blocks_embedded]})执行成功后你的知识库就具备了语义检索能力。所有的向量索引文件会默认存储在知识库目录下的faiss文件夹内。3.4 执行查询与答案生成现在最激动人心的部分来了提问。我们将进行一次检索并让LLM基于检索到的上下文生成答案。from llmware.prompts import Prompt from llmware.models import ModelCatalog # 1. 定义一个查询问题 query 产品AlphaPro的最大支持并发用户数是多少在哪些环境下可以部署 # 2. 首先进行语义检索从知识库中找出最相关的文本片段 print(f执行查询: {query}) search_results library.search(query, result_count5, embedding_model_nameembedding_model_name) print(f检索到 {len(search_results)} 个相关片段:) for i, result in enumerate(search_results): print(f\n--- 片段 {i1} (相关性: {result[similarity_score]:.3f}) ---) print(f来源: {result[file_source]}, 页码: {result[page_num]}) print(f文本预览: {result[text][:200]}...) # 3. 准备调用LLM生成答案。这里我们使用一个免费的、可在本地运行的轻量级模型示例 # 首先从ModelCatalog中加载一个模型。这里以 Hugging Face 上的一个流行小模型为例。 # 注意首次运行会下载模型请确保网络通畅和足够磁盘空间。 model_name google/flan-t5-base # 一个较小的指令微调模型用于演示 print(f\n加载语言模型: {model_name}) prompter Prompt().load_model(model_name, from_hfTrue) # from_hfTrue 表示从HuggingFace加载 # 4. 构建增强提示Prompt将检索到的上下文和问题一起交给模型 # llmware的prompt类会自动处理上下文组装、token长度限制等繁琐工作 context \n\n.join([res[text] for res in search_results]) prompt_text f基于以下上下文信息回答问题。如果上下文没有提供足够信息请回答“根据提供的信息无法确定”。 上下文 {context} 问题{query} 答案 # 5. 调用模型生成答案 print(\n正在生成答案...) response prompter.prompt_main(prompt_text, prompt_nameqa_with_context) # 6. 输出结果 print(\n *50) print(生成的答案) print(response[llm_response]) print(*50) # 7. 如果需要可以轻松地获取答案的来源引用 print(\n答案依据) for i, res in enumerate(search_results): print(f[{i1}] {res[file_source]} (P{res[page_num]}))这段代码展示了最核心的RAG流程。llmware的Prompt类极大地简化了与模型交互的复杂度。在实际生产中你可以将第4步的模型替换为GPT-4、Claude-3或更强大的本地模型以获得更佳的答案质量。4. 进阶实战优化检索效果与构建复杂工作流基础的问答跑通后你会很快遇到更实际的需求如何让检索更精准如何处理复杂问题如何管理对话历史llmware提供了丰富的工具来应对这些挑战。4.1 优化检索策略超越简单语义搜索简单的向量相似度搜索有时会返回不相关的结果尤其是当查询词与文档用语差异较大时。llmware支持多种优化策略混合检索Hybrid Search结合关键词BM25搜索和向量搜索的结果取长补短。关键词搜索对特定术语、缩写、产品代码的匹配非常有效。# 在创建嵌入时启用文本索引以支持混合检索 embedding_results embedding_handler.create_new_embedding( embedding_model_name, vector_dbvector_db, text_indexTrue # 启用文本索引 ) # 执行混合检索 search_results library.hybrid_search(query, result_count5, embedding_model_nameembedding_model_name)查询扩展Query Expansion让大模型帮你改写或扩展查询生成多个相关的搜索词再进行检索提高召回率。from llmware.responses import Query query_tool Query(library) expanded_queries query_tool.query_expansion(query, model_namemodel_name) # expanded_queries 是一个包含原始查询和多个扩展查询的列表 all_results [] for q in expanded_queries: results library.search(q, result_count2, embedding_model_nameembedding_model_name) all_results.extend(results) # 然后对 all_results 去重、排序重排序Re-ranking先用向量搜索召回大量候选片段如50个再用一个更精细的交叉编码器Cross-Encoder模型对它们进行精排选出最相关的几个。这能显著提升Top结果的精确度。# 假设已安装 sentence-transformers from sentence_transformers import CrossEncoder reranker CrossEncoder(cross-encoder/ms-marco-MiniLM-L-6-v2) # 先进行粗召回 coarse_results library.search(query, result_count50, embedding_model_nameembedding_model_name) pairs [[query, res[text]] for res in coarse_results] scores reranker.predict(pairs) # 将分数附加到结果中并重新排序 for i, res in enumerate(coarse_results): res[rerank_score] scores[i] reranked_results sorted(coarse_results, keylambda x: x[rerank_score], reverseTrue)[:5]4.2 构建多步推理与汇总工作流很多业务问题不是一次问答就能解决的。例如“对比产品A和产品B在安全特性上的差异”可能需要从多份文档中提取信息再进行综合对比。llmware的Prompt类支持链式调用Chaining可以构建复杂的工作流。from llmware.prompts import Prompt prompter Prompt().load_model(gpt-4, api_keyyour-key) # 使用GPT-4 # 第一步提取产品A的安全特性 context_a library.search(产品A 安全 特性, result_count3, embedding_model_nameembedding_model_name) prompt_a f从以下文本中列出产品A提到的所有安全特性\n{context_a}\n\n列表 response_a prompter.prompt_main(prompt_a, prompt_nameextract_security_a) security_a response_a[llm_response] # 第二步提取产品B的安全特性 context_b library.search(产品B 安全 特性, result_count3, embedding_model_nameembedding_model_name) prompt_b f从以下文本中列出产品B提到的所有安全特性\n{context_b}\n\n列表 response_b prompter.prompt_main(prompt_b, prompt_nameextract_security_b) security_b response_b[llm_response] # 第三步进行对比分析 comparison_prompt f请基于以下信息生成一个产品A与产品B在安全特性上的对比表格。 要求表格包含“特性类别”、“产品A的支持情况”、“产品B的支持情况”、“备注”四列。 产品A安全特性 {security_a} 产品B安全特性 {security_b} 对比表格 final_response prompter.prompt_main(comparison_prompt, prompt_namecompare_security) print(final_response[llm_response])这种链式调用结合llmware对提示资产的管理可以将每一个步骤提取、汇总、对比封装成可复用的模块从而构建出非常强大的自动化分析流水线。4.3 实现带历史记录的对话机器人对于聊天机器人场景记住对话历史至关重要。llmware通过Prompt状态的维护来支持多轮对话。from llmware.prompts import Prompt prompter Prompt().load_model(gpt-3.5-turbo, api_keyyour-key) library Library().load_library(产品手册知识库) # 加载已有知识库 # 初始化对话传入系统指令 system_prompt 你是一个专业的产品支持助手请严格根据提供的产品手册内容回答问题。如果不知道请明确告知。 prompter.set_system_prompt(system_prompt) conversation_history [] def chat_with_rag(user_input): global conversation_history # 1. 检索相关知识 search_results library.search(user_input, result_count3) context \n.join([res[text] for res in search_results]) # 2. 将历史记录和当前上下文组装成对话格式 # llmware的prompt_main支持传入对话历史 full_prompt prompter.build_prompt_for_dialogue( queryuser_input, contextcontext, historyconversation_history ) # 3. 调用模型 response prompter.prompt_main(full_prompt) answer response[llm_response] # 4. 更新历史记录通常保留最近N轮 conversation_history.append({role: user, content: user_input}) conversation_history.append({role: assistant, content: answer}) # 控制历史长度避免token超限 if len(conversation_history) 10: # 保留最近5轮对话 conversation_history conversation_history[-10:] # 5. 返回答案和来源 sources [{source: res[file_source], page: res[page_num]} for res in search_results] return answer, sources # 模拟对话 print(chat_with_rag(产品AlphaPro支持哪些操作系统)[0]) print(chat_with_rag(那它对硬件的最低要求呢)[0]) # 模型能理解“那它”指代的是AlphaPro通过管理conversation_history模型能够理解上下文指代实现连贯的对话。llmware内部会帮你处理token的截断和窗口管理确保输入不会超过模型限制。5. 部署考量与性能优化将原型部署到生产环境会面临新的挑战。llmware项目本身提供了一些思路和工具但生产级部署需要更多的工程化工作。5.1 向量数据库选型指南选择哪种向量数据库取决于你的数据量、并发需求、运维能力和成本预算。数据库适用场景优点缺点建议FAISS原型验证、中小规模数据百万级以下、离线或单机应用。速度快、内存/磁盘效率高、无需额外服务、集成简单。无持久化管理能力、不支持动态更新需全量重建、无分布式支持。快速起步、POC阶段、数据量小且更新不频繁的首选。Chroma开发环境、中小规模数据、需要简单持久化和元数据过滤。内置持久化、支持元数据过滤、API简单、有本地和客户端/服务器模式。大规模数据下性能和管理能力有限社区相对较新。需要比FAISS更多功能但不想引入重型数据库时的选择。Milvus大规模生产环境千万级以上向量、高并发、需要高性能检索和分布式扩展。功能全面标量过滤、时间旅行、多向量等、性能极高、云原生设计、社区活跃。架构复杂、运维成本高、需要单独部署和维护集群。企业级应用、数据量和并发请求大的生产场景。Pinecone云优先、希望完全托管、无需运维数据库团队。全托管、自动扩缩容、极高的可用性和性能、出色的开发者体验。成本较高按向量数和操作计费、数据需上传至其云端。预算充足、追求快速上线和零运维的团队。Redis已在使用Redis生态、需要向量检索与现有缓存/数据结构协同。利用现有Redis设施、支持多种数据结构、延迟极低。Redis Stack的向量搜索功能相对较新高级功能不如Milvus丰富。现有技术栈重度依赖Redis且向量搜索需求不是极端复杂的场景。对于大多数从0到1的项目我的建议是从FAISS或Chroma开始。它们能让你在几天内验证想法和效果。当数据量增长到数十万以上且需要服务多个并发用户时再考虑迁移到Milvus或Pinecone。llmware统一的接口使得这种迁移的代码改动成本降到最低。5.2 嵌入模型的选择与调优嵌入模型是将文本转化为向量的“翻译官”它的质量直接决定了检索的准确性。llmware支持多种模型选择时需权衡通用 vs. 领域专用all-MiniLM-L6-v2是一个优秀的通用起点。但如果你的文档是生物医学或法律等专业领域使用在该领域语料上训练过的模型如bge系列、sentence-transformers中的专业模型会有显著提升。多语言支持如果你的文档包含多语言需要选择多语言嵌入模型如paraphrase-multilingual-MiniLM-L12-v2。上下文长度大多数嵌入模型有512或1024 token的长度限制。llmware在分块时会处理此问题但如果你有超长文档如整本书需要考虑使用支持更长上下文的模型如text-embedding-3-large支持8192。速度与精度模型维度越高如768维 vs 384维通常精度更高但计算和存储成本也更大。需要在速度和召回率间做权衡。一个实用的技巧是分层索引。对摘要、标题等关键信息用高精度模型索引对正文内容用轻量级模型索引。查询时先在高精度索引中快速筛选再在轻量级索引中广泛召回兼顾速度和效果。5.3 系统监控与持续改进一个RAG系统上线后监控和迭代至关重要。你需要关注以下指标检索相关性自动或人工评估返回的文档片段是否与问题真正相关。可以定期用一批标准问题测试计算命中率。答案准确性这是终极指标。需要结合业务场景设计评估集。llmware生成的答案自带来源引用这为人工审核提供了极大便利。延迟与吞吐量监控端到端的响应时间检索LLM生成以及系统能承受的每秒查询数QPS。瓶颈可能出现在嵌入模型推理、向量搜索或大模型API调用上。成本如果使用商业API如OpenAI的嵌入和Chat接口成本监控必不可少。需要分析每次查询的平均token消耗和费用。基于这些监控数据你可以持续优化优化分块策略调整chunk_size和chunk_overlap。对于表格密集的文档可能需要特殊的分块逻辑来保持表格完整性。优化提示词系统指令和Few-shot示例对答案质量影响巨大。llmware的提示资产管理功能让A/B测试不同的提示词变得非常方便。迭代嵌入模型当发现某一类问题如涉及专业术语检索效果不佳时尝试更换或微调嵌入模型。实施查询理解在用户查询进入系统前先用一个小模型进行查询改写、纠错或意图分类可以显著提升下游检索和生成的效果。6. 避坑指南与常见问题排查在实际使用llmware的过程中我踩过不少坑也积累了一些经验。这里分享几个最常见的问题和解决方案。6.1 解析相关为什么我的表格/图片内容丢失了问题现象解析后的文本块中表格数据变成了混乱的文字或者图片中的文字完全缺失。原因与解决表格问题默认的解析器可能无法完美处理所有复杂的表格格式。llmware的Parser类支持指定不同的解析策略。对于PDF可以尝试使用layout模式它能更好地识别页面布局。# 尝试使用OCR和布局分析如果已安装pytesseract和pdf2image parsing_results Parser().parse_folder(library, doc_folder_path, parse_modelayout)对于结构至关重要的表格考虑使用专为表格设计的库如camelot、tabula-py先提取表格数据再以结构化文本如Markdown表格的形式导入llmware。图片文字缺失扫描版PDF或图片中的文字需要OCR功能。确保已安装pytesseract和pdf2image并在解析时启用OCR。parsing_results Parser().parse_folder(library, doc_folder_path, ocrTrue)注意OCR速度较慢且准确率依赖图片质量。建议仅对确认为图像型PDF的文档启用。6.2 检索相关为什么总是搜不到我想要的内容问题现象用户提问一个明确存在于文档中的问题但检索返回的片段完全不相关。排查步骤检查嵌入模型是否匹配确保查询时使用的embedding_model_name与创建索引时使用的模型完全一致。不同模型生成的向量空间不同直接混用会导致检索失效。检查查询语句用户的自然语言查询可能与文档中的表述差异很大。尝试关键词化从查询中提取核心名词和动词进行搜索。使用查询扩展见4.1节让模型生成同义或相关查询。人工审视直接在你的向量数据库里用一些文档中确切的句子进行搜索测试索引本身是否正常。调整检索数量默认的result_count可能太小。尝试增加数量比如从5调到20看看想要的片段是否在更靠后的位置。如果是说明语义相似度排序不理想需要考虑使用重排序或混合检索。审视分块质量可能答案被切分到了两个块中。尝试增大chunk_overlap参数例如从50字符增加到200字符让块之间有更多重叠减少信息被切断的风险。6.3 生成相关为什么答案胡言乱语或拒绝回答问题现象LLM生成的答案与提供的上下文矛盾或者直接说“根据上下文无法回答”尽管上下文里有明确信息。解决方案强化系统指令在提示词中明确、强硬地规定模型的行为。例如“你必须严格仅依据提供的上下文信息来回答问题。上下文中的信息是真实准确的。如果答案能在上下文中找到请直接引用。如果上下文信息不足请明确说‘根据提供的材料无法找到相关信息’。禁止编造信息。”优化上下文注入方式直接将大段上下文扔进提示词可能让模型迷失。可以尝试以下格式请参考以下用标记的上下文[此处插入检索到的文本块1][此处插入检索到的文本块2]问题[用户问题] 要求请综合以上上下文给出答案。并注明你的答案分别引用了哪个上下文块。这种清晰的分隔有助于模型定位信息。启用引用和溯源llmware的Prompt类可以自动在答案中插入引用标记。确保在调用时启用了相关选项这不仅能增加答案可信度有时也能“约束”模型更紧密地遵循上下文。response prompter.prompt_with_source(query, contextcontext, add_source_footnotesTrue)模型本身的问题如果使用的是较小的开源模型其指令遵循和上下文理解能力可能有限。尝试换用更强大的模型如GPT-4、Claude-3或更大的开源模型是立竿见影的方法但需要权衡成本和延迟。6.4 性能相关为什么第一次运行或查询这么慢问题现象首次导入模型或执行查询时耗时异常长。原因与解决首次下载模型当指定一个Hugging Face模型如google/flan-t5-base时llmware需要从网上下载模型权重和配置文件。这取决于模型大小和网络速度。解决方案是提前下载好模型或者使用本地已存在的模型路径。嵌入模型加载本地嵌入模型如all-MiniLM-L6-v2首次加载到内存需要时间。在生产环境中应该将嵌入模型服务化使其常驻内存而不是每次查询都加载一次。向量索引加载FAISS索引文件较大时从磁盘加载也需要时间。对于服务应保持索引加载在内存中。硬件限制在CPU上运行较大的模型会非常慢。如果追求速度考虑使用GPU进行嵌入计算和LLM推理。llmware本身支持CUDA你需要确保PyTorch等框架安装了GPU版本。6.5 部署相关如何将llmware应用封装为API服务llmware本身主要是一个库而非一个开箱即用的服务端。构建API服务需要额外的Web框架。一个常见的模式是使用FastAPI。# app.py 示例 from fastapi import FastAPI, HTTPException from pydantic import BaseModel from llmware.library import Library from llmware.prompts import Prompt import logging app FastAPI() logging.basicConfig(levellogging.INFO) # 全局加载资源启动时加载一次 library None prompter None class QueryRequest(BaseModel): question: str top_k: int 5 class QueryResponse(BaseModel): answer: str sources: list latency: float app.on_event(startup) async def startup_event(): 服务启动时加载知识库和模型 global library, prompter logging.info(正在加载知识库和模型...) try: library Library().load_library(产品手册知识库) # 加载一个轻量级模型用于快速响应或连接远程API prompter Prompt().load_model(gpt-3.5-turbo, api_keyyour-api-key) logging.info(资源加载完成。) except Exception as e: logging.error(f资源加载失败: {e}) raise app.post(/ask, response_modelQueryResponse) async def ask_question(request: QueryRequest): 问答接口 import time start_time time.time() if library is None or prompter is None: raise HTTPException(status_code503, detail服务未就绪) try: # 1. 检索 search_results library.search(request.question, result_countrequest.top_k) if not search_results: return QueryResponse(answer知识库中未找到相关信息。, sources[], latency0) context \n\n.join([res[text] for res in search_results]) # 2. 构建提示并生成 prompt_text f基于以下上下文回答问题\n{context}\n\n问题{request.question}\n答案 response prompter.prompt_main(prompt_text) answer response[llm_response] # 3. 准备来源信息 sources [{source: res[file_source], page: res[page_num], excerpt: res[text][:150]} for res in search_results] latency time.time() - start_time return QueryResponse(answeranswer, sourcessources, latencylatency) except Exception as e: logging.error(f处理查询时出错: {e}) raise HTTPException(status_code500, detail内部服务器错误) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)这个简单的FastAPI服务定义了一个/ask端点。在生产环境中你还需要考虑添加身份验证、限流、更完善的错误处理、异步处理以及将模型服务如LLM API调用、本地模型推理与Web服务解耦。llmware负责核心的RAG逻辑而Web服务框架负责提供稳定、可扩展的接口。