claude-code-system-prompts/system-prompts/data-tool-use-reference-c.md
2026-06-18 17:15:00 -06:00

6.1 KiB

Tool Use — C#

For conceptual overview (tool definitions, tool choice, tips), see shared/tool-use-concepts.md.

Tool Use

Defining a tool

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 [...].

using System.Text.Json;
using Anthropic.Models.Messages;

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeSonnet4_6,
    MaxTokens = 16000,
    Tools = [
        new Tool {
            Name = "get_weather",
            Description = "Get the current weather in a given location",
            InputSchema = new() {
                Properties = new Dictionary<string, JsonElement> {
                    ["location"] = JsonSerializer.SerializeToElement(
                        new { type = "string", description = "City name" }),
                },
                Required = ["location"],
            },
        },
    ],
    Messages = [new() { Role = Role.User, Content = "Weather in Paris?" }],
};

Derived from anthropic-sdk-csharp/src/Anthropic/Models/Messages/Tool.cs and ToolUnion.cs:799 (implicit conversion).

See shared tool use concepts for the loop pattern.

Converting response content to the follow-up assistant message

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).

using Anthropic.Models.Messages;

Message response = await client.Messages.Create(parameters);

// No .ToParam() — reconstruct per variant. Implicit conversions from each
// *Param type to ContentBlockParam mean no explicit wrapper.
List<ContentBlockParam> assistantContent = [];
List<ContentBlockParam> toolResults = [];
foreach (ContentBlock block in response.Content)
{
    if (block.TryPickText(out TextBlock? text))
    {
        assistantContent.Add(new TextBlockParam { Text = text.Text });
    }
    else if (block.TryPickThinking(out ThinkingBlock? thinking))
    {
        // Signature MUST be preserved — the API rejects tampering
        assistantContent.Add(new ThinkingBlockParam
        {
            Thinking = thinking.Thinking,
            Signature = thinking.Signature,
        });
    }
    else if (block.TryPickRedactedThinking(out RedactedThinkingBlock? redacted))
    {
        assistantContent.Add(new RedactedThinkingBlockParam { Data = redacted.Data });
    }
    else if (block.TryPickToolUse(out ToolUseBlock? toolUse))
    {
        // ToolUseBlock has required Caller; ToolUseBlockParam.Caller is optional — don't copy it
        assistantContent.Add(new ToolUseBlockParam
        {
            ID = toolUse.ID,
            Name = toolUse.Name,
            Input = toolUse.Input,
        });
        // Execute the tool; collect ONE result per tool_use block — the API
        // rejects the follow-up if any tool_use ID lacks a matching tool_result.
        string result = ExecuteYourTool(toolUse.Name, toolUse.Input);
        toolResults.Add(new ToolResultBlockParam
        {
            ToolUseID = toolUse.ID,
            Content = result,
        });
    }
}

// Follow-up: prior messages + assistant echo + user tool_result(s)
List<MessageParam> followUpMessages =
[
    .. parameters.Messages,
    new() { Role = Role.Assistant, Content = assistantContent },
    new() { Role = Role.User, Content = toolResults },
];

ToolResultBlockParam has no tuple constructor — use the object initializer. Content is a string-or-list union; a plain string implicitly converts.


Structured Output

OutputConfig = new OutputConfig {
    Format = new JsonOutputFormat {
        Schema = new Dictionary<string, JsonElement> {
            ["type"] = JsonSerializer.SerializeToElement("object"),
            ["properties"] = JsonSerializer.SerializeToElement(
                new { name = new { type = "string" } }),
            ["required"] = JsonSerializer.SerializeToElement(new[] { "name" }),
        },
    },
},

JsonOutputFormat.Type is auto-set to "json_schema" by the constructor. Schema is required.


Anthropic-Defined Tools

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.

Tools = [
    new ToolUnion(new WebSearchTool20260209()),
    new ToolUnion(new ToolBash20250124()),
    new ToolUnion(new ToolTextEditor20250728()),
    new ToolUnion(new CodeExecutionTool20260120()),
],

Also available: new ToolUnion(new WebFetchTool20260209()), new ToolUnion(new MemoryTool20250818()). WebSearchTool20260209 optionals: AllowedDomains, BlockedDomains, MaxUses, UserLocation.


Tool Runner (Beta)

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.

using Anthropic.Models.Beta.Messages;

// Define tools and create params as shown in the Tool Use section above,
// but using the beta namespace types (BetaToolUnion, etc.)
var runner = client.Beta.Messages.ToolRunner(betaParams);

await foreach (BetaMessage message in runner)
{
    foreach (var block in message.Content)
    {
        if (block.TryPickText(out var text))
        {
            Console.WriteLine(text.Text);
        }
    }
}