langchain-core 1.0.0a4__py3-none-any.whl → 1.0.0a5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. langchain_core/_api/beta_decorator.py +6 -5
  2. langchain_core/_api/deprecation.py +11 -11
  3. langchain_core/callbacks/manager.py +2 -2
  4. langchain_core/callbacks/usage.py +2 -2
  5. langchain_core/document_loaders/langsmith.py +1 -1
  6. langchain_core/indexing/api.py +30 -30
  7. langchain_core/language_models/chat_models.py +1 -1
  8. langchain_core/language_models/fake_chat_models.py +5 -2
  9. langchain_core/load/serializable.py +1 -1
  10. langchain_core/messages/__init__.py +9 -15
  11. langchain_core/messages/ai.py +75 -9
  12. langchain_core/messages/base.py +75 -37
  13. langchain_core/messages/block_translators/__init__.py +11 -1
  14. langchain_core/messages/block_translators/anthropic.py +143 -128
  15. langchain_core/messages/block_translators/bedrock_converse.py +15 -1
  16. langchain_core/messages/block_translators/langchain_v0.py +180 -43
  17. langchain_core/messages/block_translators/openai.py +224 -42
  18. langchain_core/messages/chat.py +4 -1
  19. langchain_core/messages/content.py +56 -112
  20. langchain_core/messages/function.py +9 -5
  21. langchain_core/messages/human.py +6 -2
  22. langchain_core/messages/modifier.py +1 -0
  23. langchain_core/messages/system.py +9 -2
  24. langchain_core/messages/tool.py +31 -14
  25. langchain_core/messages/utils.py +89 -83
  26. langchain_core/outputs/chat_generation.py +10 -6
  27. langchain_core/prompt_values.py +6 -2
  28. langchain_core/prompts/chat.py +6 -3
  29. langchain_core/prompts/few_shot.py +4 -1
  30. langchain_core/runnables/base.py +4 -1
  31. langchain_core/runnables/graph_ascii.py +1 -1
  32. langchain_core/tools/base.py +1 -2
  33. langchain_core/tools/convert.py +1 -1
  34. langchain_core/utils/aiter.py +1 -1
  35. langchain_core/utils/function_calling.py +5 -6
  36. langchain_core/utils/iter.py +1 -1
  37. langchain_core/vectorstores/in_memory.py +5 -5
  38. langchain_core/version.py +1 -1
  39. {langchain_core-1.0.0a4.dist-info → langchain_core-1.0.0a5.dist-info}/METADATA +8 -8
  40. {langchain_core-1.0.0a4.dist-info → langchain_core-1.0.0a5.dist-info}/RECORD +42 -42
  41. {langchain_core-1.0.0a4.dist-info → langchain_core-1.0.0a5.dist-info}/WHEEL +0 -0
  42. {langchain_core-1.0.0a4.dist-info → langchain_core-1.0.0a5.dist-info}/entry_points.txt +0 -0
@@ -6,32 +6,36 @@ from langchain_core.messages import content as types
6
6
 
7
7
 
8
8
  def _convert_v0_multimodal_input_to_v1(
9
- blocks: list[types.ContentBlock],
9
+ content: list[types.ContentBlock],
10
10
  ) -> list[types.ContentBlock]:
11
11
  """Convert v0 multimodal blocks to v1 format.
12
12
 
13
- Processes ``'non_standard'`` blocks that might be v0 format and converts them
14
- to proper v1 ``ContentBlock``.
13
+ During the `.content_blocks` parsing process, we wrap blocks not recognized as a v1
14
+ block as a ``'non_standard'`` block with the original block stored in the ``value``
15
+ field. This function attempts to unpack those blocks and convert any v0 format
16
+ blocks to v1 format.
17
+
18
+ If conversion fails, the block is left as a ``'non_standard'`` block.
15
19
 
16
20
  Args:
17
- blocks: List of content blocks to process.
21
+ content: List of content blocks to process.
18
22
 
19
23
  Returns:
20
- Updated list with v0 blocks converted to v1 format.
21
-
24
+ v1 content blocks.
22
25
  """
23
26
  converted_blocks = []
24
27
  unpacked_blocks: list[dict[str, Any]] = [
25
28
  cast("dict[str, Any]", block)
26
29
  if block.get("type") != "non_standard"
27
30
  else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks
28
- for block in blocks
31
+ for block in content
29
32
  ]
