当前位置: 首页 > news >正文

C# 实现简版 Claude Code | 4 个工具覆盖 90% 场景(2)

该系列文章基于github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。

v1: 模型即代理 - 4 个工具覆盖 90% 场景

本文是 Learn Claude Code (C# 版) 系列的第二篇,对应代码文件v1_basic_agent.cs

从 v0 到 v1

v0 用一个 bash 工具证明了最简可行性。但在实际使用中:

  • cat读文件需要额外的 tokens 来构造命令

  • echo写文件容易出错(引号转义、换行处理)

  • 缺乏安全检查,可能逃逸工作目录

v1 引入4 个专用工具,这是 Claude Code 20+ 工具的精华提炼。

四个核心工具

工具

用途

覆盖场景

bash

运行任何命令

git, npm, dotnet, curl...

read_file

读取文件内容

理解代码

write_file

创建/覆盖文件

生成新文件

edit_file

精确文本替换

修改现有代码

为什么是这 4 个?因为编程任务本质上就是:探索 → 理解 → 修改 → 验证

工具定义详解

bash - 通往一切的大门

new Tool { Name = "bash", Description = "运行 shell 命令。用于: ls, find, grep, git, npm, dotnet 等。", InputSchema = new InputSchema { Type = "object", Properties = new Dictionary<string, JsonElement> { ["command"] = JsonDocument.Parse("""{"type": "string", "description": "要执行的命令"}""").RootElement }, Required = ["command"] } }

bash 仍然是最重要的工具——它是模型与外部世界交互的通道。

read_file - 理解现有代码

new Tool { Name = "read_file", Description = "读取文件内容。返回 UTF-8 文本。", InputSchema = new InputSchema { Type = "object", Properties = new Dictionary<string, JsonElement> { ["path"] = JsonDocument.Parse("""{"type": "string", "description": "文件的相对路径"}""").RootElement, ["limit"] = JsonDocument.Parse("""{"type": "integer", "description": "最大读取行数"}""").RootElement }, Required = ["path"] } }

limit参数很重要——大文件只需要看前 N 行,避免上下文溢出。

write_file - 创建新文件

new Tool { Name = "write_file", Description = "将内容写入文件。如果需要会创建父目录。", InputSchema = new InputSchema { Type = "object", Properties = new Dictionary<string, JsonElement> { ["path"] = JsonDocument.Parse("""{"type": "string"}""").RootElement, ["content"] = JsonDocument.Parse("""{"type": "string"}""").RootElement }, Required = ["path", "content"] } }

自动创建父目录是个贴心的设计——模型不需要先mkdir -p

edit_file - 精确修改

new Tool { Name = "edit_file", Description = "替换文件中的精确文本。用于局部编辑。", InputSchema = new InputSchema { Type = "object", Properties = new Dictionary<string, JsonElement> { ["path"] = JsonDocument.Parse("""{"type": "string"}""").RootElement, ["old_text"] = JsonDocument.Parse("""{"type": "string", "description": "要查找的精确文本"}""").RootElement, ["new_text"] = JsonDocument.Parse("""{"type": "string", "description": "替换文本"}""").RootElement }, Required = ["path", "old_text", "new_text"] } }

精确文本匹配是关键——模型必须提供要替换的确切内容,这避免了意外修改。

安全机制

路径安全检查

string SafePath(string p) { var fullPath = Path.GetFullPath(Path.Combine(workDir, p)); if (!fullPath.StartsWith(workDir)) throw new InvalidOperationException($"路径逃逸工作区: {p}"); return fullPath; }

防止../../../etc/passwd这样的路径遍历攻击。

危险命令阻止

string[] dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]; if (dangerous.Any(d => command.Contains(d))) return "Error: 危险命令已阻止";

基本的安全护栏,阻止显然危险的命令。

输出截断

return output[..Math.Min(output.Length, 50000)];

50KB 上限防止一个巨大的输出撑爆上下文。

超时控制

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); await process.WaitForExitAsync(cts.Token);

60 秒超时防止命令挂起。

Agent 循环详解

