Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
948f237
feat: implement Files API client and related functionality
vvlrff Apr 15, 2026
cd3dd10
Merge branch 'main' into file-api
vvlrff Apr 15, 2026
fa1c071
feat: add error handling for unsupported FileIdInput in convert_messa…
vvlrff Apr 15, 2026
5303ef5
refactor: reorder imports and remove unused ones in agent.py
vvlrff Apr 15, 2026
3b8f2e4
fix: update create_files_client return type to NoReturn in TrackingCo…
vvlrff Apr 15, 2026
3dab50e
fix: update create_files_client return type to NoReturn in config files
vvlrff Apr 15, 2026
d0d59b9
Merge branch 'main' into file-api
vvlrff Apr 15, 2026
c89ce74
fix: update create_files_client return type from NoReturn to None in …
vvlrff Apr 15, 2026
e9a9e9b
Merge remote-tracking branch 'upstream/main' into file-api
vvlrff Apr 17, 2026
4fd2a34
Merge branch 'main' into file-api
vvlrff Apr 20, 2026
e18f12f
Merge branch 'main' into file-api
vvlrff Apr 21, 2026
084b8e3
Merge branch 'main' into file-api
vvlrff Apr 22, 2026
a5abea0
feat: enhance file handling and validation for UploadedFile across mu…
vvlrff Apr 22, 2026
06fb4e0
Merge branch 'main' into file-api
vvlrff Apr 23, 2026
8f13f67
Merge branch 'main' into file-api
vvlrff Apr 23, 2026
9276857
Add purpose parameter to file creation
Lancetnik Apr 23, 2026
802fff0
refactor: introduce providers enum
Lancetnik Apr 23, 2026
d637391
docs(beta): documenta FileAPI section
Lancetnik Apr 23, 2026
e818176
fix: correct errors
Lancetnik Apr 23, 2026
95fe358
refactor: update GeminiFilesClient to use async methods and improve u…
vvlrff Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions autogen/beta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .agent import Agent, AgentReply
from .annotations import Context, Inject, Variable
from .events import AudioInput, BinaryInput, DataInput, DocumentInput, ImageInput, TextInput, VideoInput
from .files import FilesAPI
from .observer import observer
from .response import PromptedSchema, ResponseSchema, response_schema
from .spec import AgentSpec
Expand All @@ -23,6 +24,7 @@
"DataInput",
"Depends",
"DocumentInput",
"FilesAPI",
"ImageInput",
"Inject",
"MemoryStream",
Expand Down
5 changes: 2 additions & 3 deletions autogen/beta/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
from pydantic import ValidationError
from typing_extensions import TypeVar as TypeVar313

from autogen.beta.events import BinaryResult

from .annotations import Context
from .config import LLMClient, ModelConfig
from .events import (
BaseEvent,
BinaryResult,
HumanInputRequest,
Input,
ModelRequest,
Expand Down Expand Up @@ -125,7 +124,7 @@ def body(self) -> str | None:

@property
def files(self) -> list[BinaryResult]:
"""Images generated by the model in this turn (decoded bytes)."""
"""Files generated by the model in this turn (decoded bytes)."""
return self.response.files

@property
Expand Down
9 changes: 9 additions & 0 deletions autogen/beta/config/anthropic/anthropic_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
ToolCallEvent,
ToolCallsEvent,
)
from autogen.beta.events.input_events import FileIdInput, ModelRequest
from autogen.beta.response import ResponseProto
from autogen.beta.tools.builtin.code_execution import CodeExecutionToolSchema
from autogen.beta.tools.builtin.skills import SkillsToolSchema
Expand Down Expand Up @@ -142,6 +143,14 @@ async def __call__(
create_kwargs.setdefault("extra_headers", {})
create_kwargs["extra_headers"]["anthropic-beta"] = ",".join(sorted(existing_betas))

# Files API beta: required when messages contain file_id references
if any(isinstance(inp, FileIdInput) for msg in messages if isinstance(msg, ModelRequest) for inp in msg.parts):
existing_betas = set((create_kwargs.get("extra_headers") or {}).get("anthropic-beta", "").split(","))
existing_betas.discard("")
existing_betas.add("files-api-2025-04-14")
create_kwargs.setdefault("extra_headers", {})
create_kwargs["extra_headers"]["anthropic-beta"] = ",".join(sorted(existing_betas))

max_continuations = 5

if self._streaming:
Expand Down
4 changes: 4 additions & 0 deletions autogen/beta/config/anthropic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from autogen.beta.config.config import ModelConfig

from .anthropic_client import AnthropicClient, CreateOptions
from .files import AnthropicFilesClient


class AnthropicConfigOverrides(TypedDict, total=False):
Expand Down Expand Up @@ -85,3 +86,6 @@ def create(self) -> AnthropicClient:
create_options=options,
prompt_caching=self.prompt_caching,
)

