Back to "为什么我不使用兰钱(和我所做的事情)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Agents AI Architecture C# LLM Systems Design

为什么我不使用兰钱(和我所做的事情)

Thursday, 18 December 2025

我是一个开发者。当我开始建立LLM 动力系统时, 所有人都把我指向Lang Chain。 “这是标准标准,”他们说,“所有的例子都使用它。”他们说得对,如果你在Python生态系统, Lang Chain无处不在。

但事情是这样的:我不回避朗查因(Lang Chain),因为那不好。我避免它,因为它解决的问题我已经更明确地解决了, 并且为了我使用的案例C#、当地推论、隐私、确定论框架增加了摩擦而不是价值。

这不是一个反LangChain的文章。这是一个关于理解问题框架解决了哪些问题, 并意识到你可能不需要这些问题的文章。

论文:如果你理解兰钱解决的问题, 你不需要朗钱。

兰钱究竟能做什么呢?

让我们先公平一点,兰钱在很多方面都很出色:

快速原型 你可以在几分钟内做一个工作演示。

Python生态系统一体化 - 如果你已经进入了Python/Jupyter/pandas世界, Lang Chain 将一切无缝地粘在一起。

降低屏障 - 对于新到LLMs的人,它提供有用的抽象信息:即时模板、工具调用模式、记忆管理、矢量DB集成。

Lang Chain是一个 集成加速器它加快了从"我有想法"到"我有一个演示"的道路。这很有价值。

但这也是我作为C#开发商 建立生产系统的问题 开始出现的地方。

LangChain 问题解答

在排除一个框架之前,你需要了解它正在解决什么问题。朗查因解决了这些真正的问题:

  1. 内部建筑 - 从计划、样本、历史和制约因素中形成一致的提示
  2. 工具管弦化 - 按有条件逻辑顺序管理多个工具电话
  3. 国家管理 - 保持多转对话环境
  4. 重试和错误处理 - 当LLLM产生无效输出时优雅回收
  5. 多步骤推理 - 将复杂任务分成相继步骤(“代理”模式)
  6. 可观察性 - 跟踪执行期间实际发生的情况

这些是正当的问题。问题是:你需要一个框架来解决它们吗?

Lang Chain 开始伤害的地方

Lang Chain在多个领域引发摩擦。

隐藏状态和隐隐性控制流动

Lang Chain 管理您的记忆和上下文。 这听起来很方便, 直到你需要调试为什么你的提示比预期的要长一万个符号, 或者为什么LLM突然能进入您认为已经清除的谈话历史。

框架集合了提示, 管理内存, 并隐含地处理执行命令 。 当某事破裂时, 您正在调试框架的行为, 而不是代码的行为 。

框架协调思考

一旦你收养了兰钱 你就会开始设计 Lang Chain 代表兰钱。您的结构与框架的抽象概念相结合:链条、代理人、检索器、记忆缓冲器。

这不是兰钱所独有的 所有框架都这样做。但是在像LLMS这样的快速移动的领域, 正确的抽象概念还没有解决, 与框架的世界观挂钩是危险的。

Python 阻力错配

Lang Chain假设:

  • 长寿命过程(笔记式工作流程)
  • 可变全球状态
  • Python的动态打字和鸭子打字
  • 屏蔽 I/O 模式

作为网络开发商,我想:

  • (ASP.NET核心模式)
  • 不可改变或明确管理状态
  • 强有力的打字和编译时间安全性
  • 阿斯辛克/等待各地

缩略 Lang Chain.NET港口 但是他们正在追赶 Python 版本, 和抽象 仍然觉得陌生 的特异C#。

生产实际差距

当您从原型转向生产时,需要:

  • 确定论 - 同样的投入应该产生可预测的行为
  • 审定 - 确保LLM的输出在执行前是安全的
  • 沙箱 - 限制生成的代码能实际做什么
  • 费用控制 - 跟踪象征性使用和强制限制
  • 当地推断 - 运行不依赖云的离线模型

