haystack 20 review

Haystack 2.0 评测:deepset 的 RAG 框架重生

Haystack 1.x vs 2.0:推倒重来 Haystack 2.0 不是渐进式升级,是架构重写。deepset 团队总结了 1.x 的所有教训,推倒重建: 维度 Haystack 1.x Haystack 2.0 核心抽象 Node(黑盒) Component(白盒,类型安全) 连接方式 隐式管道连接 显式 connect() 方法 类型系统 无,靠字典传递 有,输入输出类型检查 路由 基于字段名自动路由 显式连接,可视化路由 扩展性 继承 BaseComponent 实现协议,组合优先 测试 困难(需整个 Pipeline) 组件可独立测试 序列化 有限 YAML 配置,完整序列化 1.x 最大的问题是黑盒太多。一个 FARMReader 封装了模型加载、预处理、预测、后处理,你想改其中一步就得继承重写。2.0 把这些全拆开,每个步骤是独立的 Component。 Pipeline 架构:显式连接 Haystack 2.0 的 Pipeline 是 Component 的有向无环图(DAG): from haystack import Pipeline from haystack.components.converters import TextFileToDocument from haystack.components.preprocessors import DocumentSplitter from haystack.components.embedders import OpenAITextEmbedder, OpenAIDocumentEmbedder from haystack.components.writers import DocumentWriter from haystack.components.retrievers import VectorStoreRetriever from haystack.components.generators import OpenAIGenerator from haystack.components.builders import PromptBuilder from haystack.document_stores.in_memory import InMemoryDocumentStore # === 索引 Pipeline === indexing_pipeline = Pipeline() indexing_pipeline.add_component("converter", TextFileToDocument()) indexing_pipeline.add_component("splitter", DocumentSplitter(split_by="word", split_length=500)) indexing_pipeline.add_component("embedder", OpenAIDocumentEmbedder(model="text-embedding-3-small")) indexing_pipeline.add_component("writer", DocumentWriter(document_store=InMemoryDocumentStore())) # 显式连接 indexing_pipeline.connect("converter.documents", "splitter.documents") indexing_pipeline.connect("splitter.documents", "embedder.documents") indexing_pipeline.connect("embedder.documents", "writer.documents") # 运行索引 indexing_pipeline.run({"converter": {"sources": ["./data/*.txt"]}}) 注意 connect() 的参数格式:"component_name.output_field" → "component_name.input_field"。类型必须匹配——如果 splitter 输出 documents,embedder 的输入也必须是 documents,否则 Pipeline 构建时就报错。 ...

2026-06-25 · 4 min · 657 words · AI 实战派
langgraph vs langchain

LangGraph vs LangChain:该用哪个构建 Agent

LangChain 的痛点:为什么需要 LangGraph LangChain 最初设计目标是链式调用 LLM——prompt → model → output parser 串成一条链。这在简单的 Q&A 和 RAG 场景下足够用,但当你要构建真正的 Agent时,问题就来了: 痛点 具体表现 链是线性的 Agent 需要循环、条件分支、回退,Chain 只能直线走 状态管理粗糙 Memory 机制简陋,复杂状态需手动管理,多步推理容易丢上下文 没有检查点 长流程中途失败,无法从断点恢复,只能从头来 Human-in-the-loop 困难 链执行中无法暂停等待人类输入 AgentExecutor 黑盒 封装太重,调试困难,自定义控制流代价高 LangChain 的 AgentExecutor 本质上是一个 while 循环 + tool 调用,你无法精确控制每一步的行为。当你需要"如果工具返回 X 则走分支 A,否则走分支 B"这种逻辑时,AgentExecutor 就力不从心了。 LangGraph 的核心思路:把 Agent 当图来建模 LangGraph 的设计哲学很简单:Agent 的执行流程是一张有向图。 节点(Node):每个节点是一个函数,接收状态,返回更新后的状态 边(Edge):连接节点的有向边,可以是固定的或条件性的 状态(State):一个共享的 TypedDict 或 dataclass,在节点间流转 from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] tool_results: list def call_model(state: AgentState): response = llm.invoke(state["messages"]) return {"messages": [response]} def should_use_tool(state: AgentState) -> str: last_msg = state["messages"][-1] if last_msg.tool_calls: return "tools" return END # 构建图 graph = StateGraph(AgentState) graph.add_node("agent", call_model) graph.add_node("tools", tool_node) graph.set_entry_point("agent") graph.add_conditional_edges("agent", should_use_tool) graph.add_edge("tools", "agent") # 工具执行完回到 agent app = graph.compile() 这段代码实现了一个标准的 ReAct Agent,但控制流完全透明。你能看到每一步在做什么,为什么走这个分支。 ...

