2025, Oct 16 11:16

Как исправить ошибку Cannot instantiate typing.Union в связке OpenAI Agents и Ollama

Почему OpenAI Agents + Ollama падает с TypeError Cannot instantiate typing.Union, и как исправить: зафиксировать openai<1.99.0. Пояснения и рабочий пример.

Вызов функций в OpenAI Agents SDK, подключённом к бэкенду Ollama, может неожиданно падать с TypeError из typing.Union. Если агент рушится на простом вызове инструмента, а трассировка завершается сообщением «TypeError: Cannot instantiate typing.Union», скорее всего, вы столкнулись с несовместимостью между кодом агентов и актуальной версией пакета openai.

Минимальная конфигурация, воспроизводящая сбой

Ниже приведён пример, который подключает OpenAIChatCompletionsModel к HTTP-эндпоинту Ollama и публикует простой инструмент-функцию. Логика проста: создать клиент, зарегистрировать инструмент и попросить агента посчитать 2 + 2.

from dotenv import load_dotenv
from agents import Agent, Runner, AsyncOpenAI, OpenAIChatCompletionsModel, function_tool
import asyncio
load_dotenv(override=True)
host_url = "http://localhost:11434/v1"
api_token = "dummy"
model_id = "qwen3:4b"
@function_tool
def add_numbers(x: int, y: int) -> int:
    """Send two integers and return their sum"""
    print(f"Adding {x} and {y}")
    return x + y
async def entrypoint():
    async_client = AsyncOpenAI(base_url=host_url, api_key=api_token)
    chat_model = OpenAIChatCompletionsModel(model=model_id, openai_client=async_client)
    base_agent = Agent(
        name="basic_worker",
        instructions="You are a simple agent that can perform basic tasks",
        tools=[add_numbers],
        model=chat_model,
    )
    result = await Runner.run(base_agent, "What is 2 + 2?")
    print(result)
asyncio.run(entrypoint())

На затронутых конфигурациях выполнение завершается трассировкой, оканчивающейся сообщением:

TypeError: Cannot instantiate typing.Union

Что именно ломается и почему

Проблема возникает из-за того, как код агентов пытается собрать сообщение вызова инструмента. Внутри он формирует ChatCompletionMessageToolCallParam примерно так:

new_tool_call = ChatCompletionMessageToolCallParam(
    id=fs["id"],
    type="function",
    function={
        "name": "file_search_call",
        "arguments": json.dumps({
            "queries": fs.get("queries", []),
            "status": fs.get("status"),
        }),
    },
)

Однако ChatCompletionMessageToolCallParam объявлён как псевдоним типа Union из двух моделей pydantic:

ChatCompletionMessageToolCallParam: TypeAlias = Union[
    ChatCompletionMessageFunctionToolCallParam,
    ChatCompletionMessageCustomToolCallParam,
]

Пытаться инстанцировать typing.Union как класс в Python недопустимо. Union — это не конкретный тип, который можно вызвать; это типовое выражение, указывающее, что значение может быть одним из нескольких типов. Чтобы увидеть, почему это падает, тот же некорректный приём в изоляции:

from typing import Union
UType = Union[int, str]
UType("hello")

Это стабильно приводит к:

TypeError: Cannot instantiate typing.Union

В итоге путь в модуле agents, который формирует ChatCompletionMessageToolCallParam таким образом, не подходит для последних версий пакета openai. Несовместимость подтверждена и связана с версиями.

Исправление

Зафиксируйте пакет openai на версии ниже 1.99.0. Согласно связанному обсуждению, даунгрейд устраняет ошибку.

Если вы управляете зависимостями через constraints-файл или аналогичный механизм, укажите ограничения так:

openai<1.99.0
openai-agents>=0.0.15

Изменений в прикладном коде не требуется; после фиксации версии openai путь вызова инструмента перестаёт пытаться инстанцировать Union, и выполнение идёт как ожидалось.

Рабочий код после фиксации зависимости

С установленным openai<1.99.0 тот же пример запускается успешно:

from dotenv import load_dotenv
from agents import Agent, Runner, AsyncOpenAI, OpenAIChatCompletionsModel, function_tool
import asyncio
load_dotenv(override=True)
host_url = "http://localhost:11434/v1"
api_token = "dummy"
model_id = "qwen3:4b"
@function_tool
def add_numbers(x: int, y: int) -> int:
    """Send two integers and return their sum"""
    print(f"Adding {x} and {y}")
    return x + y
async def entrypoint():
    async_client = AsyncOpenAI(base_url=host_url, api_key=api_token)
    chat_model = OpenAIChatCompletionsModel(model=model_id, openai_client=async_client)
    base_agent = Agent(
        name="basic_worker",
        instructions="You are a simple agent that can perform basic tasks",
        tools=[add_numbers],
        model=chat_model,
    )
    result = await Runner.run(base_agent, "What is 2 + 2?")
    print(result)
asyncio.run(entrypoint())

Почему это важно для вашего стека

Типизированные SDK быстро меняются, и небольшие правки в определениях моделей могут ломать предположения в более высокоуровневых обёртках. Здесь псевдоним типа на базе Union в пакете openai конфликтует с кодом, который обращается с ним как с конкретным классом. Контроль совместимости версий помогает избежать труднодиагностируемых сбоев в продакшене, особенно когда вы объединяете несколько слоёв — вроде OpenAI Agents SDK и бэкенда Ollama.

Выводы

Если интеграция OpenAI Agents + Ollama падает с «Cannot instantiate typing.Union», быстрым и подтверждённым решением будет зафиксировать openai на версии ниже 1.99.0 при использовании openai-agents≥0.0.15. Это убирает некорректную попытку инстанцировать Union и возвращает корректную обработку вызова функций. Следите за обсуждением апстрима и пересмотрите фиксацию, когда несовместимость будет устранена.

Материал основан на вопросе на StackOverflow от Dileep17 и ответе Tom McLean.