async Task AgentLoopAsync(List<Message> messages) { while (true) { // 步骤 1: 调用模型 var response = await client.CreateMessageAsync( modelId, messages, new MessageParameters { System = systemPrompt, Tools = tools, MaxTokens = 8000 }); // 步骤 2: 收集工具调用并打印文本输出 var toolCalls = new List<ToolUseBlock>(); foreach (var block in response.Content) { if (block.Text is not null) Console.WriteLine(block.Text.Text); // 模型的思考 if (block.ToolUse is not null) toolCalls.Add(block.ToolUse); // 工具调用 } // 步骤 3: 如果没有工具调用,任务完成 if (response.StopReason != StopReason.ToolUse) { messages.Add(response.AsRequestMessage()); return; } // 步骤 4: 执行每个工具并收集结果 var results = new List<ContentBlock>(); foreach (var tc in toolCalls) { var output = await ExecuteToolAsync(tc.Name, tc.Input); results.Add(ContentBlock.CreateToolResult(tc.Id, output)); } // 步骤 5: 添加到对话并继续 messages.Add(response.AsRequestMessage()); messages.Add(new Message { Role = "user", Content = [.. results] }); } }

为什么工具结果是 "user" 消息?

Anthropic API 的设计:工具结果作为user角色的消息发送,包含tool_result类型的内容块。这维持了 user/assistant 的交替模式。

消息历史结构

[user] "创建一个 hello world 程序" [assistant] "我来创建..." + tool_use(write_file, {path: "hello.cs", content: "..."}) [user] tool_result(id, "Wrote 50 bytes to hello.cs") [assistant] "文件已创建。让我运行它..." + tool_use(bash, {command: "dotnet run hello.cs"}) [user] tool_result(id, "Hello, World!") [assistant] "程序运行成功,输出了 'Hello, World!'"

工具分发器

async Task<string> ExecuteToolAsync(string name, JsonElement args) { return name switch { "bash" => await RunBashAsync(args.GetProperty("command").GetString()!), "read_file" => RunRead( args.GetProperty("path").GetString()!, args.TryGetProperty("limit", outvar limit) ? limit.GetInt32() : null), "write_file" => RunWrite( args.GetProperty("path").GetString()!, args.GetProperty("content").GetString()!), "edit_file" => RunEdit( args.GetProperty("path").GetString()!, args.GetProperty("old_text").GetString()!, args.GetProperty("new_text").GetString()!), _ => $"Unknown tool: {name}" }; }

C# 的 switch expression 让分发器代码非常简洁。

关键洞察

1. 模型即代理

传统编程:程序员写逻辑,代码执行。 Agent 编程:程序员提供工具,模型决定逻辑

v1 的代码只做了两件事:

  1. 定义工具(模型能做什么)

  2. 运行循环(让模型持续决策)

2. 工具即接口

工具定义就是给模型的 API 文档:

  • Name- 函数名

  • Description- 函数说明

  • InputSchema- 参数类型

模型根据这些信息决定调用什么、传什么参数。

3. 上下文即记忆

messages列表就是 Agent 的记忆。它累积:

  • 用户的请求

  • 模型的思考和工具调用

  • 工具的执行结果

模型通过阅读历史来理解当前状态。

运行示例

Mini Claude Code v1 (C#) - /path/to/project 输入 'exit' 退出。 You: 创建一个计算斐波那契数列的函数 我来创建一个计算斐波那契数列的 C# 文件。 > write_file: {"path":"Fibonacci.cs","content":"..."} Wrote 450 bytes to Fibonacci.cs 文件已创建。让我运行测试一下: > bash: {"command":"dotnet run Fibonacci.cs"} 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 成功!Fibonacci.cs 已创建并测试通过。

从 v1 到 v2

v1 解决了工具效率问题,但对于复杂任务:

You: 重构 auth 模块、添加测试、更新文档

模型可能会:

  • 随机跳转任务

  • 忘记已完成的步骤

  • 中途失焦

v2 将引入TodoManager——让计划显式化,给模型一个"外部记忆"来追踪进度。

总结

v1 的哲学:

模型即代理。代码提供工具,模型做决策。

4 个工具覆盖了 90% 的编程场景。核心循环只有 30 行。这就是 Agent 的本质——简单到令人惊讶。

点击阅读原文,获取仓库地址:👇👇👇

http://www.jsqmd.com/news/336762/

相关文章:

  • do_exit()
  • 打造自己的大模型-01篇|LLaMA-Factory微调Llama3和其占用资源分析
  • 计算机毕业设计之springboot基于微信小程序的高校考研系统的设计与实现
  • 因果表达大不同:汉语“意合”vs英语“形合”,一篇看懂语言逻辑差异
  • 2026年最新CCTalk下载与安装全攻略:从入门到高效使用详解
  • 综述不会写?9个一键生成论文工具深度测评:继续教育毕业论文写作全攻略
  • Android onReceive方法详解:使用教程与常见问题
  • 电子秤实物量产资料 原理图和PCB文件及BOM,源码HEX 量产HX711电子秤采集模块全套资料 1
  • 遵义市英语雅思培训机构推荐;2026权威测评出国雅思辅导机构口碑榜单 - 老周说教育
  • 欣旺达冲刺港股:9个月营收435亿 王明旺家族控制28%股权
  • 阿童木机器人冲刺港股:9个月营收1.57亿利润94万 杨浩涌与联想基金是股东
  • 计算机毕业设计之springboot基于java的电影评价系统
  • 遵义市英语雅思培训机构推荐,2026权威测评出国雅思辅导机构口碑榜单 - 老周说教育
  • 2026 年设备售后工单系统 TOP5 排行榜:冠唐科技拔得头筹 - 深度智识库
  • Shell脚本if elif语法与MySQL数据库操作实用教程
  • 易买工品冲刺港股:9个月营收5.5亿,亏2.9亿 启明与高榕是股东
  • 苏宁易购预计2025年扣非后净亏41.25亿到46.5亿 推进债务和解
  • 【Rust日报】Sol——一个用 Rust 编写的网页转 Markdown 工具。
  • 遵义市英语雅思培训机构推荐:2026权威测评出国雅思辅导机构口碑榜单 - 老周说教育
  • 6-MySQL
  • python解决滑动验证码方案
  • Rust日报】RustyBoard——最大的Rust专属招聘网站
  • 库克反击中国手机,大量安卓用户转买iPhone,真是风水轮流转!
  • 别让你的 AI 像个实习生!从 Skills 到 MCP:教你如何给大模型装上‘工业级’机械臂
  • UE5 多线程(5-3):锁的智能指针版本 FScopeLock 与 FScopeUnlock。
  • 2026年非开挖管道施工企业top3深度评估:资质、设备、案例、服务四维解析 - 深度智识库
  • python基于微信小程序的智慧社区小区活动物业管理系统的设计与实现
  • 如何构建行业 Agent 的 RAG
  • 基于Python 实现直线段生成算法和圆弧生成算法
  • 盘点2026年办公空间装修性价比高的公司,不容错过 - 工业设备