보장된 구조를 위한 tool_use
구문 오류를 없애고 필수 필드를 강제합니다
보장된 구조를 위한 tool_use은(는) CoddyKit의 무료 Claude Architect 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Claude Architect 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Claude Architect 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why Free Text Fails
You asked Claude for JSON and parsed the reply with json.loads(). It worked 95% of the time. The other 5%? A stray markdown fence, a trailing comma, a chatty preamble like "Here's the JSON you requested:" — and your pipeline throws.
For a Claude Certified Architect, that 5% is the whole problem. Production extraction, classification, and routing flows can't depend on the model happening to format text correctly. This lesson shows how tool_use plus a JSON Schema turns a probabilistic format into a guaranteed one — eliminating syntax errors and enforcing required fields.
The Core Idea
A tool isn't only for taking actions. A tool definition is also a typed output contract. When you declare a tool with an input_schema, you're telling Claude exactly what shape its arguments must take — and the API validates the tool call against that schema.
So instead of asking for JSON in prose and hoping, you define a tool whose parameters are the structure you want, then force Claude to call it. The model fills in the fields; the schema guarantees the shape. Per the fact sheet: tool_use + JSON Schema eliminates syntax errors and enforces required fields.
Defining the Schema Tool
Here's an extraction tool. Note the three pillars of a good tool definition: a clear name, a descriptive description, and a precise input_schema with per-property descriptions and a required list.
The schema below extracts a support ticket. We require only the fields that are always present — more on that decision soon.
ticket_tool = {
"name": "record_ticket",
"description": "Record a structured support ticket extracted from the user message.",
"input_schema": {
"type": "object",
"properties": {
"summary": {
"type": "string",
"description": "One-line summary of the issue",
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "Triage priority",
},
},
"required": ["summary", "priority"],
},
}tool_choice: Forcing the Structure
Declaring the tool isn't enough — with the default, Claude might answer in text instead. The tool_choice parameter controls that decision:
{"type": "auto"}— Claude picks text or a tool (the default).{"type": "any"}— Claude must call some tool. This is what guarantees structured output.{"type": "tool", "name": "X"}— force one specific tool by name.
When you have exactly one schema tool and want a guaranteed call to it, force it by name. This removes the "answer in prose" escape hatch entirely.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[ticket_tool],
tool_choice={"type": "tool", "name": "record_ticket"},
messages=[{"role": "user", "content": ticket_text}],
)Reading the Tool Input Back
When Claude calls the tool, the response contains a tool_use content block. The structured data lives in block.input — already parsed into a Python dict by the SDK, conforming to your schema.
One discipline from the fact sheet: always parse with the SDK's parsed object, never raw-string-match the serialized input. Recent models may escape Unicode or forward slashes differently, so treat block.input as structured data, not text.
for block in response.content:
if block.type == "tool_use":
ticket = block.input # dict, matches the schema
print(ticket["summary"], ticket["priority"])
# Do NOT json.loads a re-serialized string here —
# block.input is already the structured object.The required Trap
This is the single most tested decision in this lesson. The rule from the fact sheet is blunt:
Mark a field required ONLY if it is always present. NEVER require a field that may be absent — the model will fabricate a value to satisfy the schema.
If customer_id sometimes isn't in the source text but you list it under required, Claude won't return an empty result — it will invent a plausible-looking ID to make the tool call valid. That's a silent data-integrity bug. Required means "guaranteed by the source," not "nice to have."
Optional Fields Done Right
Fields that may be absent simply stay out of the required array. Claude omits them when the source lacks the data, instead of hallucinating.
Here customer_id and attachments are optional; only summary and priority — which we can always derive — are required.
"properties": {
"summary": {"type": "string"},
"priority": {"type": "string",
"enum": ["low", "medium", "high", "urgent"]},
"customer_id": {"type": "string",
"description": "Only if explicitly stated"},
"attachments": {"type": "array", "items": {"type": "string"}},
},
"required": ["summary", "priority"] # NOT customer_id / attachmentsEnums With an Escape Hatch
Enums constrain a field to a fixed set of values — great for categories, labels, and priorities. But a rigid enum forces every input into one of your predefined buckets, which breaks on edge cases you didn't anticipate.
The fact-sheet pattern for extensibility: use enums with an "other" value plus a free-text detail field. When Claude hits something outside your categories, it picks other and explains in the detail field — instead of mislabeling.
"category": {
"type": "string",
"enum": ["billing", "bug", "account", "other"],
"description": "Use 'other' if none of the named categories fit",
},
"category_detail": {
"type": "string",
"description": "Free-text explanation, required only when category is 'other'",
}Validate, Then Retry With Feedback
The schema guarantees syntactic shape, but you still validate semantics — totals that add up, dates in range, cross-field consistency. Use a Pydantic-style validator on block.input.
When validation fails on a format, structural, or arithmetic error, use retry-with-feedback: resend the original source, the wrong output, and the exact validation error. Critically, the fact sheet warns: retry does NOT help when the information is simply absent from the source — retrying just invites fabrication.
from pydantic import BaseModel, ValidationError
class Ticket(BaseModel):
summary: str
priority: str
try:
ticket = Ticket(**block.input)
except ValidationError as e:
# Resend: original text + wrong output + this exact error.
# Only worth it for format/arithmetic faults, not missing data.
retry(ticket_text, block.input, str(e))Self-Correction for Numbers
For numeric extraction, a powerful schema trick is to make Claude expose its work so discrepancies are detectable. Per the fact sheet: extract both a calculated_total and a stated_total to detect discrepancies.
If the document says "Total: $1,200" but the line items sum to $1,150, two separate fields surface the mismatch — your validator catches it instead of trusting a single number. The schema becomes an audit instrument, not just a container.
"properties": {
"line_items": {"type": "array", "items": {"type": "number"}},
"calculated_total": {"type": "number",
"description": "Sum you computed from the line items"},
"stated_total": {"type": "number",
"description": "Total as literally written in the document"},
},
"required": ["line_items", "calculated_total", "stated_total"]Where This Fits in the Loop
Structured output is one stop in the larger agentic loop: request → inspect stop_reason → if tool_use, handle the tool → repeat until end_turn. A forced schema call returns stop_reason: "tool_use"; you read block.input and proceed.
Two architect-grade reminders: terminate on the stop_reason, never by parsing text for words like "done." And keep provenance — map each extracted claim back to its source (doc name, quote, date) so downstream consumers can trust and audit the structured result.
Quick Check
You're extracting invoices. The source PDFs sometimes omit a purchase-order number. A teammate proposes putting po_number in the schema's required array "so we always get one." What should you do?
Recap: Guaranteed Structure
Key takeaways for the exam and for production:
- tool_use + JSON Schema eliminates syntax errors and enforces required fields — far more reliable than parsing prose JSON.
- tool_choice:
"any"guarantees some tool call;{"type":"tool","name":"X"}forces a specific schema tool;"auto"leaves it optional. - required = always present. Never require a possibly-absent field — it causes fabrication. Optional fields simply stay out of
required. - Enums need an "other" + detail field for extensibility.
- Read structured data from
block.input; never raw-string-match the serialized tool input. - Retry-with-feedback fixes format/arithmetic faults — not absent information. Use
calculated_totalvsstated_totalfor self-correction, and keep provenance.
자주 묻는 질문
“보장된 구조를 위한 tool_use” 강의는 무료인가요?
네 — “보장된 구조를 위한 tool_use” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Claude Architect 강의 전체를 잠금 해제할 수 있습니다. Claude Architect 강의에는 총 4개의 강의가 포함되어 있습니다.
“보장된 구조를 위한 tool_use”에서 뭘 배우나요?
구문 오류를 없애고 필수 필드를 강제합니다 브라우저에서 직접 실행하는 실습 코드로 Claude Architect을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Claude Architect을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Claude Architect은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“보장된 구조를 위한 tool_use” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Claude Architect 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Claude Architect 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 보장된 구조를 위한 tool_use
- JSON 스키마 설계
- 필수 필드와 선택적/널 허용 필드
- 확장성을 위한 'other' 열거형 값