2026-06-25 · 3 min · 488 words · AI 实战派
llama index agent review

LlamaIndex Agent 评测:从 RAG 到 Agent 的进化

LlamaIndex 的演进路线 LlamaIndex 起家于 RAG(检索增强生成),最初的定位是"连接你的数据与 LLM"。但随着 LLM 能力增强,单纯检索已经不够——用户需要 Agent 来动态决定何时检索、如何检索、检索后做什么。 版本阶段 核心能力 定位 0.9 之前 Index + Query Engine RAG 框架 0.10+ LlamaHub + Workflows 数据连接 + 工作流 当前 Data Agent + AgentWorkflow RAG + Agent 融合 0.10 是分水岭——LlamaIndex 重构了整个包结构,引入了 Workflow(事件驱动工作流)和 Data Agent,正式从 RAG 框架进化为 Agent 框架。 Data Agent:带检索能力的智能体 LlamaIndex 的 Data Agent = LLM + Tools + 检索能力: from llama_index.core.agent import FunctionAgent from llama_index.llms.openai import OpenAI from llama_index.core.tools import FunctionTool, QueryEngineTool llm = OpenAI(model="gpt-4o") # 工具1:网络搜索 def search_web(query: str) -> str: """搜索互联网获取最新信息""" from llama_index.tools.tavilyi import TavilyToolSpec return TavilyToolSpec().search(query) web_tool = FunctionTool.from_defaults(fn=search_web) # 工具2:RAG 查询(复用现有索引) from llama_index.core import VectorStoreIndex index = VectorStoreIndex.from_documents(documents) query_engine = index.as_query_engine(similarity_top_k=5) rag_tool = QueryEngineTool.from_defaults( query_engine=query_engine, name="knowledge_base", description="查询内部知识库,包含产品文档和技术规范" ) # 创建 Data Agent agent = FunctionAgent( tools=[web_tool, rag_tool], llm=llm, system_prompt="你是一个研究助手。先查内部知识库,找不到再搜网络。" ) response = await agent.run("我们的API限流策略是什么?和业界最佳实践比如何?") # Agent会: 1. 先用rag_tool查内部 2. 再用web_tool搜业界实践 3. 综合回答 这是 LlamaIndex 的差异化优势:RAG 不是外部组件,而是 Agent 的原生能力。在 LangChain 里你要手动把 Retriever 包装成 Tool,在 LlamaIndex 里这是内建的。 ...

2026-06-25 · 3 min · 600 words · AI 实战派
openclaw agent review

OpenClaw 评测:个人 AI Agent 的开源实践

OpenClaw 的定位:个人 AI Agent 操作系统 OpenClaw 不是一个 Library,是一个运行时。你不需要在代码里 import 它——你启动它,它作为一个常驻服务运行,管理你的 AI Agent 的感知、决策和行动。 特征 OpenClaw LangChain/LangGraph CrewAI/AutoGen 形态 常驻服务(运行时) Python Library Python Library 使用方式 配置 + 自然语言 写代码 写代码 目标用户 非开发者 / 轻开发者 开发者 开发者 运行环境 本地机器 / 服务器 你的应用进程 你的应用进程 核心抽象 Skill + Memory + Channel Chain + Graph Agent + Crew 这个定位决定了 OpenClaw 的设计取舍——它更像一个"AI Agent OS",而不是一个"Agent 开发框架"。 架构拆解 OpenClaw 的架构可以简化为五层: ┌─────────────────────────────────────────┐ │ Channel 层(渠道) │ │ WebChat / Discord / WhatsApp / API │ ├─────────────────────────────────────────┤ │ Agent 层(核心) │ │ LLM 推理 / 工具调用 / 决策循环 │ ├─────────────────────────────────────────┤ │ Skill 层(能力) │ │ 邮件 / 搜索 / 文档 / 浏览器 / 自定义 │ ├─────────────────────────────────────────┤ │ Memory 层(记忆) │ │ MEMORY.md / Daily Notes / LCM 压缩 │ ├─────────────────────────────────────────┤ │ Scheduler 层(调度) │ │ Heartbeat / Cron / Event 触发 │ └─────────────────────────────────────────┘ Skill 系统:插件化能力 OpenClaw 的 Skill 是一个 Markdown 文件(SKILL.md),包含指令和可能的脚本: ...