Lang Chain 优化迭代速度, 而不是生产硬化。 这对演示来说很好; 这是生产的问题 。

我所构建的

我使用的心理模型是这样的: LLMs是推理引擎,不是执行引擎。

多尔女士:

  • 口译 口译 口译 口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译口译 - 从自然语言理解用户的用意
  • 规划 规划 规划 规划 规划 规划 规划 规划 - 将复杂任务分为步骤
  • 笔译 笔译 - 将意图转换成结构化格式(SQL、JSON、功能调用)

LLM 没有:

  • 计算总量 - 10万行
  • 扫描数据集 - 搜索大文件
  • 自状态 - 保持长期记忆

原则: LLM 理由 引擎计算

这种分离会驱动我建造的一切

明确的上下文, 不是魔力记忆

而不是由框架管理的内存,我根据请求明确构建上下文:

public class QueryContext
{
    public List<ColumnInfo> Schema { get; set; }
    public List<Dictionary<string, string>> SampleRows { get; set; }
    public List<ConversationTurn> History { get; set; }
    public string UserQuestion { get; set; }
}

每一个迅速的建筑都是可见的。我确切地知道什么是 发送到LLM 因为我自己制造了弦:

private string BuildPrompt(QueryContext context)
{
    var sb = new StringBuilder();
    sb.AppendLine("You are a SQL expert. Generate a query based on:");
    sb.AppendLine();
    
    // Schema
    sb.AppendLine("Schema:");
    foreach (var col in context.Schema)
        sb.AppendLine($"  - {col.Name}: {col.Type}");
    
    // History (if any)
    if (context.History.Any())
    {
        sb.AppendLine("\nPrevious conversation:");
        foreach (var turn in context.History.TakeLast(3))
            sb.AppendLine($"  Q: {turn.Question} → SQL: {turn.Sql}");
    }
    
    // Current question
    sb.AppendLine($"\nQuestion: {context.UserQuestion}");
    sb.AppendLine("Generate SQL (no explanation, just the query):");
    
    return sb.ToString();
}

没有隐藏的状态,没有魔法的结合,只有明确的弦建筑,当它错了,我知道为什么。

确定性执行图层

而不是让LLM执行任何东西, 我用它来产生 意图,然后通过确定性引擎执行这一意图:

  • SQL 引擎 (DuckDDB) - 用于数据查询
  • 搜索引擎 - 用于文件检索
  • 规则引擎 - 商业逻辑
  • 域服务 - 用于有效操作

LLM 生成 SQL. DuckDB 执行它。 LLM 从未看到数据 :

// LLM generates intent
var sql = await GenerateSqlAsync(context);

// Validate before execution
var error = ValidateSql(connection, sql);
if (error != null)
{
    // Retry with error feedback
    sql = await GenerateSqlAsync(context, previousError: error);
}

// Execute in sandboxed engine
var results = ExecuteQuery(connection, sql);

这是更安全、更快和可调试的。 LLM 不能意外运行 。 DROP TABLE 因为我首先验证了 SQL 。 LLM 不能泄露数据, 因为它从未看到过数据, 只看到计划。

具体实例:CSV无框架分析

我最近写到 使用本地LLMS 分析大型 CSV 文件架构:

用户问 题 * LLM * SQL * DuckDB * 结果

法学硕士接收:

  • CSV 计划(列名和类型)
  • 3个抽样行(以理解数据格式)
  • 用户的问题

LLM 产生:

  • DuckDB SQL 查询

然后,系统:

  • 使用 SQL 校验 SQL EXPLAIN (未执行的渔获物语法错误)
  • 对 CSV 文件执行查询
  • 向用户返回结果

法学硕士从不看实际数据,只看结构。

Lang Chain将这个称为“代理”一个使用LLM生成动作、验证它们、执行它们以及可能重试失败的系统。

除了我把它建在~200条C#线上,没有框架:

public class CsvQueryService
{
    private readonly OllamaApiClient _ollama;
    private readonly string _model;
    
