|
| 1 | +# Tool Use — C# |
| 2 | + |
| 3 | +For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). |
| 4 | + |
| 5 | +## Tool Use |
| 6 | + |
| 7 | +### Defining a tool |
| 8 | + |
| 9 | +`Tool` (NOT `ToolParam`) with an `InputSchema` record. `InputSchema.Type` is auto-set to `"object"` by the constructor — don't set it. `ToolUnion` has an implicit conversion from `Tool`, triggered by the collection expression `[...]`. |
| 10 | + |
| 11 | +```csharp |
| 12 | +using System.Text.Json; |
| 13 | +using Anthropic.Models.Messages; |
| 14 | + |
| 15 | +var parameters = new MessageCreateParams |
| 16 | +{ |
| 17 | + Model = Model.ClaudeSonnet4_6, |
| 18 | + MaxTokens = 16000, |
| 19 | + Tools = [ |
| 20 | + new Tool { |
| 21 | + Name = "get_weather", |
| 22 | + Description = "Get the current weather in a given location", |
| 23 | + InputSchema = new() { |
| 24 | + Properties = new Dictionary<string, JsonElement> { |
| 25 | + ["location"] = JsonSerializer.SerializeToElement( |
| 26 | + new { type = "string", description = "City name" }), |
| 27 | + }, |
| 28 | + Required = ["location"], |
| 29 | + }, |
| 30 | + }, |
| 31 | + ], |
| 32 | + Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }], |
| 33 | +}; |
| 34 | +``` |
| 35 | + |
| 36 | +Derived from `anthropic-sdk-csharp/src/Anthropic/Models/Messages/Tool.cs` and `ToolUnion.cs:799` (implicit conversion). |
| 37 | + |
| 38 | +See [shared tool use concepts](../../shared/tool-use-concepts.md) for the loop pattern. |
| 39 | +### Converting response content to the follow-up assistant message |
| 40 | + |
| 41 | +When echoing Claude's response back in the assistant turn, **there is no `.ToParam()` helper** — manually reconstruct each `ContentBlock` variant as its `*Param` counterpart. Do NOT use `new ContentBlockParam(block.Json)`: it compiles and serializes, but `.Value` stays `null` so `TryPick*`/`Validate()` fail (degraded JSON pass-through, not the typed path). |
| 42 | + |
| 43 | +```csharp |
| 44 | +using Anthropic.Models.Messages; |
| 45 | + |
| 46 | +Message response = await client.Messages.Create(parameters); |
| 47 | + |
| 48 | +// No .ToParam() — reconstruct per variant. Implicit conversions from each |
| 49 | +// *Param type to ContentBlockParam mean no explicit wrapper. |
| 50 | +List<ContentBlockParam> assistantContent = []; |
| 51 | +List<ContentBlockParam> toolResults = []; |
| 52 | +foreach (ContentBlock block in response.Content) |
| 53 | +{ |
| 54 | + if (block.TryPickText(out TextBlock? text)) |
| 55 | + { |
| 56 | + assistantContent.Add(new TextBlockParam { Text = text.Text }); |
| 57 | + } |
| 58 | + else if (block.TryPickThinking(out ThinkingBlock? thinking)) |
| 59 | + { |
| 60 | + // Signature MUST be preserved — the API rejects tampering |
| 61 | + assistantContent.Add(new ThinkingBlockParam |
| 62 | + { |
| 63 | + Thinking = thinking.Thinking, |
| 64 | + Signature = thinking.Signature, |
| 65 | + }); |
| 66 | + } |
| 67 | + else if (block.TryPickRedactedThinking(out RedactedThinkingBlock? redacted)) |
| 68 | + { |
| 69 | + assistantContent.Add(new RedactedThinkingBlockParam { Data = redacted.Data }); |
| 70 | + } |
| 71 | + else if (block.TryPickToolUse(out ToolUseBlock? toolUse)) |
| 72 | + { |
| 73 | + // ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional — don't copy it |
| 74 | + assistantContent.Add(new ToolUseBlockParam |
| 75 | + { |
| 76 | + ID = toolUse.ID, |
| 77 | + Name = toolUse.Name, |
| 78 | + Input = toolUse.Input, |
| 79 | + }); |
| 80 | + // Execute the tool; collect ONE result per tool_use block — the API |
| 81 | + // rejects the follow-up if any tool_use ID lacks a matching tool_result. |
| 82 | + string result = ExecuteYourTool(toolUse.Name, toolUse.Input); |
| 83 | + toolResults.Add(new ToolResultBlockParam |
| 84 | + { |
| 85 | + ToolUseID = toolUse.ID, |
| 86 | + Content = result, |
| 87 | + }); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +// Follow-up: prior messages + assistant echo + user tool_result(s) |
| 92 | +List<MessageParam> followUpMessages = |
| 93 | +[ |
| 94 | + .. parameters.Messages, |
| 95 | + new() { Role = Role.Assistant, Content = assistantContent }, |
| 96 | + new() { Role = Role.User, Content = toolResults }, |
| 97 | +]; |
| 98 | +``` |
| 99 | + |
| 100 | +`ToolResultBlockParam` has no tuple constructor — use the object initializer. `Content` is a string-or-list union; a plain `string` implicitly converts. |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +## Structured Output |
| 105 | + |
| 106 | +```csharp |
| 107 | +OutputConfig = new OutputConfig { |
| 108 | + Format = new JsonOutputFormat { |
| 109 | + Schema = new Dictionary<string, JsonElement> { |
| 110 | + ["type"] = JsonSerializer.SerializeToElement("object"), |
| 111 | + ["properties"] = JsonSerializer.SerializeToElement( |
| 112 | + new { name = new { type = "string" } }), |
| 113 | + ["required"] = JsonSerializer.SerializeToElement(new[] { "name" }), |
| 114 | + }, |
| 115 | + }, |
| 116 | +}, |
| 117 | +``` |
| 118 | + |
| 119 | +`JsonOutputFormat.Type` is auto-set to `"json_schema"` by the constructor. `Schema` is `required`. |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Anthropic-Defined Tools |
| 124 | + |
| 125 | +Web search, bash, text editor, and code execution are Anthropic-defined tools with built-in schemas. Web search and code execution are server-executed; bash and text editor are client-executed (you handle the `tool_use` locally — see `shared/tool-use-concepts.md`). Type names are version-suffixed; constructors auto-set `name`/`type`. **Wrap each in `new ToolUnion(...)` explicitly.** |
| 126 | + |
| 127 | +```csharp |
| 128 | +Tools = [ |
| 129 | + new ToolUnion(new WebSearchTool20260209()), |
| 130 | + new ToolUnion(new ToolBash20250124()), |
| 131 | + new ToolUnion(new ToolTextEditor20250728()), |
| 132 | + new ToolUnion(new CodeExecutionTool20260120()), |
| 133 | +], |
| 134 | +``` |
| 135 | + |
| 136 | +Also available: `new ToolUnion(new WebFetchTool20260209())`, `new ToolUnion(new MemoryTool20250818())`. `WebSearchTool20260209` optionals: `AllowedDomains`, `BlockedDomains`, `MaxUses`, `UserLocation`. |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## Tool Runner (Beta) |
| 141 | + |
| 142 | +The C# SDK provides a `BetaToolRunner` for automatic tool execution loops. Define tools with raw JSON schemas, and the runner handles the API call → tool execution → result feedback loop. |
| 143 | + |
| 144 | +```csharp |
| 145 | +using Anthropic.Models.Beta.Messages; |
| 146 | + |
| 147 | +// Define tools and create params as shown in the Tool Use section above, |
| 148 | +// but using the beta namespace types (BetaToolUnion, etc.) |
| 149 | +var runner = client.Beta.Messages.ToolRunner(betaParams); |
| 150 | + |
| 151 | +await foreach (BetaMessage message in runner) |
| 152 | +{ |
| 153 | + foreach (var block in message.Content) |
| 154 | + { |
| 155 | + if (block.TryPickText(out var text)) |
| 156 | + { |
| 157 | + Console.WriteLine(text.Text); |
| 158 | + } |
| 159 | + } |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +--- |
| 164 | + |
0 commit comments