30
33
  for block in unpacked_blocks:
31
34
  if block.get("type") in {"image", "audio", "file"} and "source_type" in block:
32
35
  converted_block = _convert_legacy_v0_content_block_to_v1(block)
33
36
  converted_blocks.append(cast("types.ContentBlock", converted_block))
34
37
  elif block.get("type") in types.KNOWN_BLOCK_TYPES:
38
+ # Guard in case this function is used outside of the .content_blocks flow
35
39
  converted_blocks.append(cast("types.ContentBlock", block))
36
40
  else:
37
41
  converted_blocks.append({"type": "non_standard", "value": block})
@@ -47,11 +51,18 @@ def _convert_legacy_v0_content_block_to_v1(
47
51
  Preserves unknown keys as extras to avoid data loss.
48
52
 
49
53
  Returns the original block unchanged if it's not in v0 format.
50
-
51
54
  """
52
55
 
53
56
  def _extract_v0_extras(block_dict: dict, known_keys: set[str]) -> dict[str, Any]:
54
- """Extract unknown keys from v0 block to preserve as extras."""
57
+ """Extract unknown keys from v0 block to preserve as extras.
58
+
59
+ Args:
60
+ block_dict: The original v0 block dictionary.
61
+ known_keys: Set of keys known to be part of the v0 format for this block.
62
+
63
+ Returns:
64
+ A dictionary of extra keys not part of the known v0 format.
65
+ """
55
66
  return {k: v for k, v in block_dict.items() if k not in known_keys}
56
67
 
57
68
  # Check if this is actually a v0 format block
@@ -63,7 +74,8 @@ def _convert_legacy_v0_content_block_to_v1(
63
74
  if block.get("type") == "image":
64
75
  source_type = block.get("source_type")
65
76
  if source_type == "url":
66
- known_keys = {"type", "source_type", "url", "mime_type"}
77
+ # image-url
78
+ known_keys = {"mime_type", "type", "source_type", "url"}
67
79
  extras = _extract_v0_extras(block, known_keys)
68
80
  if "id" in block:
69
81
  return types.create_image_block(
@@ -74,17 +86,21 @@ def _convert_legacy_v0_content_block_to_v1(
74
86
  )
75
87
 
76
88
  # Don't construct with an ID if not present in original block
77
- v1_block = types.ImageContentBlock(type="image", url=block["url"])
89
+ v1_image_url = types.ImageContentBlock(type="image", url=block["url"])
78
90
  if block.get("mime_type"):
79
- v1_block["mime_type"] = block["mime_type"]
91
+ v1_image_url["mime_type"] = block["mime_type"]
80
92
 
93
+ v1_image_url["extras"] = {}
81
94
  for key, value in extras.items():
82
95
  if value is not None:
83
- v1_block["extras"] = {}
84
- v1_block["extras"][key] = value
85
- return v1_block
96
+ v1_image_url["extras"][key] = value
97
+ if v1_image_url["extras"] == {}:
98
+ del v1_image_url["extras"]
99
+
100
+ return v1_image_url
86
101
  if source_type == "base64":
87
- known_keys = {"type", "source_type", "data", "mime_type"}
102
+ # image-base64
103
+ known_keys = {"mime_type", "type", "source_type", "data"}
88
104
  extras = _extract_v0_extras(block, known_keys)
89
105
  if "id" in block:
90
106
  return types.create_image_block(
@@ -94,71 +110,192 @@ def _convert_legacy_v0_content_block_to_v1(
94
110
  **extras,
95
111
  )
96
112
 
97
- v1_block = types.ImageContentBlock(type="image", base64=block["data"])
113
+ v1_image_base64 = types.ImageContentBlock(
114
+ type="image", base64=block["data"]
115
+ )
98
116
  if block.get("mime_type"):
99
- v1_block["mime_type"] = block["mime_type"]
117
+ v1_image_base64["mime_type"] = block["mime_type"]
100
118
 
119
+ v1_image_base64["extras"] = {}
101
120
  for key, value in extras.items():
102
121
  if value is not None:
103
- v1_block["extras"] = {}
104
- v1_block["extras"][key] = value
105
- return v1_block
122
+ v1_image_base64["extras"][key] = value
123
+ if v1_image_base64["extras"] == {}:
124
+ del v1_image_base64["extras"]
125
+
126
+ return v1_image_base64
106
127
  if source_type == "id":
128
+ # image-id
107
129
  known_keys = {"type", "source_type", "id"}
108
130
  extras = _extract_v0_extras(block, known_keys)
109
131
  # For id `source_type`, `id` is the file reference, not block ID
110
- v1_block = types.ImageContentBlock(type="image", file_id=block["id"])
132
+ v1_image_id = types.ImageContentBlock(type="image", file_id=block["id"])
111
133
 
134
+ v1_image_id["extras"] = {}
112
135
  for key, value in extras.items():
113
136
  if value is not None:
114
- v1_block["extras"] = {}
115
- v1_block["extras"][key] = value
137
+ v1_image_id["extras"][key] = value
138
+ if v1_image_id["extras"] == {}:
139
+ del v1_image_id["extras"]
116
140
 
117
- return v1_block
141
+ return v1_image_id
118
142
  elif block.get("type") == "audio":
119
143
  source_type = block.get("source_type")
120
144
  if source_type == "url":
121
- known_keys = {"type", "source_type", "url", "mime_type"}
145
+ # audio-url
146
+ known_keys = {"mime_type", "type", "source_type", "url"}
122
147
  extras = _extract_v0_extras(block, known_keys)
123
- return types.create_audio_block(
124
- url=block["url"], mime_type=block.get("mime_type"), **extras
148
+ if "id" in block:
149
+ return types.create_audio_block(
150
+ url=block["url"],
151
+ mime_type=block.get("mime_type"),
152
+ id=block["id"],
153
+ **extras,
154
+ )
155
+
156
+ # Don't construct with an ID if not present in original block
157
+ v1_audio_url: types.AudioContentBlock = types.AudioContentBlock(
158
+ type="audio", url=block["url"]
125
159
  )
160
+ if block.get("mime_type"):
161
+ v1_audio_url["mime_type"] = block["mime_type"]
162
+
163
+ v1_audio_url["extras"] = {}
164
+ for key, value in extras.items():
165
+ if value is not None:
166
+ v1_audio_url["extras"][key] = value
167
+ if v1_audio_url["extras"] == {}:
168
+ del v1_audio_url["extras"]
169
+
170
+ return v1_audio_url
126
171
  if source_type == "base64":
127
- known_keys = {"type", "source_type", "data", "mime_type"}
172
+ # audio-base64
173
+ known_keys = {"mime_type", "type", "source_type", "data"}
128
174
  extras = _extract_v0_extras(block, known_keys)
129
- return types.create_audio_block(
130
- base64=block["data"], mime_type=block.get("mime_type"), **extras
175
+ if "id" in block:
176
+ return types.create_audio_block(
177
+ base64=block["data"],
178
+ mime_type=block.get("mime_type"),
179
+ id=block["id"],
180
+ **extras,
181
+ )
182
+
183
+ v1_audio_base64: types.AudioContentBlock = types.AudioContentBlock(
184
+ type="audio", base64=block["data"]
131
185
  )
186
+ if block.get("mime_type"):
187
+ v1_audio_base64["mime_type"] = block["mime_type"]
188
+
189
+ v1_audio_base64["extras"] = {}
190
+ for key, value in extras.items():
191
+ if value is not None:
192
+ v1_audio_base64["extras"][key] = value
193
+ if v1_audio_base64["extras"] == {}:
194
+ del v1_audio_base64["extras"]
195
+
196
+ return v1_audio_base64
132
197
  if source_type == "id":
198
+ # audio-id
133
199
  known_keys = {"type", "source_type", "id"}
134
200
  extras = _extract_v0_extras(block, known_keys)
135
- return types.create_audio_block(file_id=block["id"], **extras)
201
+ v1_audio_id: types.AudioContentBlock = types.AudioContentBlock(
202
+ type="audio", file_id=block["id"]
203
+ )
204
+
205
+ v1_audio_id["extras"] = {}
206
+ for key, value in extras.items():
207
+ if value is not None:
208
+ v1_audio_id["extras"][key] = value
209
+ if v1_audio_id["extras"] == {}:
210
+ del v1_audio_id["extras"]
211
+
212
+ return v1_audio_id
136
213
  elif block.get("type") == "file":
137
214
  source_type = block.get("source_type")
138
215
  if source_type == "url":
139
- known_keys = {"type", "source_type", "url", "mime_type"}
216
+ # file-url
217
+ known_keys = {"mime_type", "type", "source_type", "url"}
140
218
  extras = _extract_v0_extras(block, known_keys)
141
- return types.create_file_block(
142
- url=block["url"], mime_type=block.get("mime_type"), **extras
219
+ if "id" in block:
220
+ return types.create_file_block(
221
+ url=block["url"],
222
+ mime_type=block.get("mime_type"),
223
+ id=block["id"],
224
+ **extras,
225
+ )
226
+
227
+ v1_file_url: types.FileContentBlock = types.FileContentBlock(
228
+ type="file", url=block["url"]
143
229
  )
230
+ if block.get("mime_type"):
231
+ v1_file_url["mime_type"] = block["mime_type"]
232
+
233
+ v1_file_url["extras"] = {}
234
+ for key, value in extras.items():
235
+ if value is not None:
236
+ v1_file_url["extras"][key] = value
237
+ if v1_file_url["extras"] == {}:
238
+ del v1_file_url["extras"]
239
+
240
+ return v1_file_url
144
241
  if source_type == "base64":
145
- known_keys = {"type", "source_type", "data", "mime_type"}
242
+ # file-base64
243
+ known_keys = {"mime_type", "type", "source_type", "data"}
146
244
  extras = _extract_v0_extras(block, known_keys)
147
- return types.create_file_block(
148
- base64=block["data"], mime_type=block.get("mime_type"), **extras
245
+ if "id" in block:
246
+ return types.create_file_block(
247
+ base64=block["data"],
248
+ mime_type=block.get("mime_type"),
249
+ id=block["id"],
250
+ **extras,
251
+ )
252
+
253
+ v1_file_base64: types.FileContentBlock = types.FileContentBlock(
254
+ type="file", base64=block["data"]
149
255
  )
256
+ if block.get("mime_type"):
257
+ v1_file_base64["mime_type"] = block["mime_type"]
258
+
259
+ v1_file_base64["extras"] = {}
260
+ for key, value in extras.items():
261
+ if value is not None:
262
+ v1_file_base64["extras"][key] = value
263
+ if v1_file_base64["extras"] == {}:
264
+ del v1_file_base64["extras"]
265
+
266
+ return v1_file_base64
150
267
  if source_type == "id":
268
+ # file-id
151
269
  known_keys = {"type", "source_type", "id"}
152
270
  extras = _extract_v0_extras(block, known_keys)
153
271
  return types.create_file_block(file_id=block["id"], **extras)
154
272
  if source_type == "text":
155
- known_keys = {"type", "source_type", "url", "mime_type"}
273
+ # file-text
274
+ known_keys = {"mime_type", "type", "source_type", "url"}
156
275
  extras = _extract_v0_extras(block, known_keys)
157
- return types.create_plaintext_block(
158
- # In v0, URL points to the text file content
159
- text=block["url"],
160
- **extras,
276
+ if "id" in block:
277
+ return types.create_plaintext_block(
278
+ # In v0, URL points to the text file content
279
+ # TODO: attribute this claim
280
+ text=block["url"],
281
+ id=block["id"],
282
+ **extras,
283
+ )
284
+
285
+ v1_file_text: types.PlainTextContentBlock = types.PlainTextContentBlock(
286
+ type="text-plain", text=block["url"], mime_type="text/plain"
161
287
  )
288
+ if block.get("mime_type"):
289
+ v1_file_text["mime_type"] = block["mime_type"]
290
+
291
+ v1_file_text["extras"] = {}
292
+ for key, value in extras.items():
293
+ if value is not None:
294
+ v1_file_text["extras"][key] = value
295
+ if v1_file_text["extras"] == {}:
296
+ del v1_file_text["extras"]
297
+
298
+ return v1_file_text
162
299
 
163
300
  # If we can't convert, return the block unchanged
164
301
  return block
@@ -151,15 +151,19 @@ def _convert_to_v1_from_chat_completions(
151
151
 
152
152
 
153
153
  def _convert_to_v1_from_chat_completions_input(
154
- blocks: list[types.ContentBlock],
154
+ content: list[types.ContentBlock],
155
155
  ) -> list[types.ContentBlock]:
156
156
  """Convert OpenAI Chat Completions format blocks to v1 format.
157
157
 
158
- Processes non_standard blocks that might be OpenAI format and converts them
159
- to proper ContentBlocks. If conversion fails, leaves them as non_standard.
158
+ During the `.content_blocks` parsing process, we wrap blocks not recognized as a v1
159
+ block as a ``'non_standard'`` block with the original block stored in the ``value``
160
+ field. This function attempts to unpack those blocks and convert any blocks that
161
+ might be OpenAI format to v1 ContentBlocks.
162
+
163
+ If conversion fails, the block is left as a ``'non_standard'`` block.
160
164
 
161
165
  Args:
162
- blocks: List of content blocks to process.
166
+ content: List of content blocks to process.
163
167
 
164
168
  Returns:
165
169
  Updated list with OpenAI blocks converted to v1 format.
@@ -171,7 +175,7 @@ def _convert_to_v1_from_chat_completions_input(
171
175
  cast("dict[str, Any]", block)
172
176
  if block.get("type") != "non_standard"
173
177
  else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks
174
- for block in blocks
178
+ for block in content
175
179
  ]
176
180
  for block in unpacked_blocks:
177
181
  if block.get("type") in {
@@ -713,21 +717,27 @@ def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock
713
717
  yield tool_call_block
714
718
 
715
719
  elif block_type == "web_search_call":
716
- web_search_call = {"type": "web_search_call", "id": block["id"]}
720
+ web_search_call = {
721
+ "type": "server_tool_call",
722
+ "name": "web_search",
723
+ "args": {},
724
+ "id": block["id"],
725
+ }
717
726
  if "index" in block:
718
727
  web_search_call["index"] = f"lc_wsc_{block['index']}"
719
- if (
720
- "action" in block
721
- and isinstance(block["action"], dict)
722
- and block["action"].get("type") == "search"
723
- and "query" in block["action"]
724
- ):
725
- web_search_call["query"] = block["action"]["query"]
728
+
729
+ sources: Optional[dict[str, Any]] = None
730
+ if "action" in block and isinstance(block["action"], dict):
731
+ if "sources" in block["action"]:
732
+ sources = block["action"]["sources"]
733
+ web_search_call["args"] = {
734
+ k: v for k, v in block["action"].items() if k != "sources"
735
+ }
726
736
  for key in block:
727
- if key not in ("type", "id", "index"):
737
+ if key not in ("type", "id", "action", "status", "index"):
728
738
  web_search_call[key] = block[key]
729
739
 
730
- yield cast("types.WebSearchCall", web_search_call)
740
+ yield cast("types.ServerToolCall", web_search_call)
731
741
 
732
742
  # If .content already has web_search_result, don't add
733
743
  if not any(
@@ -736,21 +746,88 @@ def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock
736
746
  and other_block.get("id") == block["id"]
737
747
  for other_block in message.content
738
748
  ):
739
- web_search_result = {"type": "web_search_result", "id": block["id"]}
749
+ web_search_result = {
750
+ "type": "server_tool_result",
751
+ "tool_call_id": block["id"],
752
+ }
753
+ if sources:
754
+ web_search_result["output"] = {"sources": sources}
755
+
756
+ status = block.get("status")
757
+ if status == "failed":
758
+ web_search_result["status"] = "error"
759
+ elif status == "completed":
760
+ web_search_result["status"] = "success"
761
+ elif status:
762
+ web_search_result["extras"] = {"status": status}
763
+ else:
764
+ pass
740
765
  if "index" in block and isinstance(block["index"], int):
741
766
  web_search_result["index"] = f"lc_wsr_{block['index'] + 1}"
742
- yield cast("types.WebSearchResult", web_search_result)
767
+ yield cast("types.ServerToolResult", web_search_result)
768
+
769
+ elif block_type == "file_search_call":
770
+ file_search_call = {
771
+ "type": "server_tool_call",
772
+ "name": "file_search",
773
+ "id": block["id"],
774
+ "args": {"queries": block.get("queries", [])},
775
+ }
776
+ if "index" in block:
777
+ file_search_call["index"] = f"lc_fsc_{block['index']}"
778
+
779
+ for key in block:
780
+ if key not in (
781
+ "type",
782
+ "id",
783
+ "queries",
784
+ "results",
785
+ "status",
786
+ "index",
787
+ ):
788
+ file_search_call[key] = block[key]
789
+
790
+ yield cast("types.ServerToolCall", file_search_call)
791
+
792
+ file_search_result = {
793
+ "type": "server_tool_result",
794
+ "tool_call_id": block["id"],
795
+ }
796
+ if file_search_output := block.get("results"):
797
+ file_search_result["output"] = file_search_output
798
+
799
+ status = block.get("status")
800
+ if status == "failed":
801
+ file_search_result["status"] = "error"
802
+ elif status == "completed":
803
+ file_search_result["status"] = "success"
804
+ elif status:
805
+ file_search_result["extras"] = {"status": status}
806
+ else:
807
+ pass
808
+ if "index" in block and isinstance(block["index"], int):
809
+ file_search_result["index"] = f"lc_fsr_{block['index'] + 1}"
810
+ yield cast("types.ServerToolResult", file_search_result)
743
811
 
744
812
  elif block_type == "code_interpreter_call":
745
813
  code_interpreter_call = {
746
- "type": "code_interpreter_call",
814
+ "type": "server_tool_call",
815
+ "name": "code_interpreter",
747
816
  "id": block["id"],
748
817
  }
749
818
  if "code" in block:
750
- code_interpreter_call["code"] = block["code"]
819
+ code_interpreter_call["args"] = {"code": block["code"]}
751
820
  if "index" in block:
752
821
  code_interpreter_call["index"] = f"lc_cic_{block['index']}"
753
- known_fields = {"type", "id", "language", "code", "extras", "index"}
822
+ known_fields = {
823
+ "type",
824
+ "id",
825
+ "outputs",
826
+ "status",
827
+ "code",
828
+ "extras",
829
+ "index",
830
+ }
754
831
  for key in block:
755
832
  if key not in known_fields:
756
833
  if "extras" not in code_interpreter_call:
@@ -758,33 +835,138 @@ def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock
758
835
  code_interpreter_call["extras"][key] = block[key]
759
836
 
760
837
  code_interpreter_result = {
761
- "type": "code_interpreter_result",
762
- "id": block["id"],
838
+ "type": "server_tool_result",
839
+ "tool_call_id": block["id"],
763
840
  }
764
841
  if "outputs" in block:
765
- code_interpreter_result["outputs"] = block["outputs"]
766
- for output in block["outputs"]:
767
- if (
768
- isinstance(output, dict)
769
- and (output_type := output.get("type"))
770
- and output_type == "logs"
771
- ):
772
- if "output" not in code_interpreter_result:
773
- code_interpreter_result["output"] = []
774
- code_interpreter_result["output"].append(
775
- {
776
- "type": "code_interpreter_output",
777
- "stdout": output.get("logs", ""),
778
- }
779
- )
780
-
781
- if "status" in block:
782
- code_interpreter_result["status"] = block["status"]
842
+ code_interpreter_result["output"] = block["outputs"]
843
+
844
+ status = block.get("status")
845
+ if status == "failed":
846
+ code_interpreter_result["status"] = "error"
847
+ elif status == "completed":
848
+ code_interpreter_result["status"] = "success"
849
+ elif status:
850
+ code_interpreter_result["extras"] = {"status": status}
851
+ else:
852
+ pass
783
853
  if "index" in block and isinstance(block["index"], int):
784
854
  code_interpreter_result["index"] = f"lc_cir_{block['index'] + 1}"
785
855
 
786
- yield cast("types.CodeInterpreterCall", code_interpreter_call)
787
- yield cast("types.CodeInterpreterResult", code_interpreter_result)
856
+ yield cast("types.ServerToolCall", code_interpreter_call)
857
+ yield cast("types.ServerToolResult", code_interpreter_result)
858
+
859
+ elif block_type == "mcp_call":
860
+ mcp_call = {
861
+ "type": "server_tool_call",
862
+ "name": "remote_mcp",
863
+ "id": block["id"],
864
+ }
865
+ if (arguments := block.get("arguments")) and isinstance(arguments, str):
866
+ try:
867
+ mcp_call["args"] = json.loads(block["arguments"])
868
+ except json.JSONDecodeError:
869
+ mcp_call["extras"] = {"arguments": arguments}
870
+ if "name" in block:
871
+ if "extras" not in mcp_call:
872
+ mcp_call["extras"] = {}
873
+ mcp_call["extras"]["tool_name"] = block["name"]
874
+ if "server_label" in block:
875
+ if "extras" not in mcp_call:
876
+ mcp_call["extras"] = {}
877
+ mcp_call["extras"]["server_label"] = block["server_label"]
878
+ if "index" in block:
879
+ mcp_call["index"] = f"lc_mcp_{block['index']}"
880
+ known_fields = {
881
+ "type",
882
+ "id",
883
+ "arguments",
884
+ "name",
885
+ "server_label",
886
+ "output",
887
+ "error",
888
+ "extras",
889
+ "index",
890
+ }
891
+ for key in block:
892
+ if key not in known_fields:
893
+ if "extras" not in mcp_call:
894
+ mcp_call["extras"] = {}
895
+ mcp_call["extras"][key] = block[key]
896
+
897
+ yield cast("types.ServerToolCall", mcp_call)
898
+
899
+ mcp_result = {
900
+ "type": "server_tool_result",
901
+ "tool_call_id": block["id"],
902
+ }
903
+ if mcp_output := block.get("output"):
904
+ mcp_result["output"] = mcp_output
905
+
906
+ error = block.get("error")
907
+ if error:
908
+ if "extras" not in mcp_result:
909
+ mcp_result["extras"] = {}
910
+ mcp_result["extras"]["error"] = error
911
+ mcp_result["status"] = "error"
912
+ else:
913
+ mcp_result["status"] = "success"
914
+
915
+ if "index" in block and isinstance(block["index"], int):
916
+ mcp_result["index"] = f"lc_mcpr_{block['index'] + 1}"
917
+ yield cast("types.ServerToolResult", mcp_result)
918
+
919
+ elif block_type == "mcp_list_tools":
920
+ mcp_list_tools_call = {
921
+ "type": "server_tool_call",
922
+ "name": "mcp_list_tools",
923
+ "args": {},
924
+ "id": block["id"],
925
+ }
926
+ if "server_label" in block:
927
+ mcp_list_tools_call["extras"] = {}
928
+ mcp_list_tools_call["extras"]["server_label"] = block[
929
+ "server_label"
930
+ ]
931
+ if "index" in block:
932
+ mcp_list_tools_call["index"] = f"lc_mlt_{block['index']}"
933
+ known_fields = {
934
+ "type",
935
+ "id",
936
+ "name",
937
+ "server_label",
938
+ "tools",
939
+ "error",
940
+ "extras",
941
+ "index",
942
+ }
943
+ for key in block:
944
+ if key not in known_fields:
945
+ if "extras" not in mcp_list_tools_call:
946
+ mcp_list_tools_call["extras"] = {}
947
+ mcp_list_tools_call["extras"][key] = block[key]
948
+
949
+ yield cast("types.ServerToolCall", mcp_list_tools_call)
950
+
951
+ mcp_list_tools_result = {
952
+ "type": "server_tool_result",
953
+ "tool_call_id": block["id"],
954
+ }
955
+ if mcp_output := block.get("tools"):
956
+ mcp_list_tools_result["output"] = mcp_output
957
+
958
+ error = block.get("error")
959
+ if error:
960
+ if "extras" not in mcp_list_tools_result:
961
+ mcp_list_tools_result["extras"] = {}
962
+ mcp_list_tools_result["extras"]["error"] = error
963
+ mcp_list_tools_result["status"] = "error"
964
+ else:
965
+ mcp_list_tools_result["status"] = "success"
966
+
967
+ if "index" in block and isinstance(block["index"], int):
968
+ mcp_list_tools_result["index"] = f"lc_mltr_{block['index'] + 1}"
969
+ yield cast("types.ServerToolResult", mcp_list_tools_result)
788
970
 
789
971
  elif block_type in types.KNOWN_BLOCK_TYPES:
790
972
  yield cast("types.ContentBlock", block)
@@ -30,7 +30,10 @@ class ChatMessageChunk(ChatMessage, BaseMessageChunk):
30
30
  # non-chunk variant.
31
31
  type: Literal["ChatMessageChunk"] = "ChatMessageChunk" # type: ignore[assignment]
32
32
  """The type of the message (used during serialization).
33
- Defaults to "ChatMessageChunk"."""
33
+
34
+ Defaults to ``'ChatMessageChunk'``.
35
+
36
+ """
34
37
 
35
38
  @override
36
39
  def __add__(self, other: Any) -> BaseMessageChunk: # type: ignore[override]