    public async Task<QueryResult> QueryAsync(string csvPath, string question)
    {
        using var connection = new DuckDBConnection("DataSource=:memory:");
        connection.Open();
        
        // 1. Build context
        var context = BuildContext(connection, csvPath, question);
        
        // 2. Generate SQL
        var sql = await GenerateSqlAsync(context);
        
        // 3. Validate
        var error = ValidateSql(connection, sql);
        if (error != null)
        {
            // Retry once with error feedback
            sql = await GenerateSqlAsync(context, error);
        }
        
        // 4. Execute
        return ExecuteQuery(connection, sql);
    }
}

没有链子,没有代理框架,没有魔法,只是明确指挥LLM 校验执行

什么是特工,真的吗?

"代理"这个词经常被传来传去 通常是指"任何与LLM有关的东西"

代理人是:

  • 循环环环 - 它会发生多次迭代
  • 状态状态 - 它记得它曾经尝试过什么
  • 工具工具 - 它可以在世界上采取行动
  • 提供反馈反馈 - 观察结果和调整

探员是 非库库这是一个模式。

我的经纪人模式在C##:

public class Agent
{
    private readonly List<ConversationTurn> _history = new();
    
    public async Task<string> RunAsync(string goal)
    {
        while (!IsGoalAchieved(goal))
        {
            // 1. Generate next action based on history
            var action = await GenerateActionAsync(goal, _history);
            
            // 2. Validate before executing
            if (!IsActionSafe(action))
            {
                _history.Add(new ConversationTurn 
                { 
                    Action = action, 
                    Result = "REJECTED: Unsafe action" 
                });
                continue;
            }
            
            // 3. Execute through deterministic tool
            var result = await ExecuteActionAsync(action);
            
            // 4. Record and continue
            _history.Add(new ConversationTurn { Action = action, Result = result });
        }
        
        return GenerateSummary(_history);
    }
}

这是一个代理。这是一个关于状态、 工具和反馈的循环。 我用30行写下来。 我不需要一个框架 。

微软的代理框架适合的地方

微软为了公平对待.NET生态系统, 微软代理框架 这是专门为.NET开发商 建造的 生产人工智能系统

微软代理框架的正确方向

该框架(前称微软.Extensions.AI)规定:

  • 表达式 - 你控制代理环环,而不是框架
  • 强打字 - 工具定义和功能要求的汇编时间安全
  • 头等可观测性 - 内置遥测、伐木和通过开放遥测进行分布追踪
  • 企业边界 - 设计用于生产.NET系统,具有适当的DI、配置和生命周期管理。
  • 多模式支助 - OpenAI、Azure OpenAI、Ollama和其他供应商的抽象事件
  • 语义内核整合 - 使用微软更大的人工智能书架

关键组成部分:

  • IChatClient - 聊天补全统一界面
  • IEmbeddingGenerator - 供应商之间的矢量嵌入
  • AIFunction - 安全类型功能调用
  • 中件管道 -- -- 用于伐木、重试、缓存、遥测

示例:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddChatClient(builder => 
    builder.UseOllama("llama3.2")
           .UseOpenTelemetry()
           .UseLogging());

var app = builder.Build();

app.MapPost("/chat", async (IChatClient client, string message) =>
{
    var response = await client.CompleteAsync(message);
    return response.Content;
});

我仍然停留在低层

即使有微软的框架, 我更喜欢保持核心管弦 明确:

我不想:

  • 透明规划者 - 自主决定应调用何种工具的框架
  • 隐含工具选择 - 基于自然语言说明的魔术路线
  • 隐藏重试逻辑 - 框架管理的错误回收我不能检查

我想:

  • 可见环 - 我看见我代码的每一个迭代
  • 可试验步骤 - 我可以测试单位的判断逻辑
  • 可替换组件 - 我可以换掉LLM 工具 验证层
  • 明确状态 - 我确切地知道上下文的内容

