libs/core/langchain_core/messages/block_translators/openai.py PYTHON 1,090 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[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()

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.