Skip to content

Commit ad56677

Browse files
feat(api): add support for Structured Outputs in the Messages API
1 parent 7780e90 commit ad56677

File tree

48 files changed

+977
-109
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+977
-109
lines changed

.github/workflows/claude.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ jobs:
3636
with:
3737
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
3838

39+
# Allow github-actions[bot] to trigger Claude Code Action
40+
allowed_bots: "stainless-app"
41+
3942
# This is an optional setting that allows Claude to read CI results on PRs
4043
additional_permissions: |
4144
actions: read
@@ -47,4 +50,3 @@ jobs:
4750
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
4851
# or https://code.claude.com/docs/en/cli-reference for available options
4952
# claude_args: '--allowed-tools Bash(gh pr:*)'
50-

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 34
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic%2Fanthropic-f7bcdc13402c6129b30be620021a724945de44cffc6add091798f9cce33a1e32.yml
3-
openapi_spec_hash: e78807e31b9233abc50ccc00304bfa4d
4-
config_hash: 712e84f0cacb3436913f57da3d959b5c
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic%2Fanthropic-4526612d12e919de063708c05d15b78902b5a52d33a6e3eb45708c562d338b18.yml
3+
openapi_spec_hash: 346bef71688ca79b107cf84bc09249ac
4+
config_hash: 0b96ef87fc0758bbc543ffa8435baa2a

api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ from anthropic.types import (
4545
DocumentBlockParam,
4646
ImageBlockParam,
4747
InputJSONDelta,
48+
JSONOutputFormat,
4849
Message,
4950
MessageCountTokensTool,
5051
MessageDeltaUsage,
5152
MessageParam,
5253
MessageTokensCount,
5354
Metadata,
5455
Model,
56+
OutputConfig,
5557
PlainTextSource,
5658
RawContentBlockDelta,
5759
RawContentBlockDeltaEvent,

examples/structured_outputs.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
# /// script
2-
# requires-python = ">=3.9"
3-
# dependencies = [
4-
# "anthropic",
5-
# ]
6-
#
7-
# [tool.uv.sources]
8-
# anthropic = { path = "../", editable = true }
9-
# ///
10-
11-
121
import pydantic
132

143
import anthropic
@@ -27,7 +16,7 @@ class Order(pydantic.BaseModel):
2716
"Hi, I’d like to order 2 packs of Green Tea for 5.50 dollars each."
2817
"""
2918

30-
parsed_message = client.beta.messages.parse(
19+
parsed_message = client.messages.parse(
3120
model="claude-sonnet-4-5",
3221
messages=[{"role": "user", "content": prompt}],
3322
max_tokens=1024,
Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
# /// script
2-
# requires-python = ">=3.9"
3-
# dependencies = [
4-
# "anthropic",
5-
# ]
6-
#
7-
# [tool.uv.sources]
8-
# anthropic = { path = "../", editable = true }
9-
# ///
10-
111
import pydantic
122

133
import anthropic
@@ -23,10 +13,10 @@ class Order(pydantic.BaseModel):
2313

2414
prompt = """
2515
Extract the product name, price, and quantity from this customer message:
26-
"Hi, Id like to order 2 packs of Green Tea for 5.50 dollars each."
16+
"Hi, I'd like to order 2 packs of Green Tea for 5.50 dollars each."
2717
"""
2818

29-
with client.beta.messages.stream(
19+
with client.messages.stream(
3020
model="claude-sonnet-4-5",
3121
messages=[{"role": "user", "content": prompt}],
3222
max_tokens=1024,
@@ -35,3 +25,7 @@ class Order(pydantic.BaseModel):
3525
for event in stream:
3626
if event.type == "text":
3727
print(event.parsed_snapshot())
28+
29+
# Get the final parsed output
30+
final_message = stream.get_final_message()
31+
print(f"\nFinal parsed order: {final_message.parsed_output}")

src/anthropic/lib/_parse/_response.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from ..._types import NotGiven
66
from ..._models import TypeAdapter, construct_type_unchecked
77
from ..._utils._utils import is_given
8+
from ...types.message import Message
9+
from ...types.parsed_message import ParsedMessage, ParsedTextBlock, ParsedContentBlock
810
from ...types.beta.beta_message import BetaMessage
911
from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaTextBlock, ParsedBetaContentBlock
1012

@@ -18,7 +20,7 @@ def parse_text(text: str, output_format: ResponseFormatT | NotGiven) -> Response
1820
return None
1921

2022

21-
def parse_response(
23+
def parse_beta_response(
2224
*,
2325
output_format: ResponseFormatT | NotGiven,
2426
response: BetaMessage,
@@ -42,3 +44,29 @@ def parse_response(
4244
"content": content_list,
4345
},
4446
)
47+
48+
49+
def parse_response(
50+
*,
51+
output_format: ResponseFormatT | NotGiven,
52+
response: Message,
53+
) -> ParsedMessage[ResponseFormatT]:
54+
content_list: list[ParsedContentBlock[ResponseFormatT]] = []
55+
for content in response.content:
56+
if content.type == "text":
57+
content_list.append(
58+
construct_type_unchecked(
59+
type_=ParsedTextBlock[ResponseFormatT],
60+
value={**content.to_dict(), "parsed_output": parse_text(content.text, output_format)},
61+
)
62+
)
63+
else:
64+
content_list.append(content) # type: ignore
65+
66+
return construct_type_unchecked(
67+
type_=ParsedMessage[ResponseFormatT],
68+
value={
69+
**response.to_dict(),
70+
"content": content_list,
71+
},
72+
)

src/anthropic/lib/streaming/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
MessageStopEvent as MessageStopEvent,
77
MessageStreamEvent as MessageStreamEvent,
88
ContentBlockStopEvent as ContentBlockStopEvent,
9+
ParsedMessageStopEvent as ParsedMessageStopEvent,
10+
ParsedMessageStreamEvent as ParsedMessageStreamEvent,
11+
ParsedContentBlockStopEvent as ParsedContentBlockStopEvent,
912
)
1013
from ._messages import (
1114
MessageStream as MessageStream,

0 commit comments

Comments
 (0)