def create_files_client(self) -> AnthropicFilesClient:
return AnthropicFilesClient(self)
70 changes: 70 additions & 0 deletions autogen/beta/config/anthropic/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

import mimetypes
from io import BytesIO
from typing import TYPE_CHECKING

from anthropic import AsyncAnthropic

from autogen.beta.files.types import FileContent, FileProvider, UploadedFile

if TYPE_CHECKING:
from autogen.beta.config.anthropic.config import AnthropicConfig


class AnthropicFilesClient:
"""Files API client for Anthropic."""

__slots__ = ("_client",)

def __init__(self, config: "AnthropicConfig") -> None:
self._client = AsyncAnthropic(
api_key=config.api_key,
base_url=config.base_url,
timeout=config.timeout if config.timeout is not None else 600.0,
max_retries=config.max_retries,
default_headers=config.default_headers,
http_client=config.http_client,
)

async def upload(self, data: bytes, filename: str, purpose: str | None = None) -> UploadedFile:
mime_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
result = await self._client.beta.files.upload(
file=(filename, BytesIO(data), mime_type),
)
return UploadedFile(
file_id=result.id,
filename=result.filename if hasattr(result, "filename") else filename,
provider=FileProvider.ANTHROPIC,
bytes_count=result.size_bytes if hasattr(result, "size_bytes") else None,
purpose=purpose,
created_at=result.created_at if hasattr(result, "created_at") else None,
)

async def read(self, file_id: str) -> FileContent:
response = await self._client.beta.files.download(file_id)
metadata = await self._client.beta.files.retrieve_metadata(file_id)
return FileContent(
name=metadata.filename if hasattr(metadata, "filename") else None,
data=response.content if hasattr(response, "content") else bytes(response),
media_type=metadata.mime_type if hasattr(metadata, "mime_type") else None,
)

async def list(self) -> list[UploadedFile]:
result = await self._client.beta.files.list()
return [
UploadedFile(
file_id=f.id,
filename=f.filename if hasattr(f, "filename") else None,
provider=FileProvider.ANTHROPIC,
bytes_count=f.size_bytes if hasattr(f, "size_bytes") else None,
purpose=None,
created_at=f.created_at if hasattr(f, "created_at") else None,
)
for f in result.data
]

async def delete(self, file_id: str) -> None:
await self._client.beta.files.delete(file_id)
7 changes: 7 additions & 0 deletions autogen/beta/config/anthropic/mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from autogen.beta.events.types import Usage
from autogen.beta.exceptions import UnsupportedInputError, UnsupportedToolError
from autogen.beta.files.types import FileProvider
from autogen.beta.response import ResponseProto
from autogen.beta.tools.builtin.code_execution import CodeExecutionToolSchema
from autogen.beta.tools.builtin.mcp_server import MCPServerToolSchema
Expand Down Expand Up @@ -261,6 +262,12 @@ def convert_messages(
content_parts.append({"type": "text", "text": serializer.encode(inp.data).decode()})

elif isinstance(inp, FileIdInput):
if (provider := getattr(inp, "provider", None)) and provider is not FileProvider.ANTHROPIC:
raise UnsupportedInputError(
f"file uploaded via '{provider.value}' cannot be used with '{FileProvider.ANTHROPIC.value}'",
"anthropic",
)

block_type = _file_id_block_type(inp.filename)
content_parts.append({"type": block_type, "source": {"type": "file", "file_id": inp.file_id}})

Expand Down
8 changes: 7 additions & 1 deletion autogen/beta/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
#
# SPDX-License-Identifier: Apache-2.0

from typing import Protocol
from typing import TYPE_CHECKING, Protocol

from typing_extensions import Self

from .client import LLMClient

if TYPE_CHECKING:
from autogen.beta.files.protocol import FilesClient


class ModelConfig(Protocol):
def copy(self) -> Self: ...

def create(self) -> LLMClient: ...

def create_files_client(self) -> "FilesClient":
raise NotImplementedError(f"{type(self).__name__} does not support Files API.")
3 changes: 3 additions & 0 deletions autogen/beta/config/dashscope/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ def create(self) -> DashScopeClient:
streaming=self.streaming,
create_options=options,
)

def create_files_client(self) -> None:
raise NotImplementedError(f"{type(self).__name__} does not support Files API.")
4 changes: 4 additions & 0 deletions autogen/beta/config/gemini/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from autogen.beta.config.config import ModelConfig

from .files import GeminiFilesClient
from .gemini_client import CreateConfig, GeminiClient


Expand Down Expand Up @@ -91,6 +92,9 @@ def create(self) -> GeminiClient:
cached_content=self.cached_content,
)

def create_files_client(self) -> GeminiFilesClient:
return GeminiFilesClient(self)


