Skip to content

Commit a2e3322

Browse files
authored
Merge branch 'master' into ruff-core-preview-3
2 parents 84f7371 + f6297ce commit a2e3322

File tree

57 files changed

+1886
-604
lines changed

Some content is hidden

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

57 files changed

+1886
-604
lines changed

.github/workflows/_lint.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ jobs:
4747
cache-suffix: lint-${{ inputs.working-directory }}
4848
working-directory: ${{ inputs.working-directory }}
4949

50-
- name: "🔒 Verify Lockfile is Up-to-Date"
51-
working-directory: ${{ inputs.working-directory }}
52-
run: |
53-
unset UV_FROZEN
54-
uv lock --check
50+
# - name: "🔒 Verify Lockfile is Up-to-Date"
51+
# working-directory: ${{ inputs.working-directory }}
52+
# run: |
53+
# unset UV_FROZEN
54+
# uv lock --check
5555

5656
- name: "📦 Install Lint & Typing Dependencies"
5757
working-directory: ${{ inputs.working-directory }}

libs/core/langchain_core/messages/block_translators/anthropic.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,26 @@ def _iter_blocks() -> Iterable[types.ContentBlock]:
461461

462462

463463
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
464-
"""Derive standard content blocks from a message with Anthropic content."""
464+
"""Derive standard content blocks from a message with Anthropic content.
465+
466+
Args:
467+
message: The message to translate.
468+
469+
Returns:
470+
The derived content blocks.
471+
"""
465472
return _convert_to_v1_from_anthropic(message)
466473

467474

468475
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
469-
"""Derive standard content blocks from a message chunk with Anthropic content."""
476+
"""Derive standard content blocks from a message chunk with Anthropic content.
477+
478+
Args:
479+
message: The message chunk to translate.
480+
481+
Returns:
482+
The derived content blocks.
483+
"""
470484
return _convert_to_v1_from_anthropic(message)
471485

472486

