Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ba35ea8
feat: Agent Config and API Interoperability
priyansh4320 Dec 24, 2025
048de72
Merge branch 'main' into agent-level-responseformat-on-bedrock-client
priyansh4320 Dec 24, 2025
ccfa815
fix: core tests
priyansh4320 Dec 25, 2025
86a45b2
Merge branch 'main' into agent-level-responseformat-on-bedrock-client
priyansh4320 Dec 25, 2025
2bdfe12
feat: update ollama client with agent response format
priyansh4320 Dec 26, 2025
1ccf034
feat: update resposnes with agent config
priyansh4320 Dec 26, 2025
2d777ee
feat: update gemini with agent config
priyansh4320 Dec 26, 2025
e2f712b
feat: update cohere client with agent config
priyansh4320 Dec 26, 2025
ce1683c
fix: coditional logic
priyansh4320 Dec 26, 2025
b207974
feat: update agent config on anthropic
priyansh4320 Dec 26, 2025
5a7e177
fix: param.get(,)
priyansh4320 Dec 26, 2025
751fb04
fix: anthropic agent_config
priyansh4320 Dec 26, 2025
96ac71f
test: bedrock agent config
priyansh4320 Dec 26, 2025
fa2f448
test: ollama agent config
priyansh4320 Dec 26, 2025
f352075
test: gemini agent config
priyansh4320 Dec 26, 2025
4b99101
test: anthropic agent config
priyansh4320 Dec 26, 2025
48bfc82
fix: scaling and memory writes
priyansh4320 Dec 27, 2025
b130ce8
fix: anthropic test
priyansh4320 Dec 27, 2025
b8a62c3
fix: gemini tests
priyansh4320 Dec 27, 2025
ca2db1f
fix: ollama tests
priyansh4320 Dec 27, 2025
8335c2e
test: agent config in OAI client
priyansh4320 Dec 27, 2025
7ae4baa
fix: pre-commit
priyansh4320 Dec 27, 2025
fefbe4c
fix: update param
priyansh4320 Dec 28, 2025
e96f03c
Merge branch 'main' into agent-level-responseformat-on-bedrock-client
priyansh4320 Dec 28, 2025
aa42c01
feat: add basic interop
priyansh4320 Dec 28, 2025
1f51d35
fix: agent event test
priyansh4320 Dec 28, 2025
dbb482f
ducumentation: add agent config guide
priyansh4320 Dec 28, 2025
9b8a5ed
fix: add docstrings
priyansh4320 Dec 28, 2025
992e7e5
test: inteoperate_llm_config
priyansh4320 Jan 5, 2026
55d5247
documentation: notebook example
priyansh4320 Jan 5, 2026
327cd85
feat: make agentconfig groupchat compatible
priyansh4320 Jan 5, 2026
56c19ef
fix: notebook content
priyansh4320 Jan 5, 2026
24d0c9d
test: agentconfig integration OAI
priyansh4320 Jan 5, 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
3 changes: 2 additions & 1 deletion autogen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
SenderRequiredError,
UndefinedNextAgentError,
)
from .llm_config import LLMConfig, ModelClient
from .llm_config import AgentConfig, LLMConfig, ModelClient
from .oai import (
Cache,
OpenAIWrapper,
Expand All @@ -54,6 +54,7 @@
"DEFAULT_MODEL",
"FAST_MODEL",
"Agent",
"AgentConfig",
"AgentNameConflictError",
"AssistantAgent",
"Cache",
Expand Down
25 changes: 21 additions & 4 deletions autogen/agentchat/conversable_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
from ..io.base import AsyncIOStreamProtocol, AsyncInputStream, IOStream, IOStreamProtocol, InputStream
from ..io.run_response import AsyncRunResponse, AsyncRunResponseProtocol, RunResponse, RunResponseProtocol
from ..io.thread_io_stream import AsyncThreadIOStream, ThreadIOStream
from ..llm_config import LLMConfig
from ..llm_config import AgentConfig, LLMConfig
from ..llm_config.client import ModelClient
from ..oai.client import OpenAIWrapper
from ..runtime_logging import log_event, log_function_use, log_new_agent, logging_enabled
Expand All @@ -86,7 +86,7 @@
from .group.on_context_condition import OnContextCondition
__all__ = ("ConversableAgent",)

logger = logging.getLogger(__name__)
logger = logging.getLogger("ag2.event.processor")

F = TypeVar("F", bound=Callable[..., Any])

Expand Down Expand Up @@ -162,6 +162,7 @@ def __init__(
silent: bool | None = None,
context_variables: Optional["ContextVariables"] = None,
functions: list[Callable[..., Any]] | Callable[..., Any] = None,
agent_config: AgentConfig | None = None,
update_agent_state_before_reply: list[Callable | UpdateSystemMessage]
| Callable
| UpdateSystemMessage
Expand Down Expand Up @@ -223,10 +224,11 @@ def __init__(
15) update_agent_state_before_reply (List[Callable[..., Any]]): A list of functions, including UpdateSystemMessage's, called to update the agent before it replies.\n
16) handoffs (Handoffs): Handoffs object containing all handoff transition conditions.\n
"""
# self.response_format = response_format if response_format is not None else None
self.agent_config = agent_config if agent_config is not None else None
self.handoffs = handoffs if handoffs is not None else Handoffs()
self.input_guardrails: list[Guardrail] = []
self.output_guardrails: list[Guardrail] = []

# we change code_execution_config below and we have to make sure we don't change the input
# in case of UserProxyAgent, without this we could even change the default value {}
code_execution_config = (
Expand Down Expand Up @@ -258,7 +260,11 @@ def __init__(
"Please implement __deepcopy__ method for each value class in llm_config to support deepcopy."
" Refer to the docs for more details: https://docs.ag2.ai/docs/user-guide/advanced-concepts/llm-configuration-deep-dive/#adding-http-client-in-llm_config-for-proxy"
) from e

llm_config = (
self._interoperate_llm_config(llm_config if isinstance(llm_config, LLMConfig) else None)
if agent_config is not None and agent_config.api_type is not None
else llm_config
)
self.llm_config = self._validate_llm_config(llm_config)
self.client = self._create_client(self.llm_config)
self._validate_name(name)
Expand Down Expand Up @@ -437,6 +443,16 @@ def _add_single_function(self, func: Callable, name: str | None = None, descript
# Register the function
self.register_for_llm(name=name, description=description, silent_override=True)(func)

def _interoperate_llm_config(self, llm_config: LLMConfig) -> LLMConfig | None:
"""Interoperate the llm_config with the agent_config"""
if self.agent_config is not None and self.agent_config.api_type is not None:
for config in llm_config.config_list:
if isinstance(config, dict):
config["api_type"] = self.agent_config.api_type
elif hasattr(config, "api_type"):
config.api_type = self.agent_config.api_type
return llm_config

def _register_update_agent_state_before_reply(
self, functions: list[Callable[..., Any]] | Callable[..., Any] | None
):
Expand Down Expand Up @@ -2148,6 +2164,7 @@ def generate_oai_reply(
**kwargs: Any,
) -> tuple[bool, str | dict[str, Any] | None]:
"""Generate a reply using autogen.oai."""
# logger.info(f"Messages: {self.client}")
client = self.client if config is None else config
if client is None:
return False, None
Expand Down
3 changes: 2 additions & 1 deletion autogen/llm_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
# SPDX-License-Identifier: Apache-2.0

from .client import ModelClient
from .config import LLMConfig
from .config import AgentConfig, LLMConfig

__all__ = (
"AgentConfig",
"LLMConfig",
"ModelClient",
)
11 changes: 11 additions & 0 deletions autogen/llm_config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ def default(cls) -> "LLMConfig":
ConfigItem: TypeAlias = LLMConfigEntry | ConfigEntries | dict[str, Any]


@export_module("autogen")
class AgentConfig(BaseModel):
response_format: str | dict[str, Any] | BaseModel | type[BaseModel] | None = None
api_type: Literal["openai", "responses"] | None = None

model_config = ConfigDict(extra="allow")

def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)


@export_module("autogen")
class LLMConfig(metaclass=MetaLLMConfig):
_current_llm_config: ContextVar["LLMConfig"] = ContextVar("current_llm_config")
Expand Down
16 changes: 16 additions & 0 deletions autogen/oai/agent_config_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from autogen.llm_config import AgentConfig


def agent_config_parser(agent_config: "AgentConfig") -> dict[str, Any]:
"""Parse the agent_config to a dictionary"""
_agent_config: dict[str, Any] = {}
if hasattr(agent_config, "response_format") and agent_config.response_format is not None:
_agent_config["response_format"] = agent_config.response_format
return _agent_config
13 changes: 11 additions & 2 deletions autogen/oai/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from ..code_utils import content_str
from ..import_utils import optional_import_block, require_optional_import
from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
from .agent_config_handler import agent_config_parser

logger = logging.getLogger(__name__)
from .client_utils import FormatterProtocol, validate_parameter
Expand Down Expand Up @@ -762,9 +763,17 @@ def create(self, params: dict[str, Any]) -> ChatCompletion:
Returns:
ChatCompletion object compatible with OpenAI format
"""
agent_config = params.pop("agent_config", None)
model = params.get("model")
response_format = params.get("response_format") or self._response_format

agent_config = agent_config_parser(agent_config) if agent_config is not None else None
logger.info(f"Agent config: {agent_config}")
response_format = (
agent_config.get("response_format")
if agent_config is not None
and "response_format" in agent_config
and agent_config.get("response_format") is not None
else params.get("response_format", self._response_format if self._response_format is not None else None)
)
# Route to appropriate implementation based on model and response_format
if response_format:
self._response_format = response_format
Expand Down
20 changes: 17 additions & 3 deletions autogen/oai/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import base64
import json
import logging
import os
import re
import time
Expand All @@ -44,13 +45,16 @@

from ..import_utils import optional_import_block, require_optional_import
from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
from .agent_config_handler import agent_config_parser
from .client_utils import validate_parameter
from .oai_models import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall, Choice, CompletionUsage

with optional_import_block():
import boto3
from botocore.config import Config

logger = logging.getLogger(__name__)


class BedrockEntryDict(LLMConfigEntryDict, total=False):
api_type: Literal["bedrock"]
Expand Down Expand Up @@ -421,15 +425,25 @@ def parse_params(self, params: BedrockEntryDict | dict[str, Any]) -> tuple[dict[
def create(self, params) -> ChatCompletion:
"""Run Amazon Bedrock inference and return AG2 response"""
# Set custom client class settings
agent_config = params.pop("agent_config", None)
agent_config = agent_config_parser(agent_config) if agent_config is not None else None
logger.info(f"Agent config: {agent_config}")
self.parse_custom_params(params)

# Parse the inference parameters
base_params, additional_params = self.parse_params(params)

# Handle response_format for structured outputs
has_response_format = self._response_format is not None
# Handle response_format for structured outputs, check if agent_config has a response_format else
has_response_format = (
agent_config.get("response_format")
if agent_config is not None
and "response_format" in agent_config
and agent_config.get("response_format") is not None
else params.get("response_format", self._response_format if self._response_format is not None else None)
)
if has_response_format:
structured_output_tool = self._create_structured_output_tool(self._response_format)
self._response_format = has_response_format
structured_output_tool = self._create_structured_output_tool(has_response_format)
# Merge with user tools if any
user_tools = params.get("tools", [])
tool_config = self._merge_tools_with_structured_output(user_tools, structured_output_tool)
Expand Down
6 changes: 6 additions & 0 deletions autogen/oai/cerebras.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from __future__ import annotations

import copy
import logging
import math
import os
import time
Expand All @@ -35,9 +36,11 @@

from ..import_utils import optional_import_block, require_optional_import
from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
from .agent_config_handler import agent_config_parser
from .client_utils import should_hide_tools, validate_parameter
from .oai_models import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall, Choice, CompletionUsage

logger = logging.getLogger(__name__)
with optional_import_block():
from cerebras.cloud.sdk import Cerebras, Stream

Expand Down Expand Up @@ -146,6 +149,9 @@ def parse_params(self, params: dict[str, Any]) -> dict[str, Any]:

@require_optional_import("cerebras", "cerebras")
def create(self, params: dict) -> ChatCompletion:
agent_config = params.pop("agent_config", None)
agent_config = agent_config_parser(agent_config) if agent_config is not None else None
logger.info(f"Agent config: {agent_config}")
messages = params.get("messages", [])

# Convert AG2 messages to Cerebras messages
Expand Down
54 changes: 44 additions & 10 deletions autogen/oai/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
from ..logger.logger_utils import get_current_ts
from ..runtime_logging import log_chat_completion, log_new_client, log_new_wrapper, logging_enabled
from .agent_config_handler import agent_config_parser
from .client_utils import FormatterProtocol, logging_formatter, merge_config_with_tools
from .openai_utils import OAI_PRICE1K, get_key, is_valid_api_key

Expand Down Expand Up @@ -200,6 +201,10 @@
_ch.setFormatter(logging_formatter)
logger.addHandler(_ch)

import logging

logger = logging.getLogger("ag2.event.processor")

LEGACY_DEFAULT_CACHE_SEED = 41
LEGACY_CACHE_DIR = ".cache"
OPEN_API_BASE_URL_PREFIX = "https://api.openai.com"
Expand Down Expand Up @@ -493,9 +498,32 @@ def create(self, params: dict[str, Any]) -> ChatCompletion:
Returns:
The completion.
"""
agent_config = params.pop("agent_config", None)
agent_config = agent_config_parser(agent_config) if agent_config is not None else None
iostream = IOStream.get_default()

is_structured_output = self.response_format is not None or "response_format" in params
# Priority: agent_config.response_format > llm_config.response_format (params)
# This logic correctly implements:
# 1. If response_format in llm_config but NOT in agent_config → use llm_config
# 2. If response_format NOT in llm_config but IN agent_config → use agent_config
# 3. If in BOTH → use agent_config (agent_config takes priority)
agent_config_response_format = (
agent_config.get("response_format")
if agent_config is not None
and "response_format" in agent_config
and agent_config.get("response_format") is not None
else None
)
llm_config_response_format = params.get("response_format")

# Set response_format with proper priority: agent_config > llm_config
self.response_format = (
agent_config_response_format if agent_config_response_format is not None else llm_config_response_format
)
params["response_format"] = self.response_format

# Fix: Only enable structured output when we actually have a response_format
is_structured_output = self.response_format is not None

if is_structured_output:

Expand All @@ -504,24 +532,26 @@ def _create_or_parse(*args, **kwargs):
kwargs.pop("stream")
kwargs.pop("stream_options", None)

if (
isinstance(kwargs["response_format"], dict)
and kwargs["response_format"].get("type") != "json_object"
):
# Get response_format from kwargs (which comes from params)
response_format_value = kwargs.get("response_format")

if response_format_value is None:
# Should not happen if is_structured_output is True, but guard against it
kwargs.pop("response_format", None)
elif isinstance(response_format_value, dict) and response_format_value.get("type") != "json_object":
kwargs["response_format"] = {
"type": "json_schema",
"json_schema": {
"schema": _ensure_strict_json_schema(
kwargs["response_format"], path=(), root=kwargs["response_format"]
response_format_value, path=(), root=response_format_value
),
"name": "response_format",
"strict": True,
},
}
else:
kwargs["response_format"] = type_to_response_format_param(
self.response_format or params["response_format"]
)
# Convert Pydantic model or other types to OpenAI's response_format format
kwargs["response_format"] = type_to_response_format_param(response_format_value)

return self._oai_client.chat.completions.create(*args, **kwargs)

Expand Down Expand Up @@ -1176,7 +1206,8 @@ def create(self, **config: Any) -> ModelClient.ModelClientResponseProtocol:
cache = extra_kwargs.get("cache")
filter_func = extra_kwargs.get("filter_func")
context = extra_kwargs.get("context")
agent = extra_kwargs.get("agent")
agent = extra_kwargs.get("agent", None)

price = extra_kwargs.get("price", None)
if isinstance(price, list):
price = tuple(price)
Expand Down Expand Up @@ -1259,6 +1290,9 @@ def create(self, **config: Any) -> ModelClient.ModelClientResponseProtocol:
return response
continue # filter is not passed; try the next config
try:
# Add agent to params if provided (for downstream use)
if agent is not None:
params["agent_config"] = agent.agent_config
request_ts = get_current_ts()
response = client.create(params)
except Exception as e:
Expand Down
Loading
Loading