2026-06-25 · 4 min · 655 words · AI 实战派
semantic kernel review

Semantic Kernel 评测:微软的 AI 编排内核

Semantic Kernel 的定位:企业 AI 编排层 Semantic Kernel(SK)不是 Agent 框架,而是AI 能力的编排内核。它的设计目标是在企业应用中嵌入 AI 能力,而不是构建独立的 Agent。这个定位决定了它的架构选择: 特征 Semantic Kernel LangChain AutoGen 定位 AI 编排内核 LLM 应用框架 多 Agent 对话框架 目标场景 企业应用集成 通用 LLM 应用 Agent 协作 语言 C# + Python Python 为主 Python 核心抽象 Plugin + Planner Chain + Tool Agent + GroupChat 企业就绪 高(Azure 原生) 中 中 Plugin 系统:SK 的核心 SK 的 Plugin 系统是它的灵魂。一个 Plugin 就是一组可被 AI 调用的函数: Python 版 from semantic_kernel.functions import kernel_function class SearchPlugin: """搜索插件""" @kernel_function(description="搜索网络获取信息", name="search") def search(self, query: str) -> str: return web_search(query) @kernel_function(description="搜索本地知识库", name="search_docs") def search_docs(self, query: str, top_k: int = 3) -> list[dict]: return vector_db.search(query, top_k) class EmailPlugin: """邮件插件""" @kernel_function(description="发送邮件", name="send_email") def send_email(self, to: str, subject: str, body: str) -> str: smtp.send(to, subject, body) return f"邮件已发送至 {to}" # 注册插件 kernel.add_plugin(SearchPlugin(), "Search") kernel.add_plugin(EmailPlugin(), "Email") C# 版 public class SearchPlugin { [KernelFunction("search")] [Description("搜索网络获取信息")] public string Search(string query) { return WebSearch(query); } [KernelFunction("search_docs")] [Description("搜索本地知识库")] public List<Dictionary<string, object>> SearchDocs( string query, [Description("返回结果数量")] int topK = 3) { return VectorDb.Search(query, topK); } } // 注册 kernel.Plugins.AddFromType<SearchPlugin>("Search"); kernel.Plugins.AddFromType<EmailPlugin>("Email"); Python 和 C# 的 API 高度对称——这是 SK 的一个重要优势。C# 团队和 Python 团队可以共享设计文档,代码迁移成本低。 ...

2026-06-25 · 4 min · 663 words · AI 实战派
smolagents review

smolagents 评测:HuggingFace 的极简 Agent 框架

smolagents 的设计哲学:少即是多 HuggingFace 的 smolagents 用一句话概括:1000 行代码实现一个可用的 Agent 框架。在 LangChain 动辄数万行代码、依赖链深不见底的当下,smolagents 是一股清流。 设计原则 smolagents 的做法 极简核心 基类不超过 1000 行 最小依赖 仅依赖 huggingface_hub,其他都是可选的 Code Agent 优先 Agent 输出代码而非 JSON 工具调用 HF 生态原生 与 HuggingFace Hub/Inference 深度集成 学习门槛 10 分钟读完源码 Code Agent vs Tool Agent:范式之争 这是 smolagents 最重要的设计决策。理解它,就理解了 smolagents。 Tool Agent(LangChain/CrewAI 方式) Agent 输出 JSON 格式的工具调用: { "thought": "我需要搜索天气", "action": "search_weather", "action_input": {"city": "北京"} } LLM 需要严格遵循 JSON schema,任何格式错误都会导致解析失败。而且 JSON 无法表达复杂逻辑——如果你需要"对搜索结果排序后取前3个再分别查详情",只能拆成多轮调用。 Code Agent(smolagents 方式) Agent 直接输出 Python 代码: ...

