libs/core/langchain_core/messages/block_translators/openai.py PYTHON 1,087 lines View on github.com → Search inside
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()

Code quality findings 28

Ensure functions have docstrings for documentation
missing-docstring
def convert_to_openai_data_block(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(converted_block, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(chunk.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.content, list):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(message.content, list)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(message.id, str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
and isinstance(response_id, str)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, dict) and block.get("type") == "text":
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.id, str) and message.id.startswith("msg_"):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(message, AIMessageChunk)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(block, dict) and (key := block.get("type")) and key in buckets:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if (block["type"] == "image_url") and isinstance(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(raw_block, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(message, AIMessageChunk)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "action" in block and isinstance(block["action"], dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(other_block, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if (arguments := block.get("arguments")) and isinstance(arguments, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if "index" in block and isinstance(block["index"], int):
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
return list(_iter_blocks())
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.content, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message.content, str):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.