微软的代理框架比朗查因更接近于我的想法。 它尊重.NET模式, 正确使用依赖注射, 并且不对抗生态系统。 但我还是更喜欢自己写管弦曲。

何时使用 Microsoft 代理框架 :

  • 建立有功能呼叫的聊天应用程序
  • 需要多模式支持(OpenAI、Azure、Ollama之间的交换)
  • 需要企业特征(遥测、伐木、分布式跟踪)
  • 在一个更倾向于框架一致性的团队中工作
  • 建筑在语义核心楼顶上

何时去无框架:

  • 您需要完全控制代理环环
  • 你在建立定制推理模式
  • 你想要零抽象间接间接
  • 您正在优化特定用途案例( 如 CSV 分析或网络剪切) 。
  • 你想知道它是如何工作的

框架并不消除建筑决策。 您仍然选择了上下文、 数据块、 以及何时重试。 它只会让管道更容易操作 。

为什么这个规模比长远更好

没有框架的系统年龄更高,原因如下:

业绩 业绩业绩 业绩业绩 我的CSV查询服务运行子100米 因为LLM和DuckDB之间没有框架

成本可预测性 框架管理的记忆里没有隐蔽的快速通货膨胀

可调试性 - 当有东西坏了,我在调试我的代码, 而不是反向设计一个框架的魔力。

隐私隐私 - 使用有严格数据居住要求的系统,确切知道机器有什么问题。

离线情景 - 边缘装置、空控网络、受管制的环境,框架包括互联网接入和云服务。

监管合规 - 在金融、医疗和政府方面,你经常需要解释和审核每一个决定。“框架做到了”是一个无法接受的答案。

环境越受限制,你就越需要明确的控制。

我什么时候会用兰钱

解除批评者的武装: 有一些合法的案例, 我会找到朗钱恩。

Hackathons( 哈卡通) - 快速演示比建筑更重要

丢弃的POCs - 如果你正在验证一个想法 并计划改写 制作反正。

Python重重队 - 如果你的团队在Python已经流利, 生态系统的适合性是很强的。

教学概念 Lang Chain的抽象学可以帮助初学者在建立自己的模式之前 了解特工的模式

了解时间 使用某物与知道何时使用一样重要。

更广泛的模式:框架与原则与原则

这不是兰钱,而是框架和第一原则工程之间的取舍

框架加速了熟悉问题。 如果您正在构建第100 CRUD API, 将会达到实体框架或 Dapper 。 模式会稳定下来 。

但是LLM 动力系统?正确的抽象元素还没有解决。 我们不知道“ 链条” 或“ 试剂” 或“ 试剂” 是正确的心理模型。 我们还在研究。

在那种环境中,我更喜欢靠近金属:

  • 通过API直拨电话(电话)的LLMs(电话)OllamaSharp,开放的AISDK)
  • 通过直线弦建筑迅速建造
  • 通过特定领域的逻辑进行验证
  • 通过专用发动机(SQL、搜索等)执行

作为一个.NET开发者,我对如何建立系统有强烈的看法: 明确的寿命、强的打字、连续不断的同步、 依赖性注射以进行测试。

Lang Chain的抽象观点并不完全符合这些观点,所以我不使用它。

外卖

如果你是一个.NET开发商 看着朗钱 想知道"我需要这个吗?" 这就是我的回答:

你需要解决兰钱解决的问题 - 环境管理、工具协调、重试逻辑、可观察性。

你不需要兰钱解决他们 - 特别是如果你重视清晰度, 强打字, 和生产硬化 相对于快速原型。

原则一建立在以下基础上:

"LLMs理由 引擎计算 指挥权属于你"

或更简单的:

"如果你理解一个框架解决的问题, 你经常不需要这个框架。"

建立在生态系统中有意义的系统, 利用你的语言的风格。对我来说,就是C#, 强大的打字, 明确的控制流, 和决定性的执行层。

对你来说,可能不同,那很好

目标不是回避框架,而是有意识地选择框架,了解框架提供的内容和成本。


进一步阅读:

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.