2026-06-25 · 4 min · 646 words · AI 实战派
dspyp review

DSPy 框架评测:声明式 LLM 编程

DSPy 是什么:Prompt 工程的范式转换 传统 Prompt 工程的核心痛点:Prompt 是手写的、脆弱的、不可优化的。你花数小时调出一个"看起来不错"的 Prompt,换个模型就失效了。DSPy 提出了一个激进的想法——不要手写 Prompt,让框架自动优化。 DSPy(Declarative Self-improving Language Programs)来自斯坦福 NLP 组,核心理念是将 LLM 编程从"写 Prompt"转变为"声明意图 + 自动优化"。 核心架构:三件套 Signature:声明输入输出 import dspy # 声明式定义:输入是什么,输出是什么 class QA(dspy.Signature): """回答问题,基于给定上下文""" context: str = dspy.InputField(desc="参考文档") question: str = dspy.InputField(desc="用户问题") answer: str = dspy.OutputField(desc="简洁准确的回答") class Summarizer(dspy.Signature): """生成长文本的摘要""" text: str = dspy.InputField(desc="原始文本") summary: str = dspy.OutputField(desc="200字以内的摘要") Signature 的关键在于:你只描述"做什么",不描述"怎么做"。框架会根据 Signature 自动生成 Prompt。 Module:组合 LLM 逻辑 # 类似 PyTorch 的模块化设计 class RAG(dspy.Module): def __init__(self): self.retrieve = dspy.Retrieve(k=3) self.generate = dspy.ChainOfThought(QA) def forward(self, question): context = self.retrieve(question).passages prediction = self.generate(context=context, question=question) return dspy.Prediction(answer=prediction.answer) # 使用 rag = RAG() result = rag(question="量子计算的基本原理是什么?") print(result.answer) DSPy 内置模块: 模块 功能 dspy.Predict 基础预测,直接调用 LLM dspy.ChainOfThought 链式思考,让模型先推理再回答 dspy.ReAct 推理+行动循环(工具调用) dspy.Retrieve 检索模块 dspy.MultiChainComparison 多路 CoT 对比 Teleprompter:自动 Prompt 优化 这是 DSPy 的杀手级能力。Teleprompter 会自动搜索最优 Prompt: ...

2026-06-24 · 3 min · 518 words · AI 实战派
instructor review

Instructor 框架评测:结构化输出的优雅方案

结构化输出的痛点 让 LLM 输出结构化数据是工程落地中最常见的需求:解析简历提取字段、分类用户意图、生成配置文件。传统做法是在 Prompt 里写"请输出 JSON",然后祈祷模型不要在 JSON 前面加一句"好的,这是你要的结果:"。Instructor 框架优雅地解决了这个问题。 Instructor 核心原理 Instructor 的设计哲学极其简洁:用 Python 类型注解定义结构,让 LLM 填充结构,自动验证和修复。 工作流程: 你定义一个 Pydantic 模型(即期望的输出结构) Instructor 将模型 Schema 注入 Prompt LLM 生成输出 Pydantic 验证输出 验证失败 → 自动将错误信息反馈给 LLM 重试 快速上手 安装与基础用法 pip install instructor import instructor from pydantic import BaseModel, Field from openai import OpenAI # 启用 Instructor client = instructor.from_openai(OpenAI()) # 定义输出结构 class UserInfo(BaseModel): name: str = Field(description="用户全名") age: int = Field(description="用户年龄", ge=0, le=150) email: str = Field(description="邮箱地址") role: str = Field(description="职位", default="unknown") # 调用——直接返回结构化对象 user = client.chat.completions.create( model="gpt-4o", response_model=UserInfo, messages=[{"role": "user", "content": "张三,28岁,邮箱 zhangsan@example.com,高级工程师"}], ) print(user.name) # "张三" print(user.age) # 28 print(user.email) # "zhangsan@example.com" print(user.role) # "高级工程师" print(type(user)) # <class 'UserInfo'> 注意:返回的不是字典,是经过验证的 Pydantic 对象。类型安全,IDE 补全全开。 ...

2026-06-24 · 3 min · 632 words · AI 实战派
guidance review

Microsoft Guidance 模板评测:控制 LLM 输出格式

