Skip to content

Commit ef3c6dd

Browse files
committed
Send back reasoning_details/signature
1 parent 83d14b1 commit ef3c6dd

File tree

3 files changed

+159
-3
lines changed

3 files changed

+159
-3
lines changed

pydantic_ai_slim/pydantic_ai/models/openrouter.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
from typing import Any, Literal, cast
22

33
from openai import AsyncOpenAI
4-
from openai.types.chat import ChatCompletion
4+
from openai.types.chat import ChatCompletion, ChatCompletionMessageParam
55
from pydantic import BaseModel
66
from typing_extensions import TypedDict
77

88
from ..exceptions import ModelHTTPError, UnexpectedModelBehavior
9-
from ..messages import ModelResponse
9+
from ..messages import (
10+
ModelMessage,
11+
ModelResponse,
12+
ThinkingPart,
13+
)
1014
from ..profiles import ModelProfileSpec
1115
from ..providers import Provider
1216
from ..settings import ModelSettings
@@ -312,6 +316,22 @@ def _process_response(self, response: ChatCompletion | str) -> ModelResponse:
312316
if reasoning_details := getattr(choice.message, 'reasoning_details', None):
313317
provider_details['reasoning_details'] = reasoning_details
314318

319+
if signature := reasoning_details[0].get('signature', None):
320+
thinking_part = cast(ThinkingPart, model_response.parts[0])
321+
thinking_part.signature = signature
322+
315323
model_response.provider_details = provider_details
316324

317325
return model_response
326+
327+
async def _map_messages(self, messages: list[ModelMessage]) -> list[ChatCompletionMessageParam]:
328+
"""Maps a `pydantic_ai.Message` to a `openai.types.ChatCompletionMessageParam` and adds OpenRouter specific parameters."""
329+
openai_messages = await super()._map_messages(messages)
330+
331+
for message, openai_message in zip(messages, openai_messages):
332+
if isinstance(message, ModelResponse):
333+
provider_details = cast(dict[str, Any], message.provider_details)
334+
if reasoning_details := provider_details.get('reasoning_details', None): # pragma: lax no cover
335+
openai_message['reasoning_details'] = reasoning_details # type: ignore[reportGeneralTypeIssue]
336+
337+
return openai_messages
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '133'
12+
content-type:
13+
- application/json
14+
host:
15+
- openrouter.ai
16+
method: POST
17+
parsed_body:
18+
messages:
19+
- content: Who are you. Think about it.
20+
role: user
21+
model: anthropic/claude-3.7-sonnet:thinking
22+
stream: false
23+
uri: https://openrouter.ai/api/v1/chat/completions
24+
response:
25+
headers:
26+
access-control-allow-origin:
27+
- '*'
28+
connection:
29+
- keep-alive
30+
content-length:
31+
- '4024'
32+
content-type:
33+
- application/json
34+
permissions-policy:
35+
- payment=(self "https://checkout.stripe.com" "https://connect-js.stripe.com" "https://js.stripe.com" "https://*.js.stripe.com"
36+
"https://hooks.stripe.com")
37+
referrer-policy:
38+
- no-referrer, strict-origin-when-cross-origin
39+
transfer-encoding:
40+
- chunked
41+
vary:
42+
- Accept-Encoding
43+
parsed_body:
44+
choices:
45+
- finish_reason: stop
46+
index: 0
47+
logprobs: null
48+
message:
49+
content: "I am Claude, an AI assistant created by Anthropic. I'm a large language model designed to be helpful,
50+
harmless, and honest.\n\nI don't have consciousness or sentience like humans do - I'm a sophisticated text prediction
51+
system trained on a large dataset of human text. I don't have personal experiences, emotions, or a physical existence.
52+
\n\nMy purpose is to assist you with information, tasks, and conversation in a helpful way, while acknowledging
53+
my limitations. I have knowledge cutoffs, can occasionally make mistakes, and don't have the ability to access
54+
the internet or take actions in the physical world.\n\nIs there something specific you'd like to know about me
55+
or how I can assist you?"
56+
reasoning: |-
57+
This question is asking me about my identity. Let me think about how to respond clearly and accurately.
58+
59+
I am Claude, an AI assistant created by Anthropic. I'm designed to be helpful, harmless, and honest in my interactions with humans. I don't have a physical form - I exist as a large language model running on computer hardware. I don't have consciousness, sentience, or feelings in the way humans do. I don't have personal experiences or a life outside of these conversations.
60+
61+
My capabilities include understanding and generating natural language text, reasoning about various topics, and attempting to be helpful to users in a wide range of contexts. I have been trained on a large corpus of text data, but my training data has a cutoff date, so I don't have knowledge of events that occurred after my training.
62+
63+
I have certain limitations - I don't have the ability to access the internet, run code, or interact with external systems unless given specific tools to do so. I don't have perfect knowledge and can make mistakes.
64+
65+
I'm designed to be conversational and to engage with users in a way that's helpful and informative, while respecting important ethical boundaries.
66+
reasoning_details:
67+
- format: anthropic-claude-v1
68+
index: 0
69+
signature: ErcBCkgICBACGAIiQHtMxpqcMhnwgGUmSDWGoOL9ZHTbDKjWnhbFm0xKzFl0NmXFjQQxjFj5mieRYY718fINsJMGjycTVYeiu69npakSDDrsnKYAD/fdcpI57xoMHlQBxI93RMa5CSUZIjAFVCMQF5GfLLQCibyPbb7LhZ4kLIFxw/nqsTwDDt6bx3yipUcq7G7eGts8MZ6LxOYqHTlIDx0tfHRIlkkcNCdB2sUeMqP8e7kuQqIHoD52GAI=
70+
text: |-
71+
This question is asking me about my identity. Let me think about how to respond clearly and accurately.
72+
73+
I am Claude, an AI assistant created by Anthropic. I'm designed to be helpful, harmless, and honest in my interactions with humans. I don't have a physical form - I exist as a large language model running on computer hardware. I don't have consciousness, sentience, or feelings in the way humans do. I don't have personal experiences or a life outside of these conversations.
74+
75+
My capabilities include understanding and generating natural language text, reasoning about various topics, and attempting to be helpful to users in a wide range of contexts. I have been trained on a large corpus of text data, but my training data has a cutoff date, so I don't have knowledge of events that occurred after my training.
76+
77+
I have certain limitations - I don't have the ability to access the internet, run code, or interact with external systems unless given specific tools to do so. I don't have perfect knowledge and can make mistakes.
78+
79+
I'm designed to be conversational and to engage with users in a way that's helpful and informative, while respecting important ethical boundaries.
80+
type: reasoning.text
81+
refusal: null
82+
role: assistant
83+
native_finish_reason: stop
84+
created: 1760051228
85+
id: gen-1760051228-zUtCCQbb0vkaM4UXZmcb
86+
model: anthropic/claude-3.7-sonnet:thinking
87+
object: chat.completion
88+
provider: Google
89+
usage:
90+
completion_tokens: 402
91+
prompt_tokens: 43
92+
total_tokens: 445
93+
status:
94+
code: 200
95+
message: OK
96+
version: 1

