Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions pydantic_ai_slim/pydantic_ai/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,13 +754,38 @@ class TextPart:
part_kind: Literal['text'] = 'text'
"""Part type identifier, this is available on all parts as a discriminator."""

# TODO(Marcelo): Is this better as a list or a set?
citations: list[Citation] = field(default_factory=list)
"""The citations of the response."""

def has_content(self) -> bool:
"""Return `True` if the text content is non-empty."""
return bool(self.content)

__repr__ = _utils.dataclasses_no_defaults_repr


@dataclass(repr=False)
class URLCitation:
"""A citation from a text response."""

url: str
"""The URL of the citation."""

title: str
"""The title of the citation."""

# TODO(Marcelo): Should we add a `vendor_details` field or something?

kind: Literal['url-citation'] = 'url-citation'
"""The type of the citation."""

__repr__ = _utils.dataclasses_no_defaults_repr


Citation = Annotated[URLCitation, pydantic.Discriminator('kind')]


@dataclass(repr=False)
class ThinkingPart:
"""A thinking response from a model."""
Expand Down
12 changes: 11 additions & 1 deletion pydantic_ai_slim/pydantic_ai/models/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime, timezone
from typing import Any, Literal, Union, cast, overload

from anthropic.types.beta import BetaCitationsWebSearchResultLocation
from typing_extensions import assert_never

from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool
Expand All @@ -33,6 +34,7 @@
ThinkingPart,
ToolCallPart,
ToolReturnPart,
URLCitation,
UserPromptPart,
)
from ..profiles import ModelProfileSpec
Expand Down Expand Up @@ -289,7 +291,15 @@ def _process_response(self, response: BetaMessage) -> ModelResponse:
items: list[ModelResponsePart] = []
for item in response.content:
if isinstance(item, BetaTextBlock):
items.append(TextPart(content=item.text))
if item.citations:
citations = [
URLCitation(url=citation.url, title=citation.title or citation.cited_text)
for citation in item.citations
if isinstance(citation, BetaCitationsWebSearchResultLocation)
]
else:
citations = []
items.append(TextPart(content=item.text, citations=citations))
elif isinstance(item, (BetaWebSearchToolResultBlock, BetaCodeExecutionToolResultBlock)):
items.append(
BuiltinToolReturnPart(
Expand Down
14 changes: 13 additions & 1 deletion pydantic_ai_slim/pydantic_ai/models/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
ThinkingPart,
ToolCallPart,
ToolReturnPart,
URLCitation,
UserPromptPart,
VideoUrl,
)
Expand Down Expand Up @@ -72,6 +73,7 @@
)
from openai.types.responses import ComputerToolParam, FileSearchToolParam, WebSearchToolParam
from openai.types.responses.response_input_param import FunctionCallOutput, Message
from openai.types.responses.response_output_text import AnnotationURLCitation
from openai.types.shared import ReasoningEffort
from openai.types.shared_params import Reasoning
except ImportError as _import_error:
Expand Down Expand Up @@ -480,6 +482,7 @@ def _process_response(self, response: chat.ChatCompletion | str) -> ModelRespons
}

