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: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, api: Literal["chat/completions", "responses"] = "chat/completions"60) -> dict:61 """Format standard data content block to format expected by OpenAI.6263 "Standard data content block" can include old-style LangChain v0 blocks64 (URLContentBlock, Base64ContentBlock, IDContentBlock) or new ones.6566 Args:67 block: The content block to convert.68 api: The OpenAI API being targeted. Either "chat/completions" or "responses".6970 Raises:71 ValueError: If required keys are missing.72 ValueError: If file URLs are used with Chat Completions API.73 ValueError: If block type is unsupported.7475 Returns:76 The formatted content block.77 """78 if block["type"] == "image":79 chat_completions_block = convert_to_openai_image_block(block)80 if api == "responses":81 formatted_block = {82 "type": "input_image",83 "image_url": chat_completions_block["image_url"]["url"],84 }85 if chat_completions_block["image_url"].get("detail"):86 formatted_block["detail"] = chat_completions_block["image_url"][87 "detail"88 ]89 else:90 formatted_block = chat_completions_block9192 elif block["type"] == "file":93 if block.get("source_type") == "base64" or "base64" in block:94 # Handle v0 format (Base64CB): {"source_type": "base64", "data": "...", ...}95 # Handle v1 format (IDCB): {"base64": "...", ...}96 base64_data = block["data"] if "source_type" in block else block["base64"]97 file = {"file_data": f"data:{block['mime_type']};base64,{base64_data}"}98 if filename := block.get("filename"):99 file["filename"] = filename100 elif (extras := block.get("extras")) and ("filename" in extras):101 file["filename"] = extras["filename"]102 elif (extras := block.get("metadata")) and ("filename" in extras):103 # Backward compat104 file["filename"] = extras["filename"]105 else:106 # Can't infer filename; set a placeholder default for compatibility.107 file["filename"] = "LC_AUTOGENERATED"108 warnings.warn(109 "OpenAI may require a filename for file uploads. Specify a filename"110 " in the content block, e.g.: {'type': 'file', 'mime_type': "111 "'...', 'base64': '...', 'filename': 'my-file.pdf'}. "112 "Using placeholder filename 'LC_AUTOGENERATED'.",113 stacklevel=1,114 )115 formatted_block = {"type": "file", "file": file}116 if api == "responses":117 formatted_block = {"type": "input_file", **formatted_block["file"]}118 elif block.get("source_type") == "id" or "file_id" in block:119 # Handle v0 format (IDContentBlock): {"source_type": "id", "id": "...", ...}120 # Handle v1 format (IDCB): {"file_id": "...", ...}121 file_id = block["id"] if "source_type" in block else block["file_id"]122 formatted_block = {"type": "file", "file": {"file_id": file_id}}123 if api == "responses":124 formatted_block = {"type": "input_file", **formatted_block["file"]}125 elif "url" in block: # Intentionally do not check for source_type="url"126 if api == "chat/completions":127 error_msg = "OpenAI Chat Completions does not support file URLs."128 raise ValueError(error_msg)129 # Only supported by Responses API; return in that format130 formatted_block = {"type": "input_file", "file_url": block["url"]}131 else:132 error_msg = "Keys base64, url, or file_id required for file blocks."133 raise ValueError(error_msg)134135 elif block["type"] == "audio":136 if "base64" in block or block.get("source_type") == "base64":137 # Handle v0 format: {"source_type": "base64", "data": "...", ...}138 # Handle v1 format: {"base64": "...", ...}139 base64_data = block["data"] if "source_type" in block else block["base64"]140 audio_format = block["mime_type"].split("/")[-1]141 formatted_block = {142 "type": "input_audio",143 "input_audio": {"data": base64_data, "format": audio_format},144 }145 else:146 error_msg = "Key base64 is required for audio blocks."147 raise ValueError(error_msg)148 else:149 error_msg = f"Block of type {block['type']} is not supported."150 raise ValueError(error_msg)151152 return formatted_block153154155# v1 / Chat Completions156def _convert_to_v1_from_chat_completions(157 message: AIMessage,158) -> list[types.ContentBlock]:159 """Mutate a Chat Completions message to v1 format."""160 content_blocks: list[types.ContentBlock] = []161 if isinstance(message.content, str):162 if message.content:163 content_blocks = [{"type": "text", "text": message.content}]164 else:165 content_blocks = []166167 for tool_call in message.tool_calls:168 content_blocks.append(169 {170 "type": "tool_call",171 "name": tool_call["name"],172 "args": tool_call["args"],173 "id": tool_call.get("id"),174 }175 )176177 return content_blocks178179180def _convert_to_v1_from_chat_completions_input(181 content: list[types.ContentBlock],182) -> list[types.ContentBlock]:183 """Convert OpenAI Chat Completions format blocks to v1 format.184185 During the `content_blocks` parsing process, we wrap blocks not recognized as a v1186 block as a `'non_standard'` block with the original block stored in the `value`187 field. This function attempts to unpack those blocks and convert any blocks that188 might be OpenAI format to v1 ContentBlocks.189190 If conversion fails, the block is left as a `'non_standard'` block.191192 Args:193 content: List of content blocks to process.194195 Returns:196 Updated list with OpenAI blocks converted to v1 format.197 """198 converted_blocks = []199 unpacked_blocks: list[dict[str, Any]] = [200 cast("dict[str, Any]", block)201 if block.get("type") != "non_standard"202 else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks203 for block in content204 ]205 for block in unpacked_blocks:206 if block.get("type") in {207 "image_url",208 "input_audio",209 "file",210 } and is_openai_data_block(block):211 converted_block = _convert_openai_format_to_data_block(block)212 # If conversion succeeded, use it; otherwise keep as non_standard213 if (214 isinstance(converted_block, dict)215 and converted_block.get("type") in types.KNOWN_BLOCK_TYPES216 ):217 converted_blocks.append(cast("types.ContentBlock", converted_block))218 else:219 converted_blocks.append({"type": "non_standard", "value": block})220 elif block.get("type") in types.KNOWN_BLOCK_TYPES:221 converted_blocks.append(cast("types.ContentBlock", block))222 else:223 converted_blocks.append({"type": "non_standard", "value": block})224225 return converted_blocks226227228def _convert_to_v1_from_chat_completions_chunk(229 chunk: AIMessageChunk,230) -> list[types.ContentBlock]:231 """Mutate a Chat Completions chunk to v1 format."""232 content_blocks: list[types.ContentBlock] = []233 if isinstance(chunk.content, str):234 if chunk.content:235 content_blocks = [{"type": "text", "text": chunk.content}]236 else:237 content_blocks = []238239 if chunk.chunk_position == "last":240 for tool_call in chunk.tool_calls:241 content_blocks.append(242 {243 "type": "tool_call",244 "name": tool_call["name"],245 "args": tool_call["args"],246 "id": tool_call.get("id"),247 }248 )249250 else:251 for tool_call_chunk in chunk.tool_call_chunks:252 tc: types.ToolCallChunk = {253 "type": "tool_call_chunk",254 "id": tool_call_chunk.get("id"),255 "name": tool_call_chunk.get("name"),256 "args": tool_call_chunk.get("args"),257 }258 if (idx := tool_call_chunk.get("index")) is not None:259 tc["index"] = idx260 content_blocks.append(tc)261262 return content_blocks263264265def _convert_from_v1_to_chat_completions(message: AIMessage) -> AIMessage:266 """Convert a v1 message to the Chat Completions format."""267 if isinstance(message.content, list):268 new_content: list = []269 for block in message.content:270 if isinstance(block, dict):271 block_type = block.get("type")272 if block_type == "text":273 # Strip annotations274 new_content.append({"type": "text", "text": block["text"]})275 elif block_type in {"reasoning", "tool_call"}:276 pass277 else:278 new_content.append(block)279 else:280 new_content.append(block)281 return message.model_copy(update={"content": new_content})282283 return message284285286# Responses287_FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__"288289290def _convert_from_v03_ai_message(message: AIMessage) -> AIMessage:291 """Convert v0 AIMessage into `output_version="responses/v1"` format."""292 # Only update ChatOpenAI v0.3 AIMessages293 is_chatopenai_v03 = (294 isinstance(message.content, list)295 and all(isinstance(b, dict) for b in message.content)296 ) and (297 any(298 item in message.additional_kwargs299 for item in [300 "reasoning",301 "tool_outputs",302 "refusal",303 _FUNCTION_CALL_IDS_MAP_KEY,304 ]305 )306 or (307 isinstance(message.id, str)308 and message.id.startswith("msg_")309 and (response_id := message.response_metadata.get("id"))310 and isinstance(response_id, str)311 and response_id.startswith("resp_")312 )313 )314 if not is_chatopenai_v03:315 return message316317 content_order = [318 "reasoning",319 "code_interpreter_call",320 "mcp_call",321 "image_generation_call",322 "text",323 "refusal",324 "function_call",325 "computer_call",326 "mcp_list_tools",327 "mcp_approval_request",328 # N. B. "web_search_call" and "file_search_call" were not passed back in329 # in v0.3330 ]331332 # Build a bucket for every known block type333 buckets: dict[str, list] = {key: [] for key in content_order}334 unknown_blocks = []335336 # Reasoning337 if reasoning := message.additional_kwargs.get("reasoning"):338 if "type" not in reasoning:339 reasoning = {**reasoning, "type": "reasoning"}340 buckets["reasoning"].append(reasoning)341342 # Refusal343 if refusal := message.additional_kwargs.get("refusal"):344 buckets["refusal"].append({"type": "refusal", "refusal": refusal})345346 # Text347 for block in message.content:348 if isinstance(block, dict) and block.get("type") == "text":349 block_copy = block.copy()350 if isinstance(message.id, str) and message.id.startswith("msg_"):351 block_copy["id"] = message.id352 buckets["text"].append(block_copy)353 else:354 unknown_blocks.append(block)355356 # Function calls357 function_call_ids = message.additional_kwargs.get(_FUNCTION_CALL_IDS_MAP_KEY)358 if (359 isinstance(message, AIMessageChunk)360 and len(message.tool_call_chunks) == 1361 and message.chunk_position != "last"362 ):363 # Isolated chunk364 tool_call_chunk = message.tool_call_chunks[0]365 function_call = {366 "type": "function_call",367 "name": tool_call_chunk.get("name"),368 "arguments": tool_call_chunk.get("args"),369 "call_id": tool_call_chunk.get("id"),370 }371 if function_call_ids is not None and (372 id_ := function_call_ids.get(tool_call_chunk.get("id"))373 ):374 function_call["id"] = id_375 buckets["function_call"].append(function_call)376 else:377 for tool_call in message.tool_calls:378 function_call = {379 "type": "function_call",380 "name": tool_call["name"],381 "arguments": json.dumps(tool_call["args"], ensure_ascii=False),382 "call_id": tool_call["id"],383 }384 if function_call_ids is not None and (385 id_ := function_call_ids.get(tool_call["id"])386 ):387 function_call["id"] = id_388 buckets["function_call"].append(function_call)389390 # Tool outputs391 tool_outputs = message.additional_kwargs.get("tool_outputs", [])392 for block in tool_outputs:393 if isinstance(block, dict) and (key := block.get("type")) and key in buckets:394 buckets[key].append(block)395 else:396 unknown_blocks.append(block)397398 # Re-assemble the content list in the canonical order399 new_content = []400 for key in content_order:401 new_content.extend(buckets[key])402 new_content.extend(unknown_blocks)403404 new_additional_kwargs = dict(message.additional_kwargs)405 new_additional_kwargs.pop("reasoning", None)406 new_additional_kwargs.pop("refusal", None)407 new_additional_kwargs.pop("tool_outputs", None)408409 if "id" in message.response_metadata:410 new_id = message.response_metadata["id"]411 else:412 new_id = message.id413414 return message.model_copy(415 update={416 "content": new_content,417 "additional_kwargs": new_additional_kwargs,418 "id": new_id,419 },420 deep=False,421 )422423424def _convert_openai_format_to_data_block(425 block: dict,426) -> types.ContentBlock | dict[Any, Any]:427 """Convert OpenAI image/audio/file content block to respective v1 multimodal block.428429 We expect that the incoming block is verified to be in OpenAI Chat Completions430 format.431432 If parsing fails, passes block through unchanged.433434 Mappings (Chat Completions to LangChain v1):435 - Image -> `ImageContentBlock`436 - Audio -> `AudioContentBlock`437 - File -> `FileContentBlock`438439 """440441 # Extract extra keys to put them in `extras`442 def _extract_extras(block_dict: dict, known_keys: set[str]) -> dict[str, Any]:443 """Extract unknown keys from block to preserve as extras."""444 return {k: v for k, v in block_dict.items() if k not in known_keys}445446 # base64-style image block447 if (block["type"] == "image_url") and (448 parsed := _parse_data_uri(block["image_url"]["url"])449 ):450 known_keys = {"type", "image_url"}451 extras = _extract_extras(block, known_keys)452453 # Also extract extras from nested image_url dict454 image_url_known_keys = {"url"}455 image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)456457 # Merge extras458 all_extras = {**extras}459 for key, value in image_url_extras.items():460 if key == "detail": # Don't rename461 all_extras["detail"] = value462 else:463 all_extras[f"image_url_{key}"] = value464465 return types.create_image_block(466 # Even though this is labeled as `url`, it can be base64-encoded467 base64=parsed["data"],468 mime_type=parsed["mime_type"],469 **all_extras,470 )471472 # url-style image block473 if (block["type"] == "image_url") and isinstance(474 block["image_url"].get("url"), str475 ):476 known_keys = {"type", "image_url"}477 extras = _extract_extras(block, known_keys)478479 image_url_known_keys = {"url"}480 image_url_extras = _extract_extras(block["image_url"], image_url_known_keys)481482 all_extras = {**extras}483 for key, value in image_url_extras.items():484 if key == "detail": # Don't rename485 all_extras["detail"] = value486 else:487 all_extras[f"image_url_{key}"] = value488489 return types.create_image_block(490 url=block["image_url"]["url"],491 **all_extras,492 )493494 # base64-style audio block495 # audio is only represented via raw data, no url or ID option496 if block["type"] == "input_audio":497 known_keys = {"type", "input_audio"}498 extras = _extract_extras(block, known_keys)499500 # Also extract extras from nested audio dict501 audio_known_keys = {"data", "format"}502 audio_extras = _extract_extras(block["input_audio"], audio_known_keys)503504 all_extras = {**extras}505 for key, value in audio_extras.items():506 all_extras[f"audio_{key}"] = value507508 return types.create_audio_block(509 base64=block["input_audio"]["data"],510 mime_type=f"audio/{block['input_audio']['format']}",511 **all_extras,512 )513514 # id-style file block515 if block.get("type") == "file" and "file_id" in block.get("file", {}):516 known_keys = {"type", "file"}517 extras = _extract_extras(block, known_keys)518519 file_known_keys = {"file_id"}520 file_extras = _extract_extras(block["file"], file_known_keys)521522 all_extras = {**extras}523 for key, value in file_extras.items():524 all_extras[f"file_{key}"] = value525526 return types.create_file_block(527 file_id=block["file"]["file_id"],528 **all_extras,529 )530531 # base64-style file block532 if (block["type"] == "file") and (533 parsed := _parse_data_uri(block["file"]["file_data"])534 ):535 known_keys = {"type", "file"}536 extras = _extract_extras(block, known_keys)537538 file_known_keys = {"file_data", "filename"}539 file_extras = _extract_extras(block["file"], file_known_keys)540541 all_extras = {**extras}542 for key, value in file_extras.items():543 all_extras[f"file_{key}"] = value544545 filename = block["file"].get("filename")546 return types.create_file_block(547 base64=parsed["data"],548 mime_type="application/pdf",549 filename=filename,550 **all_extras,551 )552553 # Escape hatch554 return block555556557# v1 / Responses558def _convert_annotation_to_v1(annotation: dict[str, Any]) -> types.Annotation:559 annotation_type = annotation.get("type")560561 if annotation_type == "url_citation":562 known_fields = {563 "type",564 "url",565 "title",566 "cited_text",567 "start_index",568 "end_index",569 }570 url_citation = cast("types.Citation", {})571 for field in ("end_index", "start_index", "title"):572 if field in annotation:573 url_citation[field] = annotation[field]574 url_citation["type"] = "citation"575 url_citation["url"] = annotation["url"]576 for field, value in annotation.items():577 if field not in known_fields:578 if "extras" not in url_citation:579 url_citation["extras"] = {}580 url_citation["extras"][field] = value581 return url_citation582583 if annotation_type == "file_citation":584 known_fields = {585 "type",586 "title",587 "cited_text",588 "start_index",589 "end_index",590 "filename",591 }592 document_citation: types.Citation = {"type": "citation"}593 if "filename" in annotation:594 document_citation["title"] = annotation["filename"]595 for field, value in annotation.items():596 if field not in known_fields:597 if "extras" not in document_citation:598 document_citation["extras"] = {}599 document_citation["extras"][field] = value600601 return document_citation602603 # TODO: standardise container_file_citation?604 non_standard_annotation: types.NonStandardAnnotation = {605 "type": "non_standard_annotation",606 "value": annotation,607 }608 return non_standard_annotation609610611def _explode_reasoning(block: dict[str, Any]) -> Iterator[types.ReasoningContentBlock]:612 if "summary" not in block:613 yield cast("types.ReasoningContentBlock", block)614 return615616 known_fields = {"type", "reasoning", "id", "index"}617 unknown_fields = [618 field for field in block if field != "summary" and field not in known_fields619 ]620 if unknown_fields:621 block["extras"] = {}622 for field in unknown_fields:623 block["extras"][field] = block.pop(field)624625 if not block["summary"]:626 # [{'id': 'rs_...', 'summary': [], 'type': 'reasoning', 'index': 0}]627 block = {k: v for k, v in block.items() if k != "summary"}628 if "index" in block:629 meaningful_idx = f"{block['index']}_0"630 block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"631 yield cast("types.ReasoningContentBlock", block)632 return633634 # Common part for every exploded line, except 'summary'635 common = {k: v for k, v in block.items() if k in known_fields}636637 # Optional keys that must appear only in the first exploded item638 first_only = block.pop("extras", None)639640 for idx, part in enumerate(block["summary"]):641 new_block = dict(common)642 new_block["reasoning"] = part.get("text", "")643 if idx == 0 and first_only:644 new_block.update(first_only)645 if "index" in new_block:646 summary_index = part.get("index", 0)647 meaningful_idx = f"{new_block['index']}_{summary_index}"648 new_block["index"] = f"lc_rs_{meaningful_idx.encode().hex()}"649650 yield cast("types.ReasoningContentBlock", new_block)651652653def _convert_to_v1_from_responses(message: AIMessage) -> list[types.ContentBlock]:654 """Convert a Responses message to v1 format."""655656 def _iter_blocks() -> Iterator[types.ContentBlock]:657 for raw_block in message.content:658 if not isinstance(raw_block, dict):659 continue660 block = raw_block.copy()661 block_type = block.get("type")662663 if block_type == "text":664 if "text" not in block:665 block["text"] = ""666 if "annotations" in block:667 block["annotations"] = [668 _convert_annotation_to_v1(a) for a in block["annotations"]669 ]670 if "index" in block:671 block["index"] = f"lc_txt_{block['index']}"672 yield cast("types.TextContentBlock", block)673674 elif block_type == "reasoning":675 yield from _explode_reasoning(block)676677 elif block_type == "image_generation_call" and (678 result := block.get("result")679 ):680 new_block = {"type": "image", "base64": result}681 if output_format := block.get("output_format"):682 new_block["mime_type"] = f"image/{output_format}"683 if "id" in block:684 new_block["id"] = block["id"]685 if "index" in block:686 new_block["index"] = f"lc_img_{block['index']}"687 for extra_key in (688 "status",689 "background",690 "output_format",691 "quality",692 "revised_prompt",693 "size",694 ):695 if extra_key in block:696 if "extras" not in new_block:697 new_block["extras"] = {}698 new_block["extras"][extra_key] = block[extra_key]699 yield cast("types.ImageContentBlock", new_block)700701 elif block_type == "function_call":702 tool_call_block: (703 types.ToolCall | types.InvalidToolCall | types.ToolCallChunk | None704 ) = None705 call_id = block.get("call_id", "")706707 if (708 isinstance(message, AIMessageChunk)709 and len(message.tool_call_chunks) == 1710 and message.chunk_position != "last"711 ):712 tool_call_block = message.tool_call_chunks[0].copy() # type: ignore[assignment]713 elif call_id:714 for tool_call in message.tool_calls or []:715 if tool_call.get("id") == call_id:716 tool_call_block = {717 "type": "tool_call",718 "name": tool_call["name"],719 "args": tool_call["args"],720 "id": tool_call.get("id"),721 }722 break723 else:724 for invalid_tool_call in message.invalid_tool_calls or []:725 if invalid_tool_call.get("id") == call_id:726 tool_call_block = invalid_tool_call.copy()727 break728 if tool_call_block:729 if "id" in block:730 if "extras" not in tool_call_block:731 tool_call_block["extras"] = {}732 tool_call_block["extras"]["item_id"] = block["id"]733 if "index" in block:734 tool_call_block["index"] = f"lc_tc_{block['index']}"735 for extra_key in ("status", "namespace"):736 if extra_key in block:737 if "extras" not in tool_call_block:738 tool_call_block["extras"] = {}739 tool_call_block["extras"][extra_key] = block[extra_key]740 yield tool_call_block741742 elif block_type == "web_search_call":743 web_search_call = {744 "type": "server_tool_call",745 "name": "web_search",746 "args": {},747 "id": block["id"],748 }749 if "index" in block:750 web_search_call["index"] = f"lc_wsc_{block['index']}"751752 sources: dict[str, Any] | None = None753 if "action" in block and isinstance(block["action"], dict):754 if "sources" in block["action"]:755 sources = block["action"]["sources"]756 web_search_call["args"] = {757 k: v for k, v in block["action"].items() if k != "sources"758 }759 for key in block:760 if key not in {"type", "id", "action", "status", "index"}:761 web_search_call[key] = block[key]762763 yield cast("types.ServerToolCall", web_search_call)764765 # If .content already has web_search_result, don't add766 if not any(767 isinstance(other_block, dict)768 and other_block.get("type") == "web_search_result"769 and other_block.get("id") == block["id"]770 for other_block in message.content771 ):772 web_search_result = {773 "type": "server_tool_result",774 "tool_call_id": block["id"],775 }776 if sources:777 web_search_result["output"] = {"sources": sources}778779 status = block.get("status")780 if status == "failed":781 web_search_result["status"] = "error"782 elif status == "completed":783 web_search_result["status"] = "success"784 elif status:785 web_search_result["extras"] = {"status": status}786 if "index" in block and isinstance(block["index"], int):787 web_search_result["index"] = f"lc_wsr_{block['index'] + 1}"788 yield cast("types.ServerToolResult", web_search_result)789790 elif block_type == "file_search_call":791 file_search_call = {792 "type": "server_tool_call",793 "name": "file_search",794 "id": block["id"],795 "args": {"queries": block.get("queries", [])},796 }797 if "index" in block:798 file_search_call["index"] = f"lc_fsc_{block['index']}"799800 for key in block:801 if key not in {802 "type",803 "id",804 "queries",805 "results",806 "status",807 "index",808 }:809 file_search_call[key] = block[key]810811 yield cast("types.ServerToolCall", file_search_call)812813 file_search_result = {814 "type": "server_tool_result",815 "tool_call_id": block["id"],816 }817 if file_search_output := block.get("results"):818 file_search_result["output"] = file_search_output819820 status = block.get("status")821 if status == "failed":822 file_search_result["status"] = "error"823 elif status == "completed":824 file_search_result["status"] = "success"825 elif status:826 file_search_result["extras"] = {"status": status}827 if "index" in block and isinstance(block["index"], int):828 file_search_result["index"] = f"lc_fsr_{block['index'] + 1}"829 yield cast("types.ServerToolResult", file_search_result)830831 elif block_type == "code_interpreter_call":832 code_interpreter_call = {833 "type": "server_tool_call",834 "name": "code_interpreter",835 "id": block["id"],836 }837 if "code" in block:838 code_interpreter_call["args"] = {"code": block["code"]}839 if "index" in block:840 code_interpreter_call["index"] = f"lc_cic_{block['index']}"841 known_fields = {842 "type",843 "id",844 "outputs",845 "status",846 "code",847 "extras",848 "index",849 }850 for key in block:851 if key not in known_fields:852 if "extras" not in code_interpreter_call:853 code_interpreter_call["extras"] = {}854 code_interpreter_call["extras"][key] = block[key]855856 code_interpreter_result = {857 "type": "server_tool_result",858 "tool_call_id": block["id"],859 }860 if "outputs" in block:861 code_interpreter_result["output"] = block["outputs"]862863 status = block.get("status")864 if status == "failed":865 code_interpreter_result["status"] = "error"866 elif status == "completed":867 code_interpreter_result["status"] = "success"868 elif status:869 code_interpreter_result["extras"] = {"status": status}870 if "index" in block and isinstance(block["index"], int):871 code_interpreter_result["index"] = f"lc_cir_{block['index'] + 1}"872873 yield cast("types.ServerToolCall", code_interpreter_call)874 yield cast("types.ServerToolResult", code_interpreter_result)875876 elif block_type == "mcp_call":877 mcp_call = {878 "type": "server_tool_call",879 "name": "remote_mcp",880 "id": block["id"],881 }882 if (arguments := block.get("arguments")) and isinstance(arguments, str):883 try:884 mcp_call["args"] = json.loads(block["arguments"])885 except json.JSONDecodeError:886 mcp_call["extras"] = {"arguments": arguments}887 if "name" in block:888 if "extras" not in mcp_call:889 mcp_call["extras"] = {}890 mcp_call["extras"]["tool_name"] = block["name"]891 if "server_label" in block:892 if "extras" not in mcp_call:893 mcp_call["extras"] = {}894 mcp_call["extras"]["server_label"] = block["server_label"]895 if "index" in block:896 mcp_call["index"] = f"lc_mcp_{block['index']}"897 known_fields = {898 "type",899 "id",900 "arguments",901 "name",902 "server_label",903 "output",904 "error",905 "extras",906 "index",907 }908 for key in block:909 if key not in known_fields:910 if "extras" not in mcp_call:911 mcp_call["extras"] = {}912 mcp_call["extras"][key] = block[key]913914 yield cast("types.ServerToolCall", mcp_call)915916 mcp_result = {917 "type": "server_tool_result",918 "tool_call_id": block["id"],919 }920 if mcp_output := block.get("output"):921 mcp_result["output"] = mcp_output922923 error = block.get("error")924 if error:925 if "extras" not in mcp_result:926 mcp_result["extras"] = {}927 mcp_result["extras"]["error"] = error928 mcp_result["status"] = "error"929 else:930 mcp_result["status"] = "success"931932 if "index" in block and isinstance(block["index"], int):933 mcp_result["index"] = f"lc_mcpr_{block['index'] + 1}"934 yield cast("types.ServerToolResult", mcp_result)935936 elif block_type == "mcp_list_tools":937 mcp_list_tools_call = {938 "type": "server_tool_call",939 "name": "mcp_list_tools",940 "args": {},941 "id": block["id"],942 }943 if "server_label" in block:944 mcp_list_tools_call["extras"] = {}945 mcp_list_tools_call["extras"]["server_label"] = block[946 "server_label"947 ]948 if "index" in block:949 mcp_list_tools_call["index"] = f"lc_mlt_{block['index']}"950 known_fields = {951 "type",952 "id",953 "name",954 "server_label",955 "tools",956 "error",957 "extras",958 "index",959 }960 for key in block:961 if key not in known_fields:962 if "extras" not in mcp_list_tools_call:963 mcp_list_tools_call["extras"] = {}964 mcp_list_tools_call["extras"][key] = block[key]965966 yield cast("types.ServerToolCall", mcp_list_tools_call)967968 mcp_list_tools_result = {969 "type": "server_tool_result",970 "tool_call_id": block["id"],971 }972 if mcp_output := block.get("tools"):973 mcp_list_tools_result["output"] = mcp_output974975 error = block.get("error")976 if error:977 if "extras" not in mcp_list_tools_result:978 mcp_list_tools_result["extras"] = {}979 mcp_list_tools_result["extras"]["error"] = error980 mcp_list_tools_result["status"] = "error"981 else:982 mcp_list_tools_result["status"] = "success"983984 if "index" in block and isinstance(block["index"], int):985 mcp_list_tools_result["index"] = f"lc_mltr_{block['index'] + 1}"986 yield cast("types.ServerToolResult", mcp_list_tools_result)987988 elif (989 block_type == "tool_search_call" and block.get("execution") == "server"990 ):991 tool_search_call: dict[str, Any] = {992 "type": "server_tool_call",993 "name": "tool_search",994 "id": block["id"],995 "args": block.get("arguments", {}),996 }997 if "index" in block:998 tool_search_call["index"] = f"lc_tsc_{block['index']}"999 extras: dict[str, Any] = {}1000 known = {"type", "id", "arguments", "index"}1001 for key in block:1002 if key not in known:1003 extras[key] = block[key]1004 if extras:1005 tool_search_call["extras"] = extras1006 yield cast("types.ServerToolCall", tool_search_call)10071008 elif (1009 block_type == "tool_search_output"1010 and block.get("execution") == "server"1011 ):1012 tool_search_output: dict[str, Any] = {1013 "type": "server_tool_result",1014 "tool_call_id": block["id"],1015 "output": {"tools": block.get("tools", [])},1016 }1017 status = block.get("status")1018 if status == "failed":1019 tool_search_output["status"] = "error"1020 elif status == "completed":1021 tool_search_output["status"] = "success"1022 if "index" in block and isinstance(block["index"], int):1023 tool_search_output["index"] = f"lc_tso_{block['index']}"1024 extras_out: dict[str, Any] = {"name": "tool_search"}1025 known_out = {"type", "id", "status", "tools", "index"}1026 for key in block:1027 if key not in known_out:1028 extras_out[key] = block[key]1029 if extras_out:1030 tool_search_output["extras"] = extras_out1031 yield cast("types.ServerToolResult", tool_search_output)10321033 elif block_type in types.KNOWN_BLOCK_TYPES:1034 yield cast("types.ContentBlock", block)1035 else:1036 new_block = {"type": "non_standard", "value": block}1037 if "index" in new_block["value"]:1038 new_block["index"] = f"lc_ns_{new_block['value'].pop('index')}"1039 yield cast("types.NonStandardContentBlock", new_block)10401041 return list(_iter_blocks())104210431044def translate_content(message: AIMessage) -> list[types.ContentBlock]:1045 """Derive standard content blocks from a message with OpenAI content.10461047 Args:1048 message: The message to translate.10491050 Returns:1051 The derived content blocks.1052 """1053 if isinstance(message.content, str):1054 return _convert_to_v1_from_chat_completions(message)1055 message = _convert_from_v03_ai_message(message)1056 return _convert_to_v1_from_responses(message)105710581059def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:1060 """Derive standard content blocks from a message chunk with OpenAI content.10611062 Args:1063 message: The message chunk to translate.10641065 Returns:1066 The derived content blocks.1067 """1068 if isinstance(message.content, str):1069 return _convert_to_v1_from_chat_completions_chunk(message)1070 message = _convert_from_v03_ai_message(message) # type: ignore[assignment]1071 return _convert_to_v1_from_responses(message)107210731074def _register_openai_translator() -> None:1075 """Register the OpenAI translator with the central registry.10761077 Run automatically when the module is imported.1078 """1079 from langchain_core.messages.block_translators import ( # noqa: PLC04151080 register_translator,1081 )10821083 register_translator("openai", translate_content, translate_content_chunk)108410851086_register_openai_translator()
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.