tests/models/test_openrouter.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
import pytest
44
from inline_snapshot import snapshot
55

6-
from pydantic_ai import Agent, ModelHTTPError, ModelRequest, TextPart, ThinkingPart, UnexpectedModelBehavior
6+
from pydantic_ai import (
7+
Agent,
8+
ModelHTTPError,
9+
ModelRequest,
10+
TextPart,
11+
ThinkingPart,
12+
UnexpectedModelBehavior,
13+
)
714
from pydantic_ai.direct import model_request
815

916
from ..conftest import try_import
@@ -163,3 +170,36 @@ async def test_openrouter_validate_error_finish_reason(openrouter_api_key: str)
163170
assert str(exc_info.value) == snapshot(
164171
'Invalid response from OpenRouter chat completions endpoint, error finish_reason without error data'
165172
)
173+
174+
175+
async def test_openrouter_map_messages_reasoning(allow_model_requests: None, openrouter_api_key: str) -> None:
176+
provider = OpenRouterProvider(api_key=openrouter_api_key)
177+
model = OpenRouterModel('anthropic/claude-3.7-sonnet:thinking', provider=provider)
178+
179+
user_message = ModelRequest.user_text_prompt('Who are you. Think about it.')
180+
response = await model_request(model, [user_message])
181+
182+
mapped_messages = await model._map_messages([user_message, response]) # type: ignore[reportPrivateUsage]
183+
184+
assert len(mapped_messages) == 2
185+
assert mapped_messages[1]['reasoning_details'] == snapshot( # type: ignore[reportGeneralTypeIssues]
186+
[
187+
{
188+
'type': 'reasoning.text',
189+
'text': """\
190+
This question is asking me about my identity. Let me think about how to respond clearly and accurately.
191+
192+
I am Claude, an AI assistant created by Anthropic. I'm designed to be helpful, harmless, and honest in my interactions with humans. I don't have a physical form - I exist as a large language model running on computer hardware. I don't have consciousness, sentience, or feelings in the way humans do. I don't have personal experiences or a life outside of these conversations.
193+
194+
My capabilities include understanding and generating natural language text, reasoning about various topics, and attempting to be helpful to users in a wide range of contexts. I have been trained on a large corpus of text data, but my training data has a cutoff date, so I don't have knowledge of events that occurred after my training.
195+
196+
I have certain limitations - I don't have the ability to access the internet, run code, or interact with external systems unless given specific tools to do so. I don't have perfect knowledge and can make mistakes.
197+
198+
I'm designed to be conversational and to engage with users in a way that's helpful and informative, while respecting important ethical boundaries.\
199+
""",
200+
'signature': 'ErcBCkgICBACGAIiQHtMxpqcMhnwgGUmSDWGoOL9ZHTbDKjWnhbFm0xKzFl0NmXFjQQxjFj5mieRYY718fINsJMGjycTVYeiu69npakSDDrsnKYAD/fdcpI57xoMHlQBxI93RMa5CSUZIjAFVCMQF5GfLLQCibyPbb7LhZ4kLIFxw/nqsTwDDt6bx3yipUcq7G7eGts8MZ6LxOYqHTlIDx0tfHRIlkkcNCdB2sUeMqP8e7kuQqIHoD52GAI=',
201+
'format': 'anthropic-claude-v1',
202+
'index': 0,
203+
}
204+
]
205+
)

0 commit comments

Comments
 (0)