@dataclass(slots=True)
class VertexAIConfig(GeminiBaseConfig, ModelConfig):
Expand Down
68 changes: 68 additions & 0 deletions autogen/beta/config/gemini/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

import io
import mimetypes
from typing import TYPE_CHECKING

from google import genai

from autogen.beta.files.types import FileContent, FileProvider, UploadedFile

if TYPE_CHECKING:
from autogen.beta.config.gemini.config import GeminiConfig


class GeminiFilesClient:
"""Files API client for Google Gemini."""

__slots__ = ("_client",)

def __init__(self, config: "GeminiConfig") -> None:
self._client = genai.Client(api_key=config.api_key)

async def upload(self, data: bytes, filename: str, purpose: str | None = None) -> UploadedFile:
mime_type, _ = mimetypes.guess_type(filename)
config = {"display_name": filename, "mime_type": mime_type or "application/octet-stream"}
result = await self._client.aio.files.upload(file=io.BytesIO(data), config=config)

return UploadedFile(
file_id=result.name,
filename=filename,
provider=FileProvider.GEMINI,
bytes_count=result.size_bytes if hasattr(result, "size_bytes") else len(data),
purpose=purpose,
created_at=str(result.create_time) if hasattr(result, "create_time") else None,
)

async def read(self, file_id: str) -> FileContent:
file_info = await self._client.aio.files.get(name=file_id)
if file_info.download_uri is None:
raise NotImplementedError(
"Gemini does not allow downloading user-uploaded files. "
"Only model-generated files with a download_uri can be downloaded."
)
data = await self._client.aio.files.download(file=file_info)
return FileContent(
name=file_info.display_name if file_info.display_name else None,
data=data,
media_type=file_info.mime_type if file_info.mime_type else None,
)

async def list(self) -> list[UploadedFile]:
pager = await self._client.aio.files.list()
return [
UploadedFile(
file_id=f.name,
filename=f.display_name if f.display_name else None,
provider=FileProvider.GEMINI,
bytes_count=f.size_bytes if f.size_bytes else None,
purpose=None,
created_at=str(f.create_time) if hasattr(f, "create_time") else None,
)
for f in pager.page
]

async def delete(self, file_id: str) -> None:
await self._client.aio.files.delete(name=file_id)
11 changes: 11 additions & 0 deletions autogen/beta/config/gemini/mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
from autogen.beta.events.input_events import (
BinaryInput,
DataInput,
FileIdInput,
UrlInput,
)
from autogen.beta.events.types import Usage
from autogen.beta.exceptions import UnsupportedInputError, UnsupportedToolError
from autogen.beta.files.types import FileProvider
from autogen.beta.response import ResponseProto
from autogen.beta.tools.builtin.code_execution import CodeExecutionToolSchema
from autogen.beta.tools.builtin.skills import SkillsToolSchema
Expand Down Expand Up @@ -224,6 +226,15 @@ def convert_messages(
else:
parts.append(types.Part(file_data=types.FileData(file_uri=inp.url)))

elif isinstance(inp, FileIdInput):
if (provider := getattr(inp, "provider", None)) and provider is not FileProvider.GEMINI:
raise UnsupportedInputError(
f"file uploaded via '{provider.value}' cannot be used with '{FileProvider.GEMINI.value}'",
"gemini",
)
file_uri = f"https://generativelanguage.googleapis.com/v1beta/{inp.file_id}"
parts.append(types.Part(file_data=types.FileData(file_uri=file_uri)))

elif isinstance(inp, BinaryInput):
part = types.Part.from_bytes(data=inp.data, mime_type=inp.media_type)
_apply_vendor_metadata(part, inp.vendor_metadata)
Expand Down
3 changes: 3 additions & 0 deletions autogen/beta/config/ollama/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ def create(self) -> OllamaClient:
streaming=self.streaming,
create_options=options,
)

def create_files_client(self) -> None:
raise NotImplementedError(f"{type(self).__name__} does not support Files API.")
7 changes: 7 additions & 0 deletions autogen/beta/config/openai/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from autogen.beta.config.config import ModelConfig

from .files import OpenAIFilesClient
from .openai_client import CreateOptions, OpenAIClient, ReasoningEffort
from .openai_responses_client import CreateOptions as ResponseCreateOptions
from .openai_responses_client import OpenAIResponsesClient
Expand Down Expand Up @@ -150,6 +151,9 @@ def create(self) -> OpenAIClient:
create_options=options,
)

def create_files_client(self) -> OpenAIFilesClient:
return OpenAIFilesClient(self)


class OpenAIResponsesConfigOverrides(TypedDict, total=False):
model: ChatModel | str
Expand Down Expand Up @@ -239,3 +243,6 @@ def create(self) -> OpenAIResponsesClient:
http_client=self.http_client,
create_options=options,
)

def create_files_client(self) -> OpenAIFilesClient:
return OpenAIFilesClient(self)
Loading
Loading