Ensure functions have docstrings for documentation
def convert_to_openai_data_block(
1"""Derivations of standard content blocks from OpenAI content."""23from __future__ import annotations45import json6import warnings7from typing import TYPE_CHECKING, Any, Literal, cast89from langchain_core.language_models._utils import (10 _parse_data_uri,11 is_openai_data_block,12)13from langchain_core.messages import AIMessageChunk14from langchain_core.messages import content as types1516if TYPE_CHECKING:17 from collections.abc import Iterator1819 from langchain_core.messages import AIMessage202122def convert_to_openai_image_block(block: dict[str, Any]) -> dict[str, Any]:23 """Convert `ImageContentBlock` to format expected by OpenAI Chat Completions.2425 Args:26 block: The image content block to convert.2728 Raises:29 ValueError: If required keys are missing.30 ValueError: If source type is unsupported.3132 Returns:33 The formatted image content block.34 """35 if "url" in block:36 return {37 "type": "image_url",38 "image_url": {39 "url": block["url"],40 },41 }42 if "base64" in block or block.get("source_type") == "base64":43 if "mime_type" not in block:44 error_message = "mime_type key is required for base64 data."45 raise ValueError(error_message)46 mime_type = block["mime_type"]47 base64_data = block["data"] if "data" in block else block["base64"]48 return {49 "type": "image_url",50 "image_url": {51 "url": f"data:{mime_type};base64,{base64_data}",52 },53 }54 error_message = "Unsupported source type. Only 'url' and 'base64' are supported."55 raise ValueError(error_message)565758def convert_to_openai_data_block(59 block: dict[str, Any],60 api: Literal["chat/completions", "responses"] = "chat/completions",61) -> dict[str, Any]:62 """Format standard data content block to format expected by OpenAI.6364 "Standard data content block" can include old-style LangChain v0 blocks65 (URLContentBlock, Base64ContentBlock, IDContentBlock) or new ones.6667 Args:68 block: The content block to convert.69 api: The OpenAI API being targeted. Either "chat/completions" or "responses".7071 Raises:72 ValueError: If required keys are missing.73 ValueError: If file URLs are used with Chat Completions API.74 ValueError: If block type is unsupported.7576 Returns:77 The formatted content block.78 """79 if block["type"] == "image":80 chat_completions_block = convert_to_openai_image_block(block)81 if api == "responses":82 formatted_block = {83 "type": "input_image",84 "image_url": chat_completions_block["image_url"]["url"],85 }86 if chat_completions_block["image_url"].get("detail"):87 formatted_block["detail"] = chat_completions_block["image_url"][88 "detail"89 ]90 else:91 formatted_block = chat_completions_block9293 elif block["type"] == "file":94 if block.get("source_type") == "base64" or "base64" in block:95 # Handle v0 format (Base64CB): {"source_type": "base64", "data": "...", ...}96 # Handle v1 format (IDCB): {"base64": "...", ...}97 base64_data = block["data"] if "source_type" in block else block["base64"]98 file = {"file_data": f"data:{block['mime_type']};base64,{base64_data}"}99 if filename := block.get("filename"):100 file["filename"] = filename101 elif (extras := block.get("extras")) and ("filename" in extras):102 file["filename"] = extras["filename"]103 elif (extras := block.get("metadata")) and ("filename" in extras):104 # Backward compat105 file["filename"] = extras["filename"]106 else:107 # Can't infer filename; set a placeholder default for compatibility.108 file["filename"] = "LC_AUTOGENERATED"109 warnings.warn(110 "OpenAI may require a filename for file uploads. Specify a filename"111 " in the content block, e.g.: {'type': 'file', 'mime_type': "112 "'...', 'base64': '...', 'filename': 'my-file.pdf'}. "113 "Using placeholder filename 'LC_AUTOGENERATED'.",114 stacklevel=1,115 )116 formatted_block = {"type": "file", "file": file}117 if api == "responses":118 formatted_block = {"type": "input_file", **formatted_block["file"]}119 elif block.get("source_type") == "id" or "file_id" in block:120 # Handle v0 format (IDContentBlock): {"source_type": "id", "id": "...", ...}121 # Handle v1 format (IDCB): {"file_id": "...", ...}122 file_id = block["id"] if "source_type" in block else block["file_id"]123 formatted_block = {"type": "file", "file": {"file_id": file_id}}124 if api == "responses":125 formatted_block = {"type": "input_file", **formatted_block["file"]}126 elif "url" in block: # Intentionally do not check for source_type="url"127 if api == "chat/completions":128 error_msg = "OpenAI Chat Completions does not support file URLs."129 raise ValueError(error_msg)130 # Only supported by Responses API; return in that format131 formatted_block = {"type": "input_file", "file_url": block["url"]}132 else:133 error_msg = "Keys base64, url, or file_id required for file blocks."134 raise ValueError(error_msg)135136 elif block["type"] == "audio":137 if "base64" in block or block.get("source_type") == "base64":138 # Handle v0 format: {"source_type": "base64", "data": "...", ...}139 # Handle v1 format: {"base64": "...", ...}140 base64_data = block["data"] if "source_type" in block else block["base64"]141 audio_format = block["mime_type"].split("/")[-1]142 formatted_block = {143 "type": "input_audio",144 "input_audio": {"data": base64_data, "format": audio_format},145 }146 else:147 error_msg = "Key base64 is required for audio blocks."148 raise ValueError(error_msg)149 else:150 error_msg = f"Block of type {block['type']} is not supported."151 raise ValueError(error_msg)152153 return formatted_block154155156# v1 / Chat Completions157def _convert_to_v1_from_chat_completions(158 message: AIMessage,159) -> list[types.ContentBlock]:160 """Mutate a Chat Completions message to v1 format."""161 content_blocks: list[types.ContentBlock] = []162 if isinstance(message.content, str):163 if message.content:164 content_blocks = [{"type": "text", "text": message.content}]165 else:166 content_blocks = []167168 for tool_call in message.tool_calls:169 content_blocks.append(170 {171 "type": "tool_call",172 "name": tool_call["name"],173 "args": tool_call["args"],174 "id": tool_call.get("id"),175 }176 )177178 return content_blocks179180181def _convert_to_v1_from_chat_completions_input(182 content: list[types.ContentBlock],183) -> list[types.ContentBlock]:184 """Convert OpenAI Chat Completions format blocks to v1 format.185186 During the `content_blocks` parsing process, we wrap blocks not recognized as a v1187 block as a `'non_standard'` block with the original block stored in the `value`188 field. This function attempts to unpack those blocks and convert any blocks that189 might be OpenAI format to v1 ContentBlocks.190191 If conversion fails, the block is left as a `'non_standard'` block.192193 Args:194 content: List of content blocks to process.195196 Returns:197 Updated list with OpenAI blocks converted to v1 format.198 """199 converted_blocks = []200 unpacked_blocks: list[dict[str, Any]] = [201 cast("dict[str, Any]", block)202 if block.get("type") != "non_standard"203 else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks204 for block in content205 ]206 for block in unpacked_blocks:207 if block.get("type") in {208 "image_url",209 "input_audio",210 "file",211 } and is_openai_data_block(block):212 converted_block = _convert_openai_format_to_data_block(block)213 # If conversion succeeded, use it; otherwise keep as non_standard214 if (215 isinstance(converted_block, dict)216 and converted_block.get("type") in types.KNOWN_BLOCK_TYPES217 ):218 converted_blocks.append(cast("types.ContentBlock", converted_block))219 else:220 converted_blocks.append({"type": "non_standard", "value": block})221 elif block.get("type") in types.KNOWN_BLOCK_TYPES:222 converted_blocks.append(cast("types.ContentBlock", block))223 else:224 converted_blocks.append({"type": "non_standard", "value": block})225226 return converted_blocks227228229def _convert_to_v1_from_chat_completions_chunk(230 chunk: AIMessageChunk,231) -> list[types.ContentBlock]:232 """Mutate a Chat Completions chunk to v1 format."""233 content_blocks: list[types.ContentBlock] = []234 if isinstance(chunk.content, str):235 if chunk.content:236 content_blocks = [{"type": "text", "text": chunk.content}]237 else:238 content_blocks = []239240 if chunk.chunk_position == "last":241 for tool_call in chunk.tool_calls:242 content_blocks.append(243 {244 "type": "tool_call",245 "name": tool_call["name"],246 "args": tool_call["args"],247 "id": tool_call.get("id"),248 }249 )250251 else:252 for tool_call_chunk in chunk.tool_call_chunks:253 tc: types.ToolCallChunk = {254 "type": "tool_call_chunk",255 "id": tool_call_chunk.get("id"),256 "name": tool_call_chunk.get("name"),257 "args": tool_call_chunk.get("args"),258 }259 if (idx := tool_call_chunk.get("index")) is not None:260 tc["index"] = idx261 content_blocks.append(tc)262263 return content_blocks264265266def _convert_from_v1_to_chat_completions(message: AIMessage) -> AIMessage:267 """Convert a v1 message to the Chat Completions format."""268 if isinstance(message.content, list):269 new_content: list[Any] = []270 for block in message.content:271 if isinstance(block, dict):272 block_type = block.get("type")273 if block_type == "text":274 # Strip annotations275 new_content.append({"type": "text", "text": block["text"]})276 elif block_type in {"reasoning", "tool_call"}:277 pass278 else:279 new_content.append(block)280 else:281 new_content.append(block)282 return message.model_copy(update={"content": new_content})283284 return message285286287# Responses288_FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__"289290291def _convert_from_v03_ai_message(message: AIMessage) -> AIMessage:292 """Convert v0 AIMessage into `output_version="responses/v1"` format."""293 # Only update ChatOpenAI v0.3 AIMessages294 is_chatopenai_v03 = (295 isinstance(message.content, list)296 and all(isinstance(b, dict) for b in message.content)297 ) and (298 any(299 item in message.additional_kwargs300 for item in [301 "reasoning",302 "tool_outputs",303 "refusal",304 _FUNCTION_CALL_IDS_MAP_KEY,305 ]306 )307 or (308 isinstance(message.id, str)309 and message.id.startswith("msg_")310 and (response_id := message.response_metadata.get("id"))311 and isinstance(response_id, str)312 and response_id.startswith("resp_")313 )314 )315 if not is_chatopenai_v03:316 return message317318 content_order = [319 "reasoning",320 "code_interpreter_call",321 "mcp_call",322 "image_generation_call",323 "text",324 "refusal",325 "function_call",326 "computer_call",327 "mcp_list_tools",328 "mcp_approval_request",329 # N. B. "web_search_call" and "file_search_call" were not passed back in330 # in v0.3331 ]332333 # Build a bucket for every known block type334 buckets: dict[str, list[Any]] = {key: [] for key in content_order}335 unknown_blocks = []336337 # Reasoning338 if reasoning := message.additional_kwargs.get("reasoning"):339 if "type" not in reasoning:340 reasoning = {**reasoning, "type": "reasoning"}341 buckets["reasoning"].append(reasoning)342343 # Refusal344 if refusal := message.additional_kwargs.get("refusal"):345 buckets["refusal"].append({"type": "refusal", "refusal": refusal})346347 # Text348 for block in message.content:349 if isinstance(block, dict) and block.get("type") == "text":350 block_copy = block.copy()351 if isinstance(message.id, str) and message.id.startswith("msg_"):352 block_copy["id"] = message.id353 buckets["text"].append(block_copy)354 else:355 unknown_blocks.append(block)356357 # Function calls358 function_call_ids = message.additional_kwargs.get(_FUNCTION_CALL_IDS_MAP_KEY)359 if (360 isinstance(message, AIMessageChunk)361 and len(message.tool_call_chunks) == 1362 and message.chunk_position != "last"363 ):364 # Isolated chunk365 tool_call_chunk = message.tool_call_chunks[0]366 function_call = {367 "type": "function_call",368 "name": tool_call_chunk.get("name"),369 "arguments": tool_call_chunk.get("args"),370 "call_id": tool_call_chunk.get("id"),371 }372 if function_call_ids is not None and (373 id_ := function_call_ids.get(tool_call_chunk.get("id"))374 ):375 function_call["id"] = id_376 buckets["function_call"].append(function_call)377 else:378 for tool_call in message.tool_calls:379 function_call = {380 "type": "function_call",381 "name": tool_call["name"],382 "arguments": json.dumps(tool_call["args"], ensure_ascii=False),383 "call_id": tool_call["id"],384 }385 if function_call_ids is not None and (386 id_ := function_call_ids.get(tool_call["id"])387 ):388 function_call["id"] = id_389 buckets["function_call"].append(function_call)390391 # Tool outputs392 tool_outputs = message.additional_kwargs.get("tool_outputs", [])393 for block in tool_outputs:394 if isinstance(block, dict) and (key := block.get("type")) and key in buckets:395 buckets[key].append(block)396 else:397 unknown_blocks.append(block)398399 # Re-assemble the content list in the canonical order400 new_content = []401 for key in content_order:402 new_content.extend(buckets[key])403 new_content.extend(unknown_blocks)404405 new_additional_kwargs = dict(message.additional_kwargs)406 new_additional_kwargs.pop("reasoning", None)407 new_additional_kwargs.pop("refusal", None)408 new_additional_kwargs.pop("tool_outputs", None)409410 if "id" in message.response_metadata:411 new_id = message.response_metadata["id"]412 else:413 new_id = message.id414415 return message.model_copy(416 update={417 "content": new_content,418 "additional_kwargs": new_additional_kwargs,419 "id": new_id,420 },421 deep=False,422 )423424425def _convert_openai_format_to_data_block(426 block: dict[str, Any],427) -> types.ContentBlock | dict[str, Any]:428 """Convert OpenAI image/audio/file content block to respective v1 multimodal block.429430 We expect that the incoming block is verified to be in OpenAI Chat Completions431 format.432433 If parsing fails, passes block through unchanged.434435 Mappings (Chat Completions to LangChain v1):436 - Image -> `ImageContentBlock`437 - Audio -> `AudioContentBlock`438 - File -> `FileContentBlock`439440 """441442 # Extract extra keys to put them in `extras`443 def _extract_extras(444 block_dict: dict[str, Any], known_keys: set[str]445 ) -> dict[str, Any]:446 """Extract unknown keys from block to preserve as extras."""447 return {k: v for k, v in block_dict.items() if k not in known_keys}448449 # base64-style image block450 if (block["type"] == "image_url") and (451 parsed := _parse_data_uri(block["image_url"]["url"])452 ):453 known_keys = {"type", "image_url"}454 extras = _extract_extras(block, known_keys)455456 # Also extract extras from nested image_url dict457 image_url_known_keys = {"url"}458 image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)459460 # Merge extras461 all_extras = {**extras}462 for key, value in image_url_extras.items():463 if key == "detail": # Don't rename464 all_extras["detail"] = value465 else:466 all_extras[f"image_url_{key}"] = value467468 return types.create_image_block(469 # Even though this is labeled as `url`, it can be base64-encoded470 base64=parsed["data"],471 mime_type=parsed["mime_type"],472 **all_extras,473 )474475 # url-style image block476 if (block["type"] == "image_url") and isinstance(477 block["image_url"].get("url"), str478 ):479 known_keys = {"type", "image_url"}480 extras = _extract_extras(block, known_keys)481482 image_url_known_keys = {"url"}483 image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)484485 all_extras = {**extras}486 for key, value in image_url_extras.items():487 if key == "detail": # Don't rename488 all_extras["detail"] = value489 else:490 all_extras[f"image_url_{key}"] = value491492 return types.create_image_block(493 url=block["image_url"]["url"],494 **all_extras,495 )496497 # base64-style audio block498 # audio is only represented via raw data, no url or ID option499 if block["type"] == "input_audio":500 known_keys = {"type", "input_audio"}501 extras = _extract_extras(block, known_keys)502503 # Also extract extras from nested audio dict504 audio_known_keys = {"data", "format"}505 audio_extras = _extract_extras(block["input_audio"], audio_known_keys)506507 all_extras = {**extras}508 for key, value in audio_extras.items():509 all_extras[f"audio_{key}"] = value510511 return types.create_audio_block(512 base64=block["input_audio"]["data"],513 mime_type=f"audio/{block['input_audio']['format']}",514 **all_extras,515 )516517 # id-style file block518 if block.get("type") == "file" and "file_id" in block.get("file", {}):519 known_keys = {"type", "file"}520 extras = _extract_extras(block, known_keys)521522 file_known_keys = {"file_id"}523 file_extras = _extract_extras(block["file"], file_known_keys)524525 all_extras = {**extras}526 for key, value in file_extras.items():527 all_extras[f"file_{key}"] = value528529 return types.create_file_block(530 file_id=block["file"]["file_id"],531 **all_extras,532 )533534 # base64-style file block535 if (block["type"] == "file") and (536 parsed := _parse_data_uri(block["file"]["file_data"])537 ):538 known_keys = {"type", "file"}539 extras = _extract_extras(block, known_keys)540541 file_known_keys = {"file_data", "filename"}542 file_extras = _extract_extras(block["file"], file_known_keys)543544 all_extras = {**extras}545 for key, value in file_extras.items():546 all_extras[f"file_{key}"] = value547548 filename = block["file"].get("filename")549 return types.create_file_block(550 base64=parsed["data"],551 mime_type="application/pdf",552 filename=filename,553 **all_extras,554 )555556 # Escape hatch557 return block558559560# v1 / Responses561def _convert_annotation_to_v1(annotation: dict[str, Any]) -> types.Annotation:562 annotation_type = annotation.get("type")563564 if annotation_type == "url_citation":565 known_fields = {566 "type",567 "url",568 "title",569 "cited_text",570 "start_index",571 "end_index",572 }573 url_citation = cast("types.Citation", {})574 for field in ("end_index", "start_index", "title"):575 if field in annotation:576 url_citation[field] = annotation[field]577 url_citation["type"] = "citation"578 url_citation["url"] = annotation["url"]579 for field, value in annotation.items():580 if field not in known_fields:581 if "extras" not in url_citation:582 url_citation["extras"] = {}583 url_citation["extras"][field] = value584 return url_citation585586 if annotation_type == "file_citation":587 known_fields = {588 "type",589 "title",590 "cited_text",591 "start_index",592 "end_index",593 "filename",594 }595 document_citation: types.Citation = {"type": "citation"}596 if "filename" in annotation:597 document_citation["title"] = annotation["filename"]598 for field, value in annotation.items():599 if field not in known_fields:600 if "extras" not in document_citation:601 document_citation["extras"] = {}602 document_citation["extras"][field] = value603604 return document_citation605606 # TODO: standardise container_file_citation?607 non_standard_annotation: types.NonStandardAnnotation = {608 "type": "non_standard_annotation",609 "value": annotation,610 }611 return non_standard_annotation612613614def _explode_reasoning(block: dict[str, Any]) -> Iterator[types.ReasoningContentBlock]:615 if "summary" not in block:616 yield cast("types.ReasoningContentBlock", block)617 return618619 known_fields = {"type", "reasoning", "id", "index"}620 unknown_fields = [621 field for field in block if field != "summary" and field not in known_fields622 ]623 if unknown_fields:624 block["extras"] = {}625 for field in unknown_fields:626 block["extras"][field] = block.pop(field)627628 if not block["summary"]:629 # [{'id': 'rs_...', 'summary': [], 'type': 'reasoning', 'index': 0}]630 block = {k: v for k, v in block.items() if k != "summary"}631 if "index" in block:632 meaningful_idx = f"{block['index']}_0"633 block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"634 yield cast("types.ReasoningContentBlock", block)635 return636637 # Common part for every exploded line, except 'summary'638 common = {k: v for k, v in block.items() if k in known_fields}639640 # Optional keys that must appear only in the first exploded item641 first_only = block.pop("extras", None)642643 for idx, part in enumerate(block["summary"]):644 new_block = dict(common)645 new_block["reasoning"] = part.get("text", "")646 if idx == 0 and first_only:647 new_block.update(first_only)648 if "index" in new_block:649 summary_index = part.get("index", 0)650 meaningful_idx = f"{new_block['index']}_{summary_index}"651 new_block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"652653 yield cast("types.ReasoningContentBlock", new_block)654655656def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock]:657 """Convert a Responses message to v1 format."""658659 def _iter_blocks() -> Iterator[types.ContentBlock]:660 for raw_block in message.content:661 if not isinstance(raw_block, dict):662 continue663 block = raw_block.copy()664 block_type = block.get("type")665666 if block_type == "text":667 if "text" not in block:668 block["text"] = ""669 if "annotations" in block:670 block["annotations"] = [671 _convert_annotation_to_v1(a) for a in block["annotations"]672 ]673 if "index" in block:674 block["index"] = f"lc_txt_{block['index']}"675 yield cast("types.TextContentBlock", block)676677 elif block_type == "reasoning":678 yield from _explode_reasoning(block)679680 elif block_type == "image_generation_call" and (681 result := block.get("result")682 ):683 new_block = {"type": "image", "base64": result}684 if output_format := block.get("output_format"):685 new_block["mime_type"] = f"image/{output_format}"686 if "id" in block:687 new_block["id"] = block["id"]688 if "index" in block:689 new_block["index"] = f"lc_img_{block['index']}"690 for extra_key in (691 "status",692 "background",693 "output_format",694 "quality",695 "revised_prompt",696 "size",697 ):698 if extra_key in block:699 if "extras" not in new_block:700 new_block["extras"] = {}701 new_block["extras"][extra_key] = block[extra_key]702 yield cast("types.ImageContentBlock", new_block)703704 elif block_type == "function_call":705 tool_call_block: (706 types.ToolCall | types.InvalidToolCall | types.ToolCallChunk | None707 ) = None708 call_id = block.get("call_id", "")709710 if (711 isinstance(message, AIMessageChunk)712 and len(message.tool_call_chunks) == 1713 and message.chunk_position != "last"714 ):715 tool_call_block = message.tool_call_chunks[0].copy() # type: ignore[assignment]716 elif call_id:717 for tool_call in message.tool_calls or []:718 if tool_call.get("id") == call_id:719 tool_call_block = {720 "type": "tool_call",721 "name": tool_call["name"],722 "args": tool_call["args"],723 "id": tool_call.get("id"),724 }725 break726 else:727 for invalid_tool_call in message.invalid_tool_calls or []:728 if invalid_tool_call.get("id") == call_id:729 tool_call_block = invalid_tool_call.copy()730 break731 if tool_call_block:732 if "id" in block:733 if "extras" not in tool_call_block:734 tool_call_block["extras"] = {}735 tool_call_block["extras"]["item_id"] = block["id"]736 if "index" in block:737 tool_call_block["index"] = f"lc_tc_{block['index']}"738 for extra_key in ("status", "namespace"):739 if extra_key in block:740 if "extras" not in tool_call_block:741 tool_call_block["extras"] = {}742 tool_call_block["extras"][extra_key] = block[extra_key]743 yield tool_call_block744745 elif block_type == "web_search_call":746 web_search_call = {747 "type": "server_tool_call",748 "name": "web_search",749 "args": {},750 "id": block["id"],751 }752 if "index" in block:753 web_search_call["index"] = f"lc_wsc_{block['index']}"754755 sources: dict[str, Any] | None = None756 if "action" in block and isinstance(block["action"], dict):757 if "sources" in block["action"]:758 sources = block["action"]["sources"]759 web_search_call["args"] = {760 k: v for k, v in block["action"].items() if k != "sources"761 }762 for key in block:763 if key not in {"type", "id", "action", "status", "index"}:764 web_search_call[key] = block[key]765766 yield cast("types.ServerToolCall", web_search_call)767768 # If .content already has web_search_result, don't add769 if not any(770 isinstance(other_block, dict)771 and other_block.get("type") == "web_search_result"772 and other_block.get("id") == block["id"]773 for other_block in message.content774 ):775 web_search_result = {776 "type": "server_tool_result",777 "tool_call_id": block["id"],778 }779 if sources:780 web_search_result["output"] = {"sources": sources}781782 status = block.get("status")783 if status == "failed":784 web_search_result["status"] = "error"785 elif status == "completed":786 web_search_result["status"] = "success"787 elif status:788 web_search_result["extras"] = {"status": status}789 if "index" in block and isinstance(block["index"], int):790 web_search_result["index"] = f"lc_wsr_{block['index'] + 1}"791 yield cast("types.ServerToolResult", web_search_result)792793 elif block_type == "file_search_call":794 file_search_call = {795 "type": "server_tool_call",796 "name": "file_search",797 "id": block["id"],798 "args": {"queries": block.get("queries", [])},799 }800 if "index" in block:801 file_search_call["index"] = f"lc_fsc_{block['index']}"802803 for key in block:804 if key not in {805 "type",806 "id",807 "queries",808 "results",809 "status",810 "index",811 }:812 file_search_call[key] = block[key]813814 yield cast("types.ServerToolCall", file_search_call)815816 file_search_result = {817 "type": "server_tool_result",818 "tool_call_id": block["id"],819 }820 if file_search_output := block.get("results"):821 file_search_result["output"] = file_search_output822823 status = block.get("status")824 if status == "failed":825 file_search_result["status"] = "error"826 elif status == "completed":827 file_search_result["status"] = "success"828 elif status:829 file_search_result["extras"] = {"status": status}830 if "index" in block and isinstance(block["index"], int):831 file_search_result["index"] = f"lc_fsr_{block['index'] + 1}"832 yield cast("types.ServerToolResult", file_search_result)833834 elif block_type == "code_interpreter_call":835 code_interpreter_call = {836 "type": "server_tool_call",837 "name": "code_interpreter",838 "id": block["id"],839 }840 if "code" in block:841 code_interpreter_call["args"] = {"code": block["code"]}842 if "index" in block:843 code_interpreter_call["index"] = f"lc_cic_{block['index']}"844 known_fields = {845 "type",846 "id",847 "outputs",848 "status",849 "code",850 "extras",851 "index",852 }853 for key in block:854 if key not in known_fields:855 if "extras" not in code_interpreter_call:856 code_interpreter_call["extras"] = {}857 code_interpreter_call["extras"][key] = block[key]858859 code_interpreter_result = {860 "type": "server_tool_result",861 "tool_call_id": block["id"],862 }863 if "outputs" in block:864 code_interpreter_result["output"] = block["outputs"]865866 status = block.get("status")867 if status == "failed":868 code_interpreter_result["status"] = "error"869 elif status == "completed":870 code_interpreter_result["status"] = "success"871 elif status:872 code_interpreter_result["extras"] = {"status": status}873 if "index" in block and isinstance(block["index"], int):874 code_interpreter_result["index"] = f"lc_cir_{block['index'] + 1}"875876 yield cast("types.ServerToolCall", code_interpreter_call)877 yield cast("types.ServerToolResult", code_interpreter_result)878879 elif block_type == "mcp_call":880 mcp_call = {881 "type": "server_tool_call",882 "name": "remote_mcp",883 "id": block["id"],884 }885 if (arguments := block.get("arguments")) and isinstance(arguments, str):886 try:887 mcp_call["args"] = json.loads(block["arguments"])888 except json.JSONDecodeError:889 mcp_call["extras"] = {"arguments": arguments}890 if "name" in block:891 if "extras" not in mcp_call:892 mcp_call["extras"] = {}893 mcp_call["extras"]["tool_name"] = block["name"]894 if "server_label" in block:895 if "extras" not in mcp_call:896 mcp_call["extras"] = {}897 mcp_call["extras"]["server_label"] = block["server_label"]898 if "index" in block:899 mcp_call["index"] = f"lc_mcp_{block['index']}"900 known_fields = {901 "type",902 "id",903 "arguments",904 "name",905 "server_label",906 "output",907 "error",908 "extras",909 "index",910 }911 for key in block:912 if key not in known_fields:913 if "extras" not in mcp_call:914 mcp_call["extras"] = {}915 mcp_call["extras"][key] = block[key]916917 yield cast("types.ServerToolCall", mcp_call)918919 mcp_result = {920 "type": "server_tool_result",921 "tool_call_id": block["id"],922 }923 if mcp_output := block.get("output"):924 mcp_result["output"] = mcp_output925926 error = block.get("error")927 if error:928 if "extras" not in mcp_result:929 mcp_result["extras"] = {}930 mcp_result["extras"]["error"] = error931 mcp_result["status"] = "error"932 else:933 mcp_result["status"] = "success"934935 if "index" in block and isinstance(block["index"], int):936 mcp_result["index"] = f"lc_mcpr_{block['index'] + 1}"937 yield cast("types.ServerToolResult", mcp_result)938939 elif block_type == "mcp_list_tools":940 mcp_list_tools_call = {941 "type": "server_tool_call",942 "name": "mcp_list_tools",943 "args": {},944 "id": block["id"],945 }946 if "server_label" in block:947 mcp_list_tools_call["extras"] = {}948 mcp_list_tools_call["extras"]["server_label"] = block[949 "server_label"950 ]951 if "index" in block:952 mcp_list_tools_call["index"] = f"lc_mlt_{block['index']}"953 known_fields = {954 "type",955 "id",956 "name",957 "server_label",958 "tools",959 "error",960 "extras",961 "index",962 }963 for key in block:964 if key not in known_fields:965 if "extras" not in mcp_list_tools_call:966 mcp_list_tools_call["extras"] = {}967 mcp_list_tools_call["extras"][key] = block[key]968969 yield cast("types.ServerToolCall", mcp_list_tools_call)970971 mcp_list_tools_result = {972 "type": "server_tool_result",973 "tool_call_id": block["id"],974 }975 if mcp_output := block.get("tools"):976 mcp_list_tools_result["output"] = mcp_output977978 error = block.get("error")979 if error:980 if "extras" not in mcp_list_tools_result:981 mcp_list_tools_result["extras"] = {}982 mcp_list_tools_result["extras"]["error"] = error983 mcp_list_tools_result["status"] = "error"984 else:985 mcp_list_tools_result["status"] = "success"986987 if "index" in block and isinstance(block["index"], int):988 mcp_list_tools_result["index"] = f"lc_mltr_{block['index'] + 1}"989 yield cast("types.ServerToolResult", mcp_list_tools_result)990991 elif (992 block_type == "tool_search_call" and block.get("execution") == "server"993 ):994 tool_search_call: dict[str, Any] = {995 "type": "server_tool_call",996 "name": "tool_search",997 "id": block["id"],998 "args": block.get("arguments", {}),999 }1000 if "index" in block:1001 tool_search_call["index"] = f"lc_tsc_{block['index']}"1002 extras: dict[str, Any] = {}1003 known = {"type", "id", "arguments", "index"}1004 for key in block:1005 if key not in known:1006 extras[key] = block[key]1007 if extras:1008 tool_search_call["extras"] = extras1009 yield cast("types.ServerToolCall", tool_search_call)10101011 elif (1012 block_type == "tool_search_output"1013 and block.get("execution") == "server"1014 ):1015 tool_search_output: dict[str, Any] = {1016 "type": "server_tool_result",1017 "tool_call_id": block["id"],1018 "output": {"tools": block.get("tools", [])},1019 }1020 status = block.get("status")1021 if status == "failed":1022 tool_search_output["status"] = "error"1023 elif status == "completed":1024 tool_search_output["status"] = "success"1025 if "index" in block and isinstance(block["index"], int):1026 tool_search_output["index"] = f"lc_tso_{block['index']}"1027 extras_out: dict[str, Any] = {"name": "tool_search"}1028 known_out = {"type", "id", "status", "tools", "index"}1029 for key in block:1030 if key not in known_out:1031 extras_out[key] = block[key]1032 if extras_out:1033 tool_search_output["extras"] = extras_out1034 yield cast("types.ServerToolResult", tool_search_output)10351036 elif block_type in types.KNOWN_BLOCK_TYPES:1037 yield cast("types.ContentBlock", block)1038 else:1039 new_block = {"type": "non_standard", "value": block}1040 if "index" in new_block["value"]:1041 new_block["index"] = f"lc_ns_{new_block['value'].pop('index')}"1042 yield cast("types.NonStandardContentBlock", new_block)10431044 return list(_iter_blocks())104510461047def translate_content(message: AIMessage) -> list[types.ContentBlock]:1048 """Derive standard content blocks from a message with OpenAI content.10491050 Args:1051 message: The message to translate.10521053 Returns:1054 The derived content blocks.1055 """1056 if isinstance(message.content, str):1057 return _convert_to_v1_from_chat_completions(message)1058 message = _convert_from_v03_ai_message(message)1059 return _convert_to_v1_from_responses(message)106010611062def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:1063 """Derive standard content blocks from a message chunk with OpenAI content.10641065 Args:1066 message: The message chunk to translate.10671068 Returns:1069 The derived content blocks.1070 """1071 if isinstance(message.content, str):1072 return _convert_to_v1_from_chat_completions_chunk(message)1073 message = _convert_from_v03_ai_message(message) # type: ignore[assignment]1074 return _convert_to_v1_from_responses(message)107510761077def _register_openai_translator() -> None:1078 """Register the OpenAI translator with the central registry.10791080 Run automatically when the module is imported.1081 """1082 from langchain_core.messages.block_translators import ( # noqa: PLC04151083 register_translator,1084 )10851086 register_translator("openai", translate_content, translate_content_chunk)108710881089_register_openai_translator()
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.