Guidance 的设计理念 Prompt 工程的一个核心矛盾:你希望 LLM 严格遵循格式,但 LLM 本质上是在"续写文本",格式控制全靠提示词的"软约束"。Microsoft Guidance 提出了另一种思路——用模板语言硬约束 LLM 的输出结构。 Guidance 不是让 LLM “生成一个 JSON”,而是在 token 层面强制 LLM “先输出 {",再输出 key,再输出 ":",以此类推。这种约束式生成从根本上消除了格式错误。 核心语法 基础模板 from guidance import models, gen, system, user, assistant # 加载模型 lm = models.LlamaCpp("models/Llama-3-8B-Instruct-Q4_K_M.gguf", n_gpu_layers=35) # 使用角色标签 with system(): lm += "你是一个有帮助的助手。" with user(): lm += "解释量子纠缠。" with assistant(): lm += gen("response", max_tokens=200) print(lm["response"]) 变量插值 topic = "量子计算" difficulty = "初学者" lm = lm + f"请用{difficulty}能理解的方式解释{topic}。" + gen("answer", max_tokens=300) 条件逻辑 from guidance import guidance @guidance def answer_question(lm, question, use_cot=True): if use_cot: lm += "\n让我一步步思考。\n" lm += gen("reasoning", max_tokens=200) + "\n" lm += "因此,答案是:" lm += gen("answer", max_tokens=100) else: lm += "\n答案:" lm += gen("answer", max_tokens=100) return lm lm += answer_question("光合作用的原理是什么?", use_cot=True) 循环 @guidance def generate_questions(lm, topic, n=3): lm += f"关于{topic}的{n}个问题:\n" for i in range(n): lm += f"\n{i+1}. " lm += gen(f"question_{i}", max_tokens=50, stop=["\n", "?"]) lm += "?" return lm lm += generate_questions("机器学习", n=5) 结构化输出保证 JSON 保证输出 import guidance from guidance import json @guidance def extract_entity(lm, text): lm += f"从以下文本中提取实体信息:\n{text}\n" lm += json("entity", schema={ "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "occupation": {"type": "string"} }, "required": ["name", "age", "occupation"] }) return lm lm += extract_entity("李四今年30岁,是一名软件工程师") # 输出保证是合法 JSON,因为 Guidance 在 token 层面约束了结构 正则约束 from guidance import regex @guidance def generate_phone(lm): lm += "电话号码:" lm += regex(pattern=r"\d{3}-\d{3}-\d{4}") return lm # 输出格式保证为 XXX-XXX-XXXX 选择题约束 from guidance import select @guidance def classify(lm, text): lm += f"文本:{text}\n类别:" lm += select(options=["正面", "负面", "中性"], name="sentiment") return lm # LLM 只能输出这三个选项之一,不可能输出其他内容 与传统 Prompt 工程对比 格式可靠性 方法 JSON 成功率 格式控制方式 容错能力 手写 Prompt ~85% 软约束(提示词) 无 Function Calling ~95% API 约束 需手动 Guidance ~100% Token 级硬约束 内置 代码对比:生成结构化产品信息 传统 Prompt 方式: ...

2026-06-24 · 4 min · 656 words · AI 实战派
outlines review

Outlines 框架评测:保证 LLM 输出结构化