libs/core/langchain_core/messages/block_translators/bedrock.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,28 @@ def _convert_to_v1_from_bedrock_chunk(
6565

6666

6767
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
68-
"""Derive standard content blocks from a message with Bedrock content."""
68+
"""Derive standard content blocks from a message with Bedrock content.
69+
70+
Args:
71+
message: The message to translate.
72+
73+
Returns:
74+
The derived content blocks.
75+
"""
6976
if "claude" not in message.response_metadata.get("model_name", "").lower():
7077
raise NotImplementedError # fall back to best-effort parsing
7178
return _convert_to_v1_from_bedrock(message)
7279

7380

7481
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
75-
"""Derive standard content blocks from a message chunk with Bedrock content."""
82+
"""Derive standard content blocks from a message chunk with Bedrock content.
83+
84+
Args:
85+
message: The message chunk to translate.
86+
87+
Returns:
88+
The derived content blocks.
89+
"""
7690
# TODO: add model_name to all Bedrock chunks and update core merging logic
7791
# to not append during aggregation. Then raise NotImplementedError here if
7892
# not an Anthropic model to fall back to best-effort parsing.

libs/core/langchain_core/messages/block_translators/bedrock_converse.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,26 @@ def _iter_blocks() -> Iterable[types.ContentBlock]:
281281

282282

283283
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
284-
"""Derive standard content blocks from a message with Bedrock Converse content."""
284+
"""Derive standard content blocks from a message with Bedrock Converse content.
285+
286+
Args:
287+
message: The message to translate.
288+
289+
Returns:
290+
The derived content blocks.
291+
"""
285292
return _convert_to_v1_from_converse(message)
286293

287294

288295
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
289-
"""Derive standard content blocks from a chunk with Bedrock Converse content."""
296+
"""Derive standard content blocks from a chunk with Bedrock Converse content.
297+
298+
Args:
299+
message: The message chunk to translate.
300+
301+
Returns:
302+
The derived content blocks.
303+
"""
290304
return _convert_to_v1_from_converse(message)
291305

292306

libs/core/langchain_core/messages/block_translators/google_genai.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,12 +526,26 @@ def _convert_to_v1_from_genai(message: AIMessage) -> list[types.ContentBlock]:
526526

527527

528528
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
529-
"""Derive standard content blocks from a message with Google (GenAI) content."""
529+
"""Derive standard content blocks from a message with Google (GenAI) content.
530+
531+
Args:
532+
message: The message to translate.
533+
534+
Returns:
535+
The derived content blocks.
536+
"""
530537
return _convert_to_v1_from_genai(message)
531538

532539

533540
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
534-
"""Derive standard content blocks from a chunk with Google (GenAI) content."""
541+
"""Derive standard content blocks from a chunk with Google (GenAI) content.
542+
543+
Args:
544+
message: The message chunk to translate.
545+
546+
Returns:
547+
The derived content blocks.
548+
"""
535549
return _convert_to_v1_from_genai(message)
536550

537551

libs/core/langchain_core/messages/block_translators/groq.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,26 @@ def _convert_to_v1_from_groq(message: AIMessage) -> list[types.ContentBlock]:
119119

120120

121121
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
122-
"""Derive standard content blocks from a message with groq content."""
122+
"""Derive standard content blocks from a message with groq content.
123+
124+
Args:
125+
message: The message to translate.
126+
127+
Returns:
128+
The derived content blocks.
129+
"""
123130
return _convert_to_v1_from_groq(message)
124131

125132

126133
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
127-
"""Derive standard content blocks from a message chunk with groq content."""
134+
"""Derive standard content blocks from a message chunk with groq content.
135+
136+
Args:
137+
message: The message chunk to translate.
138+
139+
Returns:
140+
The derived content blocks.
141+
"""
128142
return _convert_to_v1_from_groq(message)
129143

130144

libs/core/langchain_core/messages/block_translators/openai.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@
1919

2020

2121
def convert_to_openai_image_block(block: dict[str, Any]) -> dict:
22-
"""Convert `ImageContentBlock` to format expected by OpenAI Chat Completions."""
22+
"""Convert `ImageContentBlock` to format expected by OpenAI Chat Completions.
23+
24+
Args:
25+
block: The image content block to convert.
26+
27+
Raises:
28+
ValueError: If required keys are missing.
29+
ValueError: If source type is unsupported.
30+
31+
Returns:
32+
The formatted image content block.
33+
"""
2334
if "url" in block:
2435
return {
2536
"type": "image_url",
@@ -50,6 +61,18 @@ def convert_to_openai_data_block(
5061
5162
"Standard data content block" can include old-style LangChain v0 blocks
5263
(URLContentBlock, Base64ContentBlock, IDContentBlock) or new ones.
64+
65+
Args:
66+
block: The content block to convert.
67+
api: The OpenAI API being targeted. Either "chat/completions" or "responses".
68+
69+
Raises:
70+
ValueError: If required keys are missing.
71+
ValueError: If file URLs are used with Chat Completions API.
72+
ValueError: If block type is unsupported.
73+
74+
Returns:
75+
The formatted content block.
5376
"""
5477
if block["type"] == "image":
5578
chat_completions_block = convert_to_openai_image_block(block)
@@ -973,15 +996,29 @@ def _iter_blocks() -> Iterable[types.ContentBlock]:
973996

974997

975998
def translate_content(message: AIMessage) -> list[types.ContentBlock]:
976-
"""Derive standard content blocks from a message with OpenAI content."""
999+
"""Derive standard content blocks from a message with OpenAI content.
1000+
1001+
Args:
1002+
message: The message to translate.
1003+
1004+
Returns:
1005+
The derived content blocks.
1006+
"""
9771007
if isinstance(message.content, str):
9781008
return _convert_to_v1_from_chat_completions(message)
9791009
message = _convert_from_v03_ai_message(message)
9801010
return _convert_to_v1_from_responses(message)
9811011

9821012

9831013
def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
984-
"""Derive standard content blocks from a message chunk with OpenAI content."""
1014+
"""Derive standard content blocks from a message chunk with OpenAI content.
1015+
1016+
Args:
1017+
message: The message chunk to translate.
1018+
1019+
Returns:
1020+
The derived content blocks.
1021+
"""
9851022
if isinstance(message.content, str):
9861023
return _convert_to_v1_from_chat_completions_chunk(message)
9871024
message = _convert_from_v03_ai_message(message) # type: ignore[assignment]

libs/core/langchain_core/messages/utils.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ def convert_to_openai_messages(
10491049
*,
10501050
text_format: Literal["string", "block"] = "string",
10511051
include_id: bool = False,
1052+
pass_through_unknown_blocks: bool = True,
10521053
) -> dict | list[dict]:
10531054
"""Convert LangChain messages into OpenAI message dicts.
10541055
@@ -1068,6 +1069,9 @@ def convert_to_openai_messages(
10681069
content blocks these are left as is.
10691070
include_id: Whether to include message IDs in the openai messages, if they
10701071
are present in the source messages.
1072+
pass_through_unknown_blocks: Whether to include content blocks with unknown
1073+
formats in the output. If `False`, an error is raised if an unknown
1074+
content block is encountered.
10711075
10721076
Raises:
10731077
ValueError: if an unrecognized `text_format` is specified, or if a message
@@ -1317,6 +1321,36 @@ def convert_to_openai_messages(
13171321
},
13181322
}
13191323
)
1324+
elif block.get("type") == "function_call": # OpenAI Responses
1325+
if not any(
1326+
tool_call["id"] == block.get("call_id")
1327+
for tool_call in cast("AIMessage", message).tool_calls
1328+
):
1329+
if missing := [
1330+
k
1331+
for k in ("call_id", "name", "arguments")
1332+
if k not in block
1333+
]:
1334+
err = (
1335+
f"Unrecognized content block at "
1336+
f"messages[{i}].content[{j}] has 'type': "
1337+
f"'tool_use', but is missing expected key(s) "
1338+
f"{missing}. Full content block:\n\n{block}"
1339+
)
1340+
raise ValueError(err)
1341+
oai_msg["tool_calls"] = oai_msg.get("tool_calls", [])
1342+
oai_msg["tool_calls"].append(
1343+
{
1344+
"type": "function",
1345+
"id": block.get("call_id"),
1346+
"function": {
1347+
"name": block.get("name"),
1348+
"arguments": block.get("arguments"),
1349+
},
1350+
}
1351+
)
1352+
if pass_through_unknown_blocks:
1353+
content.append(block)
13201354
elif block.get("type") == "tool_result":
13211355
if missing := [
13221356
k for k in ("content", "tool_use_id") if k not in block
@@ -1397,7 +1431,10 @@ def convert_to_openai_messages(
13971431
},
13981432
}
13991433
)
1400-
elif block.get("type") in {"thinking", "reasoning"}:
1434+
elif (
1435+
block.get("type") in {"thinking", "reasoning"}
1436+
or pass_through_unknown_blocks
1437+
):
14011438
content.append(block)
14021439
else:
14031440
err = (

libs/core/langchain_core/output_parsers/openai_functions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def parse_result(self, result: list[Generation], *, partial: bool = False) -> An
3737
The parsed JSON object.
3838
3939
Raises:
40-
`OutputParserException`: If the output is not valid JSON.
40+
OutputParserException: If the output is not valid JSON.
4141
"""
4242
generation = result[0]
4343
if not isinstance(generation, ChatGeneration):
@@ -88,7 +88,7 @@ def parse_result(self, result: list[Generation], *, partial: bool = False) -> An
8888
The parsed JSON object.
8989
9090
Raises:
91-
OutputParserExcept`ion: If the output is not valid JSON.
91+
OutputParserException: If the output is not valid JSON.
9292
"""
9393
if len(result) != 1:
9494
msg = f"Expected exactly one result, but got {len(result)}"

libs/core/langchain_core/output_parsers/pydantic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def parse_result(
5454
all the keys that have been returned so far.
5555
5656
Raises:
57-
`OutputParserException`: If the result is not valid JSON
57+
OutputParserException: If the result is not valid JSON
5858
or does not conform to the Pydantic model.
5959
6060
Returns:

0 commit comments

Comments
 (0)