Haystack DocumentStore API 深度解析:InMemoryDocumentStore 的内存文档存储与检索原理 📅 发布时间:2026/9/12 17:52:27 👁 浏览次数: Haystack DocumentStore API 深度解析InMemoryDocumentStore 的内存文档存储与检索原理【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack本篇技术指南围绕 Haystack 2.18 的document_store模块展开系统讲解内存型 DocumentStoreInMemoryDocumentStore的完整 API包括 BM25 词法检索与向量相似度检索两种召回方式、BM25 参数调优、文档写入/删除/过滤的元数据操作、序列化与磁盘持久化以及配套的异步方法。读完本文你将掌握如何在 Haystack 中快速搭建内存文档库、配置三种 BM25 变体、编写元数据过滤器并理解底层评分与缩放机制为构建 RAG 与语义搜索 Pipeline 打好数据层基础。本文对应仓库文档document_stores_api.md源码实现位于 document_store.py测试用例见 test_in_memory.py。一、模块概览DocumentStore 在 Haystack 中的角色在 Haystack 的 Pipeline 体系中DocumentStore 负责存储 Documents文本及其元数据并在查询时提供给 Retriever。官方对该模块的定义是Stores your texts and meta data and provides them to the Retriever at query time.document_store模块向开发者暴露两个核心对象对象类型职责BM25DocumentStatsdataclass管理 BM25 检索所需的文档统计信息词频、文档长度InMemoryDocumentStoreclass内存型 DocumentStore 实现纯内存存储、进程内共享InMemoryDocumentStore的定位很明确——数据保存在内存中是临时性的ephemeral无法像磁盘型存储那样常驻持久化。它适合原型验证、测试、中小规模数据集以及不需要跨进程持久化的场景。从源码结构看document_stores目录haystack/document_stores由三部分组成in_memory/document_store.py本文主角InMemoryDocumentStore及BM25DocumentStats的实现types/定义了DocumentStore协议protocol.py、DuplicatePolicy枚举policy.py和过滤器策略errors/DocumentStoreError、DuplicateDocumentError、MissingDocumentError等异常errors.py。InMemoryDocumentStore遵循 protocol.py 中定义的DocumentStore协议——协议规定了to_dict/from_dict、count_documents、filter_documents、write_documents、delete_documents等标准接口任何实现该协议的存储类都可以被 Retriever 组件直接使用。BM25DocumentStatsBM25 检索的统计单元BM25DocumentStats是一个轻量 dataclass仅包含两个字段document_store.pydataclass class BM25DocumentStats: freq_token: dict[str, int] # 文档中各 token 的出现次数Counter doc_len: int # 文档中的 token 总数它在写入文档时被创建write_documents内部对文档内容做 tokenization 后生成并随文档删除而移除。词频freq_token与文档长度doc_len正是 BM25 系列算法计算 TF 与 IDF 的核心输入详见下文BM25 检索一节。二、InMemoryDocumentStore 完整构造参数InMemoryDocumentStore的构造函数签名如下源码见 document_store.pyAPI 参考中给出的版本略有差异以源码为准def __init__( self, bm25_tokenization_regex: str r(?u)\b\w\b, # 注意2.18 源码默认值已包含单字符 token bm25_algorithm: Literal[BM25Okapi, BM25L, BM25Plus] BM25L, bm25_parameters: dict | None None, embedding_similarity_function: Literal[dot_product, cosine] dot_product, index: str | None None, shared: bool True, async_executor: ThreadPoolExecutor | None None, return_embedding: bool True, *, strict_datetime_comparison: bool False, ) - None:各参数的含义与取值如下参数默认值说明bm25_tokenization_regexr(?u)\b\w\b用于 BM25 检索时切分文本的正则表达式。源码通过re.compile(regex).findall生成 tokenizer并统一先做小写化处理bm25_algorithmBM25L使用的 BM25 变体可选BM25Okapi、BM25L、BM25Plus由_dispatch_bm25分发到对应评分函数bm25_parametersNone以字典形式传给 BM25 实现的参数例如{k1:1.5, b:0.75, epsilon:0.25}参数细节可参考 rank_bm25 库dorianbrown/rank_bm25的文档embedding_similarity_functiondot_product向量相似度函数可选dot_product默认或cosine。选择依据是嵌入模型自身的语义空间设计index随机 UUID存储文档的索引名。若指定相同index多个InMemoryDocumentStore实例可在sharedTrue时共享同一份数据sharedTrue文档是否存放在进程级全局存储中供使用同一index的实例共享默认为False时数据仅属于当前实例随实例被垃圾回收而释放。频繁创建实例的场景如按请求创建建议设为False避免全局存储无限增长async_executorNone异步方法使用的ThreadPoolExecutor不提供时内部会初始化一个单线程 executor 并自行管理其生命周期return_embeddingTrue检索返回的 Document 是否携带 embedding 字段。默认Truestrict_datetime_comparisonFalse为True时无时区naive与带时区aware的 datetime 在过滤器中永远不相等为False默认时比较前会把 aware datetime 的时区复制到 naive datetime 上需要注意API 参考文档中给出的bm25_tokenization_regex默认值是r(?u)\b\w\w\b要求至少两个字符的 token而 2.18 源码实际默认值是r(?u)\b\w\b允许单字符 token。测试文件 test_in_memory.py 中的test_bm25_tokenization_includes_single_char_tokens明确验证了分词包含单字符 token这一行为说明文档与源码存在版本差异时以当前仓库源码为准。初始化时的内部状态构建构造函数除了保存参数还会做几件关键事情document_store.py编译 tokenizerself.tokenizer re.compile(bm25_tokenization_regex).findall确定索引与共享模式未传index时生成随机 UUIDsharedTrue时在进程级全局字典中按index建立存储桶存储、BM25 统计、平均文档长度、IDF 词表sharedFalse时则初始化实例本地的存储结构分发 BM25 算法self.bm25_algorithm_inst self._dispatch_bm25()根据bm25_algorithm选择_score_bm25okapi/_score_bm25l/_score_bm25plus之一非法的算法名会抛出ValueErrortest_invalid_bm25_algorithm测试覆盖了该行为创建 executor若未传入async_executor会创建一个max_workers1的ThreadPoolExecutor并记录_owns_executorTrue以便在__del__或shutdown()时释放。进程级全局存储定义在 document_store.py它们是四个按index索引的字典分别保存文档本体、BM25 文档统计、平均文档长度和用于 IDF 计算的全局词频表。生命周期管理shutdown 与 __del__shutdown()显式关闭内部创建的 executor仅当实例自己创建了 executor 时才生效外部传入的 executor 由调用方负责管理__del__()实例被销毁时的清理钩子同样只关闭自有的 executorstorage属性一个便捷属性返回当前实例实际使用的存储字典——sharedTrue时返回全局存储_STORAGES[self.index]否则返回实例本地字典。对非共享实例访问storage相当于拿到了只属于这个实例的数据视图。三、序列化to_dict / from_dict / save_to_disk / load_from_disk虽然InMemoryDocumentStore的默认形态是不可保存到磁盘但 Haystack 仍提供了两套数据持久化能力组件级序列化与整库落盘。组件序列化YAML / JSON Pipeline 场景to_dict()将组件序列化为字典会带上全部构造参数bm25_tokenization_regex、bm25_algorithm、bm25_parameters、embedding_similarity_function、index、shared、return_embedding、strict_datetime_comparison以便在 Pipeline 的 YAML/JSON 描述中完整还原from_dict(data)类方法从字典反序列化出InMemoryDocumentStore实例内部通过default_from_dict实现。这两个方法让 DocumentStore 可以作为组件被 marshal/yaml.py 等序列化工具处理从而嵌入完整的 Haystack Pipeline 描述文件。整库落盘与加载save_to_disk(path)把配置 全部文档写入指定路径的 JSON 文件。内部实现为先to_dict()拿到配置再遍历storage中的每个 Document 调用doc.to_dict(flattenFalse)存入documents键最后json.dump写出document_store.pyload_from_disk(path)类方法从 JSON 文件加载。读取失败会抛出DocumentStoreError文件不存在则抛FileNotFoundError。加载时会用DuplicatePolicy.OVERWRITE策略把文档写回新实例确保幂等恢复。测试 test_in_memory.py 中的test_save_to_disk_and_load_from_disk验证了往返一致性test_save_to_disk_and_load_from_disk_with_blob_and_sparse_embedding则覆盖了包含blob与稀疏向量sparse embedding的复杂文档场景。四、文档写入write_documents 与 DuplicatePolicywrite_documents是向存储写入文档的入口document_store.pydef write_documents(self, documents: list[Document], policy: DuplicatePolicy DuplicatePolicy.NONE) - int它做四件事类型校验入参必须是非字符串的可迭代对象且每个元素都是Document否则抛ValueError策略归一化policy为DuplicatePolicy.NONE时默认回退为DuplicatePolicy.FAIL这是文档明确标注的行为也与 protocol.py 的说明一致逐文档写入依据策略处理 ID 冲突增量维护 BM25 统计对每个文档内容做 tokenization生成BM25DocumentStats存入_bm25_attr更新全局 IDF 词表_freq_vocab_for_idf和平均文档长度_avg_doc_len用滑动平均公式(len(tokens) avg * (n-1)) / n增量计算。返回值是实际写入的文档数量使用OVERWRITE时恒等于输入数量使用SKIP时因跳过重复项可能小于输入数量。DuplicatePolicy 四种取值DuplicatePolicy定义在 policy.py取值行为NONE默认策略具体行为由存储实现决定在InMemoryDocumentStore中等价于FAILSKIPID 已存在则跳过该文档并记录 warning不覆盖OVERWRITEID 已存在则覆盖覆盖前会先删除旧文档以回滚其 BM25 统计再写入新文档FAILID 已存在则抛DuplicateDocumentErrorerrors.py删除与批量操作delete_documents(document_ids)删除指定 ID 的文档同时回滚 BM25 统计——从_freq_vocab_for_idf中减去该文档的词频词频归零的 token 会被清除并修正_avg_doc_len文档全部删除时平均长度归零除文档 API 外源码还提供一批便捷方法包括delete_all_documents()清空整个索引、update_by_filter(filters, meta)按过滤器批量更新元数据、delete_by_filter(filters)按过滤器批量删除、count_documents_by_filter(filters)、count_unique_metadata_by_filter(filters, metadata_fields)统计某元数据字段去重后的取值数、get_metadata_fields_info()推断各元数据字段的类型keyword/int/float/boolean、get_metadata_field_min_max()与get_metadata_field_unique_values()带分页与搜索词过滤的取值枚举。这些方法为元数据治理类应用提供了开箱即用的能力。五、文档查询filter_documents 与元数据过滤语法filter_documents是检索前最常用的查询接口返回匹配过滤器的文档列表def filter_documents(self, filters: dict[str, Any] | None None) - list[Document]filters为None时返回全部文档提供了filters时会先做语法校验缺少operator或conditions键即抛ValueError再逐文档调用document_matches_filter判定若构造时return_embeddingFalse返回结果中的 embedding 会被替换为None通过dataclasses.replace生成副本不污染存储。过滤器语法规范过滤器由嵌套字典构成分为两类协议定义见 protocol.py比较字典Comparison必须包含三个键field要比较的字段operator比较运算符可取、!、、、、、in、not invalue比较值。逻辑字典Logic必须包含两个键operator逻辑运算符可取AND、OR、NOTconditions由比较字典或逻辑字典组成的列表。一个简单过滤器filters {field: meta.type, operator: , value: article}一个复合过滤器来自协议文档的完整示例filters { operator: AND, conditions: [ {field: meta.type, operator: , value: article}, {field: meta.date, operator: , value: 1420066800}, {field: meta.date, operator: , value: 1609455600}, {field: meta.rating, operator: , value: 3}, { operator: OR, conditions: [ {field: meta.genre, operator: in, value: [economy, politics]}, {field: meta.publisher, operator: , value: nytimes}, ], }, ], }底层求值机制过滤求值实现在 haystack/utils/filters.py 的document_matches_filter中包含field键的字典按比较条件处理否则按逻辑条件处理比较运算符与实现函数的映射关系定义在COMPARISON_OPERATORS/!/////in/not in逻辑运算符映射在LOGICAL_OPERATORSAND/OR/NOT点号路径field含.时按路径逐层解析如meta.person.name会先取document.meta再逐级取字典值中间值不是字典或缺失时按None处理日期比较字符串仅在是 ISO 8601 格式日期时才能用于///比较否则抛FilterError/!支持datetime对象与 ISO 字符串的等价比较strict_datetime_comparison参数控制 naive 与 aware datetime 是否可互相匹配相关行为由test_filter_documents_date_equality_with_equivalent_iso_formats与test_filter_documents_with_strict_datetime_comparison测试验证in/not in运算符要求value必须是list否则抛FilterError。六、BM25 词法检索bm25_retrieval 与三种算法变体bm25_retrieval是纯词法lexical/keyword检索入口基于 BM25 系列算法对文档打分排序document_store.pydef bm25_retrieval( self, query: str, filters: dict[str, Any] | None None, top_k: int 10, scale_score: bool False, ) - list[Document]参数说明参数默认值说明query—查询字符串必须是非空字符串否则抛ValueErrorfiltersNone用于缩小搜索空间的过滤器字典语法见上文top_k10返回最相关的文档数量scale_scoreFalse是否对得分做缩放映射见下文得分缩放执行流程空查询校验空字符串直接抛ValueError测试test_bm25_retrieval_empty_query覆盖自动加内容过滤无论是否传入filters检索都会附加一个{field: content, operator: !, value: None}条件内容为空的文档不参与 BM25 打分候选集过滤通过filter_documents得到候选文档无候选时记录日志并返回空列表特殊边界处理如果语料平均文档长度为 0所有文档内容为空、无词表则所有文档得分记为 0.0避免三种 BM25 算法在 TF 归一化时分母为零打分与排序调用构造时选定的 BM25 算法对全部候选打分按分数降序取top_k负分过滤默认未缩放情况下BM25Okapi允许返回有意义的负分其余算法与缩放模式下得分 ≤ 0 的文档会被剔除源码注释说明这是由 PR deepset-ai/haystack#6889 引入的取舍构造返回文档把score写入每个文档副本若return_embeddingFalse则剥离 embedding。三种 BM25 变体的差异源码级_dispatch_bm25把算法名映射到三个内部评分函数document_store.pyBM25Okapi_score_bm25okapi经典 BM25TF 采用freq * (k11) / (freq k1 * (1-bb*dl/avgdl))IDF 用标准公式log((N-n0.5)/(n0.5))当语料中某词出现次数超过半数时 IDF 可为负因此负分是合法结果。其bm25_parameters支持k1、b、epsilon默认0.25用于把负 IDF 平滑为一个小正数BM25L_score_bm25l引入delta默认0.5对 TF 做加性平滑缓解 BM25 对长文档的惩罚IDF 采用log((N1)/(n0.5))BM25Plus_score_bm25plusTF 项额外加上delta默认1.0作为下限提升IDF 采用log(1 (N-n0.5)/(n0.5))对低频词更友好。各算法共享k1默认1.5词频饱和参数与b默认0.75文档长度归一化强度两个超参数。文档 API 示例给出的参数{k1:1.5, b:0.75, epsilon:0.25}即 BM25Okapi 的经典配置。得分缩放scale_scoreBM25 得分scale_scoreTrue时使用expit(score / BM25_SCALING_FACTOR)将得分映射到(0,1)区间。模块顶部的BM25_SCALING_FACTOR 8是按经验选择的缩放因子因子越大缩放后的分数越低例如原始得分 10因子为 2 时映射到约 0.99因子为 8 时约 0.78。如果未缩放的得分普遍大于 30 且都被错误地映射到接近 1应当调大该因子。test_bm25_retrieval_with_scale_score测试验证了缩放行为BM25 文档得分的无界特性、缩放因子选择逻辑以及 tokenless 语料的兜底处理均在 document_store.py 的模块级注释中有详细说明。分词细节_tokenize_bm25先对文本lower()小写化再用bm25_tokenization_regex编译出的findall切分。测试覆盖了单字符 token、单字符查询、平均文档长度正确性及删除后的均值修正test_bm25_avg_doc_len_correctness、test_bm25_avg_doc_len_after_delete、test_bm25_retrieval_with_updated_docs等边界场景。七、向量检索embedding_retrieval 与相似度计算embedding_retrieval是向量semantic检索入口按向量相似度返回与查询嵌入最接近的文档document_store.pydef embedding_retrieval( self, query_embedding: list[float], filters: dict[str, Any] | None None, top_k: int 10, scale_score: bool False, return_embedding: bool | None False, ) - list[Document]参数说明参数默认值说明query_embedding—查询的嵌入向量必须是非空 float 列表否则抛ValueErrorfiltersNone元数据过滤器语法同上top_k10返回最相似的文档数量scale_scoreFalse是否缩放得分return_embeddingFalse返回文档是否携带 embedding为None时回退使用构造参数return_embedding的值执行流程输入校验空向量或非 float 元素抛ValueError测试test_embedding_retrieval_invalid_query覆盖过滤与收集按filters收集候选文档没有候选时返回空列表嵌入完整性检查只对embedding is not None的文档打分并给出日志提示——完全没有嵌入的文档返回空列表并 warningtest_embedding_retrieval_no_embeddings部分缺失则记录 info 后跳过test_embedding_retrieval_some_documents_wo_embeddings相似度打分交给_compute_query_embedding_similarity_scores计算排序与构造返回降序取top_k把score写入文档副本resolved_return_embeddingFalse时把embedding置为None。相似度计算的底层实现_compute_query_embedding_similarity_scoresdocument_store.py基于 NumPy 实现查询向量与文档向量统一转为二维数组一维输入自动expand_dimsdot_product默认直接计算np.dot(query, documents.T)得分即向量内积cosine先分别对查询与文档向量做 L2 归一化再求点积。源码对零范数向量做了保护np.where(norm 0.0, 1.0, norm)替代归一化避免零向量除零产生 NaN——test_embedding_retrieval_with_zero_vector_does_not_produce_nan正是针对该场景维度一致性错误处理文档间嵌入维度不一致会抛DocumentStoreError提示请确保所有文档由同一模型嵌入查询与文档维度不一致同样抛DocumentStoreError。对应测试为test_embedding_retrieval_documents_different_embedding_sizes与test_embedding_retrieval_query_documents_different_embedding_sizes得分缩放scale_scoreTrue时dot_product 得分用expit(score / DOT_PRODUCT_SCALING_FACTOR)模块级常量DOT_PRODUCT_SCALING_FACTOR 100映射到(0,1)cosine 得分取值[-1,1]则线性映射为(score 1) / 2归一化到[0,1]。test_embedding_retrieval_with_scale_score、test_embedding_retrieval_return_embedding、test_embedding_retrieval_return_embedding_false_on_store与test_embedding_retrieval_override_return_embedding共同验证了缩放与 embedding 返回行为的各种组合。八、异步 API让 Pipeline 高并发执行InMemoryDocumentStore为每个核心方法都提供了async变体便于在异步 Pipeline 中避免阻塞事件循环同步方法异步方法count_documentscount_documents_asyncfilter_documentsfilter_documents_asyncwrite_documentswrite_documents_asyncdelete_documentsdelete_documents_asyncbm25_retrievalbm25_retrieval_asyncembedding_retrievalembedding_retrieval_asyncupdate_by_filterupdate_by_filter_asyncdelete_all_documentsdelete_all_documents_asynccount_documents_by_filtercount_documents_by_filter_asynccount_unique_metadata_by_filtercount_unique_metadata_by_filter_asyncget_metadata_fields_infoget_metadata_fields_info_asyncget_metadata_field_min_maxget_metadata_field_min_max_asyncget_metadata_field_unique_valuesget_metadata_field_unique_values_async所有异步方法的实现模式一致通过asyncio.get_running_loop().run_in_executor(self.executor, ...)把同步逻辑调度到构造时创建的线程池执行器上默认单线程可通过async_executor参数注入自定义线程池。因此同步与异步方法的语义完全一致参数与返回值一一对应内部 executor 若由实例创建会在shutdown()/__del__时被回收若由外部传入生命周期归调用方管理测试文件中的异步用例test_write_documents_async、test_filter_documents、test_bm25_retrieval_async、test_embedding_retrieval_async、test_concurrent_bm25_retrievals、test_concurrent_embedding_retrievals验证了异步路径的正确性与并发安全性。九、完整实战示例从写入到检索的端到端流程综合上文各节下面是一个可运行的完整示例——构建一个支持 BM25 与向量检索的内存文档库并演示过滤、缩放与持久化from haystack import Document from haystack.document_stores.in_memory import InMemoryDocumentStore from haystack.document_stores.types import DuplicatePolicy # 1. 初始化BM25L 词法检索 余弦向量相似度实例独立存储 store InMemoryDocumentStore( bm25_algorithmBM25L, bm25_parameters{k1: 1.5, b: 0.75, delta: 0.5}, embedding_similarity_functioncosine, indexmy-docs, sharedFalse, # 实例本地存储随实例释放 return_embeddingTrue, ) # 2. 写入文档元数据用于后续过滤 docs [ Document(contentHaystack is an open-source LLM orchestration framework., meta{topic: framework, rating: 5}), Document(contentBM25 is a classic lexical ranking algorithm for retrieval., meta{topic: retrieval, rating: 4}), Document(contentEmbedding similarity powers semantic search over vectors., meta{topic: search, rating: 3}), ] store.write_documents(docs, policyDuplicatePolicy.OVERWRITE) # 3. 元数据过滤 词法检索 filtered store.filter_documents({field: meta.rating, operator: , value: 4}) print(frating 4 的文档数: {len(filtered)}) hits store.bm25_retrieval(querylexical retrieval ranking, filters{field: meta.topic, operator: , value: retrieval}, top_k5, scale_scoreTrue) for doc in hits: print(fBM25 命中: {doc.content[:40]}... score{doc.score:.3f}) # 4. 向量检索查询嵌入需与文档由同一模型生成 vec_hits store.embedding_retrieval(query_embedding[0.1, 0.2, 0.3], top_k2, scale_scoreTrue, return_embeddingFalse) for doc in vec_hits: print(f向量命中: {doc.content[:40]}... score{doc.score:.3f}) # 5. 统计与清空 print(f文档总数: {store.count_documents()}) store.delete_all_documents() # 6. 整库持久化与恢复load_from_disk 为类方法 store.write_documents(docs) store.save_to_disk(/tmp/my_store.json) restored InMemoryDocumentStore.load_from_disk(/tmp/my_store.json) print(f恢复后文档数: {restored.count_documents()})在 Pipeline 中使用实际应用中通常把 DocumentStore 与 Retriever 组件组合进 Pipeline。文档存储的序列化能力使其可以被 Pipeline 描述文件引用写入端可接入DocumentWriter查询端可接入对应 Retriever。内存存储适合开发调试、单机小规模检索与测试环境生产级持久化与分布式场景应切换到其他 DocumentStore 后端如基于向量数据库的实现。十、常见问题与最佳实践选哪种 BM25 变体BM25L是默认且对长文档更友好BM25Okapi是经典实现但可能产生负分未缩放时这些负分结果会被保留BM25Plus对低频词更友好。建议在小规模语料上对比三者效果后再定。嵌入相似度函数选 dot_product 还是 cosine取决于嵌入模型部分模型如 OpenAI 系列的内积即可反映语义相似度另一些模型要求余弦归一化。请参考你所使用嵌入模型的官方说明。sharedTrue 与 sharedFalse 怎么选默认True让同一index的实例共享数据适合在单进程中跨组件共享文档库但全局存储生命周期与进程一致频繁创建/销毁实例如 Web 服务每请求新建 store应使用sharedFalse避免内存无限增长。何时使用 scale_score需要统一得分区间如混合多路召回结果、接入下游排序器时开启默认False保留原始得分便于调试。过滤器写错会怎样语法错误缺少operator/conditions/field/value键、in的值不是列表、用非 ISO 日期字符串做范围比较会抛出ValueError或FilterError并附带指向元数据过滤文档的提示信息便于快速定位。内存存储的边界InMemoryDocumentStore天然受进程内存与单机资源限制save_to_disk提供的手动快照机制适合备份与迁移若数据规模或持久化要求超出内存方案应评估其他 DocumentStore 后端。通过本文你已经掌握了 Haystack 内存文档存储层的全部核心 API构造参数、三种 BM25 算法与得分缩放、向量相似度检索、元数据过滤语法、序列化/持久化以及异步接口。下一步可以结合 test_in_memory.py 中的测试用例或直接进入 retrievers 目录了解如何将 DocumentStore 接入检索组件构建完整的 RAG Pipeline。【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考