Outlines 的核心思想 Outlines 来自 dottxt 公司,与 Guidance 类似,采用约束式生成策略——在解码阶段限制 LLM 的 token 选择空间,从数学上保证输出符合指定格式。区别在于 Outlines 更聚焦于"结构"而非"模板",且在 CFG(上下文无关文法)支持上更加深入。 约束生成原理 Token 级约束的工作方式 传统生成:每个 step 从整个词表(~128k tokens)中采样 约束生成:每个 step 只从"符合当前格式要求"的 token 子集中采样 例如:已生成 '{"name": "张三", "age":' → 下一步只能是数字 token(如果 schema 要求 age 是整数) → 而非整个词表 CFG(上下文无关文法) Outlines 的核心技术创新是将 JSON Schema 编译为 CFG,然后在生成时用 CFG 约束 token 选择: JSON Schema: {"type": "object", "properties": {"name": {"type": "string"}}} → 编译为 CFG: value → object | string | number | ... object → "{" ws string ws ":" ws value ws "}" string → "\"" chars "\"" chars → char chars | ε ... → 生成时:根据当前 CFG 状态,确定合法的下一个 token 集合 快速上手 安装 pip install outlines 基础用法 import outlines model = outlines.models.transformers("meta-llama/Llama-3-8B-Instruct") # 直接定义输出类型 @outlines.generate.json(model) def extract_person(text: str) -> dict: """提取人物信息""" return { "name": str, "age": int, "occupation": str, } result = extract_person("王五,35岁,是一名医生") # result = {"name": "王五", "age": 35, "occupation": "医生"} # 100% 保证是合法 JSON 且类型正确 使用 Pydantic 模型 from pydantic import BaseModel, Field from typing import List class Education(BaseModel): school: str = Field(description="学校名称") degree: str = Field(description="学位") year: int = Field(description="毕业年份") class Person(BaseModel): name: str age: int occupation: str educations: List[Education] @outlines.generate.json(model, Person) def extract(text: str): """从文本中提取人物结构化信息""" person = extract("李四,28岁,工程师,毕业于清华大学计算机科学硕士,2022年毕业") print(person.name) # "李四" print(person.educations[0].school) # "清华大学" 正则约束 @outlines.generate.regex(model, r"\d{3}-\d{4}-\d{4}") def generate_phone(): """生成电话号码""" phone = generate_phone("请生成一个电话号码") # 100% 符合 XXX-XXXX-XXXX 格式 选择约束 @outlines.generate.choice(model, ["正面", "负面", "中性"]) def classify_sentiment(text: str): """情感分类""" result = classify_sentiment("这个产品太棒了!") # 只能是 "正面"、"负面" 或 "中性" 类型约束 # 整数约束 @outlines.generate.format(model, int) def generate_score(text: str): """生成评分""" # 布尔约束 @outlines.generate.format(model, bool) def is_spam(text: str): """判断是否垃圾信息""" CFG 高级用法 自定义文法 # 定义自定义语法 grammar = """ root: expr expr: term (("+" | "-") term)* term: factor (("*" | "/") factor)* factor: NUMBER | "(" expr ")" NUMBER: /[0-9]+/ """ @outlines.generate.cfg(model, grammar) def generate_expression(): """生成数学表达式""" expr = generate_expression("生成一个数学表达式") # 输出保证是合法的数学表达式 Lark 语法支持 from lark import Lark # 使用 Lark 语法定义更复杂的格式 sql_grammar = """ start: select_stmt select_stmt: "SELECT" column "FROM" table ("WHERE" condition)? column: NAME table: NAME condition: NAME OP VALUE OP: "=" | ">" | "<" | "!=" NAME: /[a-zA-Z_]+/ VALUE: /"[^"]*"/ | NUMBER NUMBER: /[0-9]+/ """ @outlines.generate.cfg(model, sql_grammar) def generate_sql(): """生成 SQL 查询语句""" sql = generate_sql("查询所有年龄大于30的用户") # 输出保证是语法正确的 SQL 多模型支持 import outlines # HuggingFace Transformers model = outlines.models.transformers("meta-llama/Llama-3-8B-Instruct") # llama.cpp (GGUF) model = outlines.models.llamacpp("models/Llama-3-8B-Instruct-Q4_K_M.gguf", n_gpu_layers=35) # vLLM (高吞吐) model = outlines.models.vllm("meta-llama/Llama-3-8B-Instruct") # mamba (SSM 架构) model = outlines.models.mamba("state-spaces/mamba-130m-hf") Outlines vs Function Calling vs Instructor vs Guidance 维度 Outlines Function Calling Instructor Guidance 格式保证 100%(CFG 约束) ~95%(API 约束) ~99%(重试) 100%(Token 约束) 模型要求 本地模型 API 模型 任意 本地模型优先 延迟开销 低(无重试) 中 中(可能重试) 低 Schema 支持 JSON Schema/Pydantic/正则/CFG JSON Schema Pydantic JSON Schema/正则/select 灵活性 高 中 高 很高 生产成熟度 高 高 高 中 可靠性对比 在 500 条信息抽取任务上的测试: ...

2026-06-24 · 4 min · 700 words · AI 实战派
鲁ICP备2026018361号