if choice.message.content is not None:
# NOTE: Should we include the citations on each `TextPart` here?
items.extend(split_content_into_text_and_thinking(choice.message.content, self.profile.thinking_tags))
if choice.message.tool_calls is not None:
for c in choice.message.tool_calls:
Expand Down Expand Up @@ -796,7 +799,16 @@ def _process_response(self, response: responses.Response) -> ModelResponse:
elif item.type == 'message':
for content in item.content:
if content.type == 'output_text': # pragma: no branch
items.append(TextPart(content.text))
items.append(
TextPart(
content.text,
citations=[
URLCitation(url=annotation.url, title=annotation.title)
for annotation in content.annotations
if isinstance(annotation, AnnotationURLCitation)
],
)
)
elif item.type == 'function_call':
items.append(ToolCallPart(item.name, item.arguments, tool_call_id=item.call_id))
return ModelResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interactions:
uri: https://us.inference.heroku.com/available-models
response:
headers:
connection:
- keep-alive
content-length:
- '760'
content-security-policy:
Expand Down
84 changes: 73 additions & 11 deletions tests/models/cassettes/test_openai/test_openai_web_search_tool.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interactions:
connection:
- keep-alive
content-length:
- '218'
- '236'
content-type:
- application/json
host:
Expand All @@ -18,7 +18,7 @@ interactions:
messages:
- content: You are a helpful assistant.
role: system
- content: What day is today?
- content: What are the latest news in Iceland?
role: user
model: gpt-4o-search-preview
stream: false
Expand All @@ -34,13 +34,15 @@ interactions:
connection:
- keep-alive
content-length:
- '785'
- '6079'
content-type:
- application/json
openai-organization:
- pydantic-28gund
openai-processing-ms:
- '2051'
- '11243'
openai-project:
- proj_dKobscVY9YJxeEaDJen54e3d
openai-version:
- '2020-10-01'
strict-transport-security:
Expand All @@ -52,27 +54,87 @@ interactions:
- finish_reason: stop
index: 0
message:
annotations: []
content: 'May 14, 2025, 8:51:29 AM '
annotations:
- type: url_citation
url_citation:
end_index: 700
start_index: 565
title: Iceland volcano emits smoke and glowing lava in 12th eruption since 2021
url: https://www.reuters.com/business/environment/iceland-volcano-erupts-12th-time-since-2021-2025-07-16/?utm_source=openai
- type: url_citation
url_citation:
end_index: 1247
start_index: 1106
title: Iceland to launch negotiations on security, defence partnership with EU
url: https://www.reuters.com/world/iceland-launch-negotiations-security-defence-partnership-with-eu-2025-07-17/?utm_source=openai
- type: url_citation
url_citation:
end_index: 1674
start_index: 1605
title: News | Icelandic Meteorological office
url: https://en.vedur.is/about-imo/news?utm_source=openai
- type: url_citation
url_citation:
end_index: 2011
start_index: 1924
title: Latest news - Iceland Monitor
url: https://icelandmonitor.mbl.is/news/latest/?utm_source=openai
- type: url_citation
url_citation:
end_index: 2250
start_index: 2051
title: Iceland to launch negotiations on security, defence partnership with EU
url: https://www.reuters.com/world/iceland-launch-negotiations-security-defence-partnership-with-eu-2025-07-17/?utm_source=openai
- type: url_citation
url_citation:
end_index: 2410
start_index: 2253
title: Iceland volcano eruption forces evacuation of town and iconic geothermal spa
url: https://apnews.com/article/c72f44eb9ecb5f2d87e87fd53ed3b26d?utm_source=openai
- type: url_citation
url_citation:
end_index: 2607
start_index: 2413
title: Iceland volcano emits smoke and glowing lava in 12th eruption since 2021
url: https://www.reuters.com/business/environment/iceland-volcano-erupts-12th-time-since-2021-2025-07-16/?utm_source=openai
content: "Here are some of the latest news updates from Iceland:\n\n**Volcanic Activity**\n\nOn July 16, 2025, a
volcanic eruption occurred on the Reykjanes Peninsula in southwest Iceland. The eruption produced smoke and vibrant
lava flows from a fissure spanning 700 to 1,000 meters. Authorities evacuated the Blue Lagoon geothermal spa and
the nearby town of Grindavik as a precaution. The Icelandic Meteorological Office described the eruption as relatively
small, posing no immediate threat to infrastructure, and flights at Reykjavik's Keflavik airport remained unaffected.
([reuters.com](https://www.reuters.com/business/environment/iceland-volcano-erupts-12th-time-since-2021-2025-07-16/?utm_source=openai))\n\n**Political
Developments**\n\nIceland is set to begin negotiations on a security and defense partnership with the European
Union. Prime Minister Kristrún Frostadóttir announced the talks, aiming to establish cooperation in areas such
as infrastructure, civil protection, dual-use defense investment, and addressing hybrid and cyber threats. The
discussions are expected to conclude by the end of the year. ([reuters.com](https://www.reuters.com/world/iceland-launch-negotiations-security-defence-partnership-with-eu-2025-07-17/?utm_source=openai))\n\n**Environmental
Concerns**\n\nThe Icelandic Meteorological Office has issued warnings about air pollution resulting from the recent
volcanic eruption. Volcanic haze has spread widely, with gas pollution, mainly sulfur dioxide, reaching populated
areas such as Reykjanesbær. Vulnerable groups are advised to limit time outdoors if they experience discomfort.
([en.vedur.is](https://en.vedur.is/about-imo/news?utm_source=openai))\n\n**Cultural Events**\n\nThe town of Hafnarfjörður
recently celebrated the ninth annual \"Í hjarta Hafnarfjarðar\" (\"In the Heart of Hafnarfjörður\") festival.
The event featured various festivities and was noted for its positive impact on the community. ([icelandmonitor.mbl.is](https://icelandmonitor.mbl.is/news/latest/?utm_source=openai))\n\n\n##
Recent Developments in Iceland:\n- [Iceland to launch negotiations on security, defence partnership with EU](https://www.reuters.com/world/iceland-launch-negotiations-security-defence-partnership-with-eu-2025-07-17/?utm_source=openai)\n-
[Iceland volcano eruption forces evacuation of town and iconic geothermal spa](https://apnews.com/article/c72f44eb9ecb5f2d87e87fd53ed3b26d?utm_source=openai)\n-
[Iceland volcano emits smoke and glowing lava in 12th eruption since 2021](https://www.reuters.com/business/environment/iceland-volcano-erupts-12th-time-since-2021-2025-07-16/?utm_source=openai) "
refusal: null
role: assistant
created: 1747227087
id: chatcmpl-e05fbf7b-44c0-406d-8662-bc7a9a518747
created: 1756109356
id: chatcmpl-b2ed94c7-51cb-4fb3-bf4d-4138b0c2a0fc
model: gpt-4o-search-preview-2025-03-11
object: chat.completion
system_fingerprint: ''
usage:
completion_tokens: 17
completion_tokens: 603
completion_tokens_details:
accepted_prediction_tokens: 0
audio_tokens: 0
reasoning_tokens: 0
rejected_prediction_tokens: 0
prompt_tokens: 11
prompt_tokens: 14
prompt_tokens_details:
audio_tokens: 0
cached_tokens: 0
total_tokens: 28
total_tokens: 617
status:
code: 200
message: OK
Expand Down
Loading
Loading