From b397b58e2194df786f6b23d865387a38879d0e42 Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Tue, 4 Aug 2026 17:12:22 +0200 Subject: [PATCH 1/8] Upgrading agent-framework adapter to work on latest release --- pyagentspec/constraints/constraints.txt | 3 +- pyagentspec/constraints/constraints_dev.txt | 3 +- pyagentspec/setup.py | 13 ++- .../_agentframeworkconverter.py | 46 ++++++--- .../agent_framework/_agentspecconverter.py | 26 +++-- .../adapters/agent_framework/_types.py | 35 +++---- .../test_agentframework_to_agentspec.py | 54 ++++++++--- .../test_agentspec_to_agentframework.py | 97 +++++++++++++++---- 8 files changed, 194 insertions(+), 83 deletions(-) diff --git a/pyagentspec/constraints/constraints.txt b/pyagentspec/constraints/constraints.txt index 23ab31e4..0426bed5 100644 --- a/pyagentspec/constraints/constraints.txt +++ b/pyagentspec/constraints/constraints.txt @@ -26,7 +26,8 @@ langgraph-swarm==0.1.0 wayflowcore==26.1.2 # AgentFramework adapter -agent-framework==1.0.0b260130 +agent-framework-core==1.13.0 +agent-framework-openai==1.12.0 # OpenAI Agents adapter openai-agents==0.6.9 diff --git a/pyagentspec/constraints/constraints_dev.txt b/pyagentspec/constraints/constraints_dev.txt index 23ab31e4..0426bed5 100644 --- a/pyagentspec/constraints/constraints_dev.txt +++ b/pyagentspec/constraints/constraints_dev.txt @@ -26,7 +26,8 @@ langgraph-swarm==0.1.0 wayflowcore==26.1.2 # AgentFramework adapter -agent-framework==1.0.0b260130 +agent-framework-core==1.13.0 +agent-framework-openai==1.12.0 # OpenAI Agents adapter openai-agents==0.6.9 diff --git a/pyagentspec/setup.py b/pyagentspec/setup.py index b35e807f..dd20c6de 100644 --- a/pyagentspec/setup.py +++ b/pyagentspec/setup.py @@ -156,14 +156,13 @@ def read(file_name): ], "agent-framework": [ # 3rd party dependencies (imported in code) - "agent-framework>=1.0.0b260130; python_version < '3.14'", - "httpx>0.28.0; python_version < '3.14'", + "agent-framework-core>=1.10.0", + "agent-framework-openai>=1.10.0", + "httpx>0.28.0", # 4rth party dependencies - "certifi>=2025.1.31; python_version < '3.14'", # needed to avoid CVE present in earlier versions - "cryptography>=46.0.7; python_version < '3.14'", # needed to avoid CVE present in earlier versions - # including otel-semconv-ai to address internal agent-framework bug - "opentelemetry-semantic-conventions-ai<0.4.14", - "urllib3>=2.7.0; python_version < '3.14'", # needed to avoid CVE present in earlier versions + "certifi>=2025.1.31", # needed to avoid CVE present in earlier versions + "cryptography>=46.0.7", # needed to avoid CVE present in earlier versions + "urllib3>=2.7.0", # needed to avoid CVE present in earlier versions ], "evaluation": [ # 3rd party dependencies (imported in code) diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py index 5cf6fd21..7e827375 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentframeworkconverter.py @@ -10,22 +10,23 @@ from pyagentspec.adapters._tools_common import _create_remote_tool_func from pyagentspec.adapters.agent_framework._types import ( + Agent, AgentFrameworkComponent, AgentFrameworkMCPTool, AgentFrameworkTool, BaseChatClient, - ChatAgent, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, OpenAIChatClient, + OpenAIChatCompletionClient, ) from pyagentspec.agent import Agent as AgentSpecAgent from pyagentspec.component import Component as AgentSpecComponent from pyagentspec.llms.llmconfig import LlmConfig as AgentSpecLlmConfig from pyagentspec.llms.llmgenerationconfig import LlmGenerationConfig from pyagentspec.llms.ollamaconfig import OllamaConfig -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.llms.openaiconfig import OpenAiConfig from pyagentspec.mcp.tools import MCPTool as AgentSpecMCPTool from pyagentspec.property import Property as AgentSpecProperty @@ -234,6 +235,19 @@ def _llm_convert_to_agent_framework( tool_registry: dict[str, AgentFrameworkTool], converted_components: dict[str, AgentFrameworkComponent], ) -> BaseChatClient: + def openai_client(**kwargs: Any) -> BaseChatClient: + if llm_config.api_type in (OpenAIAPIType.RESPONSES, "responses"): + return OpenAIChatClient(**kwargs) + if llm_config.api_type in ( + None, + OpenAIAPIType.CHAT_COMPLETIONS, + "chat_completions", + ): + return OpenAIChatCompletionClient(**kwargs) + raise NotImplementedError( + f"LlmConfig with api_type='{llm_config.api_type}' is not supported in agent_framework" + ) + if isinstance(llm_config, OpenAiCompatibleConfig): from urllib.parse import urljoin @@ -242,30 +256,28 @@ def _llm_convert_to_agent_framework( base_url = f"http://{base_url}" if "/v1" not in base_url: base_url = urljoin(base_url + "/", "v1") - return OpenAIChatClient( + return openai_client( api_key="openai", base_url=base_url, - model_id=llm_config.model_id, + model=llm_config.model_id, ) elif isinstance(llm_config, OllamaConfig): - return OpenAIChatClient( + return openai_client( api_key="ollama", base_url=llm_config.url, - model_id=llm_config.model_id, + model=llm_config.model_id, ) elif isinstance(llm_config, OpenAiConfig): - return OpenAIChatClient( - model_id=llm_config.model_id, - ) + return openai_client(model=llm_config.model_id) else: # Bare LlmConfig — dispatch on api_provider string if llm_config.api_provider == "openai": - kwargs: dict[str, Any] = {"model_id": llm_config.model_id} + kwargs: dict[str, Any] = {"model": llm_config.model_id} if llm_config.url is not None: kwargs["base_url"] = llm_config.url if llm_config.api_key is not None: kwargs["api_key"] = llm_config.api_key - return OpenAIChatClient(**kwargs) + return openai_client(**kwargs) raise NotImplementedError( f"LlmConfig with api_provider='{llm_config.api_provider}' is not yet supported " f"in agent_framework. Consider using a specific LlmConfig subclass instead." @@ -283,14 +295,16 @@ def _agent_convert_to_agent_framework( chat_client = self.convert(agent.llm_config, tool_registry, converted_components) tools = [self.convert(tool, tool_registry, converted_components) for tool in agent.tools] prompt = agent.system_prompt - return ChatAgent( + return Agent( id=agent.id, name=agent.name, description=agent.description, - chat_client=cast(BaseChatClient, chat_client), + client=cast(BaseChatClient, chat_client), tools=cast(AgentFrameworkTool, tools), instructions=prompt, - temperature=generation_parameters.temperature, - top_p=generation_parameters.top_p, - max_tokens=generation_parameters.max_tokens, + additional_properties=dict( + temperature=generation_parameters.temperature, + top_p=generation_parameters.top_p, + max_tokens=generation_parameters.max_tokens, + ), ) diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py index 22b41960..59dc56aa 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_agentspecconverter.py @@ -9,20 +9,21 @@ from pyagentspec.adapters._utils import _get_obj_reference from pyagentspec.adapters.agent_framework._types import ( + Agent, AgentFrameworkLlmConfig, AgentFrameworkMCPTool, AgentFrameworkTool, - ChatAgent, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, OpenAIChatClient, + OpenAIChatCompletionClient, ) from pyagentspec.agent import Agent as AgentSpecAgent from pyagentspec.component import Component as AgentSpecComponent from pyagentspec.llms.llmconfig import LlmConfig from pyagentspec.llms.llmgenerationconfig import LlmGenerationConfig -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.mcp.clienttransport import StdioTransport, StreamableHTTPTransport from pyagentspec.mcp.tools import MCPTool from pyagentspec.property import Property as AgentSpecProperty @@ -92,7 +93,7 @@ def convert( # If we did not find the object, we create it, and we record it in the referenced_objects registry agentspec_component: AgentSpecComponent - if isinstance(runtime_component, ChatAgent): + if isinstance(runtime_component, Agent): agentspec_component = self._agent_convert_to_agentspec( runtime_component, referenced_objects, @@ -187,22 +188,27 @@ def _llm_convert_to_agentspec( chat_client: AgentFrameworkLlmConfig, referenced_objects: dict[str, AgentSpecComponent], ) -> OpenAiCompatibleConfig: - if isinstance(chat_client, OpenAIChatClient): - if chat_client.model_id is None: + if isinstance(chat_client, (OpenAIChatClient, OpenAIChatCompletionClient)): + if chat_client.model is None: # Defensive check for None in some versions due to fast iteration # Once the framework stabilizes and the type is set in stone this check can be removed - raise ValueError(f"model_id for {type(chat_client)} is not set.") + raise ValueError(f"model for {type(chat_client)} is not set.") return OpenAiCompatibleConfig( - name=chat_client.model_id, - model_id=chat_client.model_id, + name=chat_client.model, + model_id=chat_client.model, url=chat_client.service_url(), + api_type=( + OpenAIAPIType.RESPONSES + if isinstance(chat_client, OpenAIChatClient) + else OpenAIAPIType.CHAT_COMPLETIONS + ), ) else: raise NotImplementedError(f"Chat client {type(chat_client)} not supported") def _agent_convert_to_agentspec( self, - chat_agent: ChatAgent, + chat_agent: Agent, referenced_objects: dict[str, AgentSpecComponent], ) -> AgentSpecComponent: generation_config = LlmGenerationConfig( @@ -213,7 +219,7 @@ def _agent_convert_to_agentspec( llm_config = cast( LlmConfig, self.convert( - chat_agent.chat_client, + chat_agent.client, referenced_objects, ), ) diff --git a/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py b/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py index cf0d4295..e000441c 100644 --- a/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py +++ b/pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py @@ -10,49 +10,50 @@ if TYPE_CHECKING: from agent_framework import ( + Agent, BaseChatClient, - ChatAgent, - ChatClientProtocol, ChatOptions, FunctionTool, MCPStdioTool, MCPStreamableHTTPTool, MCPWebsocketTool, - ToolProtocol, ) - from agent_framework.openai import OpenAIChatClient + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient else: ChatOptions = LazyLoader("agent_framework").ChatOptions FunctionTool = LazyLoader("agent_framework").FunctionTool BaseChatClient = LazyLoader("agent_framework").BaseChatClient - ChatAgent = LazyLoader("agent_framework").ChatAgent - ChatClientProtocol = LazyLoader("agent_framework").ChatClientProtocol + Agent = LazyLoader("agent_framework").Agent MCPStdioTool = LazyLoader("agent_framework").MCPStdioTool MCPStreamableHTTPTool = LazyLoader("agent_framework").MCPStreamableHTTPTool MCPWebsocketTool = LazyLoader("agent_framework").MCPWebsocketTool - ToolProtocol = LazyLoader("agent_framework").ToolProtocol - OpenAIChatClient = LazyLoader("agent_framework.openai").OpenAIChatClient + _openai = LazyLoader("agent_framework.openai") + OpenAIChatClient = _openai.OpenAIChatClient + OpenAIChatCompletionClient = _openai.OpenAIChatCompletionClient -AgentFrameworkComponent: TypeAlias = ChatAgent +AgentFrameworkMCPTool: TypeAlias = MCPStdioTool | MCPStreamableHTTPTool | MCPWebsocketTool AgentFrameworkTool: TypeAlias = ( - ToolProtocol - | FunctionTool[Any, Any] + FunctionTool | Callable[..., Any] | MutableMapping[str, Any] - | Sequence[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]] + | Sequence[Callable[..., Any] | MutableMapping[str, Any]] + | AgentFrameworkMCPTool ) -AgentFrameworkLlmConfig: TypeAlias = BaseChatClient | ChatClientProtocol -AgentFrameworkMCPTool: TypeAlias = MCPStdioTool | MCPStreamableHTTPTool | MCPWebsocketTool +AgentFrameworkLlmConfig: TypeAlias = BaseChatClient +AgentFrameworkComponent: TypeAlias = Agent | AgentFrameworkTool | AgentFrameworkLlmConfig __all__ = [ + "AgentFrameworkComponent", + "AgentFrameworkLlmConfig", + "AgentFrameworkMCPTool", + "AgentFrameworkTool", "BaseChatClient", "FunctionTool", - "ChatAgent", - "ChatClientProtocol", + "Agent", "MCPStdioTool", "MCPStreamableHTTPTool", "MCPWebsocketTool", - "ToolProtocol", "OpenAIChatClient", + "OpenAIChatCompletionClient", "ChatOptions", ] diff --git a/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py b/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py index 87bb7900..b379d944 100644 --- a/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py +++ b/pyagentspec/tests/adapters/agent_framework/test_agentframework_to_agentspec.py @@ -6,14 +6,14 @@ from typing import cast -from pyagentspec.agent import Agent -from pyagentspec.llms.openaicompatibleconfig import OpenAiCompatibleConfig +from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig def test_agent_framework_converts_to_agent_spec_with_server_tool() -> None: - from agent_framework import ChatAgent, tool - from agent_framework.openai import OpenAIChatClient + from agent_framework import Agent, tool + from agent_framework.openai import OpenAIChatCompletionClient from pyagentspec.adapters.agent_framework import AgentSpecExporter @@ -21,31 +21,34 @@ def test_agent_framework_converts_to_agent_spec_with_server_tool() -> None: def add_tool(a: int, b: int) -> int: return a + b - agent = ChatAgent( - chat_client=OpenAIChatClient( + agent = Agent( + client=OpenAIChatCompletionClient( api_key="ollama", base_url="url.to.agi.model", - model_id="agi_ollama_model", + model="agi_ollama_model", ), name="MathAgent", instructions="You are a helpful math agent", tools=add_tool, - temperature=0.2, - top_p=0.5, - max_tokens=10000, + additional_properties=dict( + temperature=0.2, + top_p=0.5, + max_tokens=10000, + ), ) exporter = AgentSpecExporter() - agent_component = cast(Agent, exporter.to_component(agent)) + agent_component = cast(AgentSpecAgent, exporter.to_component(agent)) # Agent config assert agent_component.name == agent.name assert agent_component.description == agent.description assert isinstance(agent_component.llm_config, OpenAiCompatibleConfig) - assert isinstance(agent.chat_client, OpenAIChatClient) + assert agent_component.llm_config.api_type == OpenAIAPIType.CHAT_COMPLETIONS + assert isinstance(agent.client, OpenAIChatCompletionClient) assert agent_component.system_prompt == agent.default_options["instructions"] # Llm Config - assert agent_component.llm_config.url == agent.chat_client.service_url() - assert agent_component.llm_config.model_id == agent.chat_client.model_id + assert agent_component.llm_config.url == agent.client.service_url() + assert agent_component.llm_config.model_id == agent.client.model default_generation_parameters = agent_component.llm_config.default_generation_parameters assert default_generation_parameters is not None assert default_generation_parameters.temperature == agent.additional_properties["temperature"] @@ -65,3 +68,26 @@ def add_tool(a: int, b: int) -> int: assert "b" in (schema["title"] for schema in input_json_schemas) assert all(schema["type"] == "integer" for schema in input_json_schemas) assert output_json_schema["title"] == "result" and output_json_schema["type"] == "integer" + + +def test_agent_framework_responses_client_converts_to_agent_spec() -> None: + from agent_framework import Agent + from agent_framework.openai import OpenAIChatClient + + from pyagentspec.adapters.agent_framework import AgentSpecExporter + + agent = Agent( + client=OpenAIChatClient( + api_key="test-key", + base_url="https://api.example.com/v1", + model="gpt-test", + ), + name="ResponsesAgent", + instructions="Be helpful.", + ) + + agent_component = cast(AgentSpecAgent, AgentSpecExporter().to_component(agent)) + + assert isinstance(agent_component.llm_config, OpenAiCompatibleConfig) + assert agent_component.llm_config.api_type == OpenAIAPIType.RESPONSES + assert agent_component.llm_config.model_id == agent.client.model diff --git a/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py b/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py index b173d700..368f29bf 100644 --- a/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py +++ b/pyagentspec/tests/adapters/agent_framework/test_agentspec_to_agentframework.py @@ -5,28 +5,92 @@ # (UPL) 1.0 (LICENSE-UPL or https://oss.oracle.com/licenses/upl), at your option. import anyio +import pytest -from pyagentspec.agent import Agent +from pyagentspec.agent import Agent as AgentSpecAgent +from pyagentspec.llms import LlmConfig +from pyagentspec.llms.openaicompatibleconfig import OpenAIAPIType, OpenAiCompatibleConfig from pyagentspec.llms.vllmconfig import VllmConfig from pyagentspec.serialization import AgentSpecDeserializer from .conftest import get_weather +@pytest.mark.parametrize( + ("api_type", "expected_client_name"), + [ + (OpenAIAPIType.CHAT_COMPLETIONS, "OpenAIChatCompletionClient"), + (OpenAIAPIType.RESPONSES, "OpenAIChatClient"), + ], +) +def test_agentspec_api_type_selects_agent_framework_client( + api_type: OpenAIAPIType, expected_client_name: str +) -> None: + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient + + from pyagentspec.adapters.agent_framework import AgentSpecLoader + + client = AgentSpecLoader().load_component( + OpenAiCompatibleConfig( + name="test-model", + model_id="test-model", + url="http://test.example/v1", + api_type=api_type, + ) + ) + + expected_client = { + "OpenAIChatClient": OpenAIChatClient, + "OpenAIChatCompletionClient": OpenAIChatCompletionClient, + }[expected_client_name] + assert isinstance(client, expected_client) + + +@pytest.mark.parametrize( + ("api_type", "expected_client_name"), + [ + ("chat_completions", "OpenAIChatCompletionClient"), + ("responses", "OpenAIChatClient"), + ], +) +def test_bare_llmconfig_api_type_selects_agent_framework_client( + api_type: str, expected_client_name: str +) -> None: + from agent_framework.openai import OpenAIChatClient, OpenAIChatCompletionClient + + from pyagentspec.adapters.agent_framework import AgentSpecLoader + + client = AgentSpecLoader().load_component( + LlmConfig( + name="test-model", + model_id="test-model", + api_provider="openai", + api_type=api_type, + url="http://test.example/v1", + ) + ) + + expected_client = { + "OpenAIChatClient": OpenAIChatClient, + "OpenAIChatCompletionClient": OpenAIChatCompletionClient, + }[expected_client_name] + assert isinstance(client, expected_client) + + def test_agentspec_converts_to_agent_framework_with_server_tool( weather_agent_server_tool: str, ) -> None: - from agent_framework import ChatAgent, FunctionTool - from agent_framework.openai import OpenAIChatClient + from agent_framework import Agent, FunctionTool + from agent_framework.openai import OpenAIChatCompletionClient from pyagentspec.adapters.agent_framework import AgentSpecLoader agent_component = AgentSpecDeserializer().from_yaml(weather_agent_server_tool) - assert isinstance(agent_component, Agent) + assert isinstance(agent_component, AgentSpecAgent) loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) # Agent Config assert agent_component.name == agent.name @@ -35,9 +99,9 @@ def test_agentspec_converts_to_agent_framework_with_server_tool( # Llm Config assert isinstance(agent_component.llm_config, VllmConfig) - assert isinstance(agent.chat_client, OpenAIChatClient) - assert agent_component.llm_config.url in agent.chat_client.service_url() - assert agent_component.llm_config.model_id == agent.chat_client.model_id + assert isinstance(agent.client, OpenAIChatCompletionClient) + assert agent_component.llm_config.url in agent.client.service_url() + assert agent_component.llm_config.model_id == agent.client.model default_generation_parameters = agent_component.llm_config.default_generation_parameters assert default_generation_parameters is not None assert default_generation_parameters.temperature == agent.additional_properties["temperature"] @@ -56,27 +120,26 @@ def test_agentspec_converts_to_agent_framework_with_server_tool( def test_agent_with_server_tool_runs_after_config_load(weather_agent_server_tool: str) -> None: - from agent_framework import ChatAgent + from agent_framework import Agent from pyagentspec.adapters.agent_framework import AgentSpecLoader loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) result = anyio.run(agent.run, "What is the weather like in Agadir?") contents = result.messages[-1].contents assert len(contents) > 0 - last_content = contents[-1] def test_remote_tool_with_agent(weather_agent_remote_tool: str) -> None: - from agent_framework import ChatAgent + from agent_framework import Agent from pyagentspec.adapters.agent_framework import AgentSpecLoader agent = AgentSpecLoader().load_yaml(weather_agent_remote_tool) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) result = anyio.run(agent.run, "What is the weather like in Agadir?") contents = result.messages[-1].contents @@ -89,13 +152,13 @@ def test_server_tool_requires_confirmation_with_agent( weather_agent_server_tool_confirmation: str, ) -> None: - from agent_framework import ChatAgent, ChatMessage, FunctionTool, Role + from agent_framework import Agent, FunctionTool, Message from pyagentspec.adapters.agent_framework import AgentSpecLoader loader = AgentSpecLoader(tool_registry={"get_weather": get_weather}) agent = loader.load_yaml(weather_agent_server_tool_confirmation) - assert isinstance(agent, ChatAgent) + assert isinstance(agent, Agent) # Ensure approval mode is set to always_require for the converted tool tools = agent.default_options["tools"] or [] @@ -118,8 +181,8 @@ async def run_agent_with_confirmation(agent): final_result = await agent.run( [ "What is the weather like in Agadir?", - ChatMessage(role=Role.ASSISTANT, contents=[req]), - ChatMessage(role=Role.USER, contents=[req.to_function_approval_response(True)]), + Message(role="assistant", contents=[req]), + Message(role="user", contents=[req.to_function_approval_response(True)]), ], ) contents = final_result.messages[-1].contents From fd4b04ddf3fb5553e6ba286dab48118cbf55399e Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 13:47:16 +0200 Subject: [PATCH 2/8] Fix api key in langgraph adapter --- .../adapters/langgraph/_langgraphconverter.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py index 7393e9c8..c8d0d4c6 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py @@ -7,6 +7,7 @@ import inspect import logging +import os import sys from typing import ( TYPE_CHECKING, @@ -1195,7 +1196,9 @@ def _create_react_agent_with_given_info( ) if middleware: create_agent_kwargs["middleware"] = middleware - compiled_graph = langchain_agents.create_agent(**create_agent_kwargs) + compiled_graph: CompiledStateGraph[Any, Any, Any] = langchain_agents.create_agent( + **create_agent_kwargs + ) # To enable flow execution traces monkey patch all the functions that invoke the compiled graph @@ -1323,7 +1326,7 @@ def _llm_convert_to_langgraph( return _create_chat_openai_model( model_id=llm_config.model_id, base_url=_prepare_openai_compatible_url(llm_config.url), - api_key=llm_config.api_key if llm_config.api_key is not None else "EMPTY", + api_key=llm_config.api_key, use_responses_api=use_responses_api, callbacks=callbacks, generation_config=generation_config, @@ -1650,7 +1653,6 @@ def _generation_config_from_agentspec( def _create_chat_openai_model( - *, model_id: str, use_responses_api: bool, callbacks: List[BaseCallbackHandler], @@ -1661,12 +1663,15 @@ def _create_chat_openai_model( ) -> BaseChatModel: """Create a ChatOpenAI model without overriding env-based defaults. - Important: passing `api_key=None` disables LangChain's env-based default (`OPENAI_API_KEY`) - and results in a model without a sync client. Only pass `api_key` when it is explicitly - specified in the Agent Spec config. + Important: passing `api_key=None` fallbacks to env-based default (`OPENAI_API_KEY`) or a fake API key. """ from langchain_openai import ChatOpenAI + # If api_key is None (or not passed), and the environment variable is not set, openai raises an error + # Therefore, we set a fake API key to avoid raising an exception in case no key is required + if api_key is None: + api_key = os.getenv("OPENAI_API_KEY", "EMPTY") + optional_kwargs: _ChatOpenAIOptionalKwargs = {} max_retries = retry_config.get("max_retries") if max_retries is not None: @@ -1676,13 +1681,12 @@ def _create_chat_openai_model( optional_kwargs["timeout"] = timeout if base_url is not None: optional_kwargs["base_url"] = base_url - if api_key is not None: - optional_kwargs["api_key"] = SecretStr(api_key) return ChatOpenAI( model=model_id, use_responses_api=use_responses_api, callbacks=callbacks, + api_key=SecretStr(api_key), temperature=generation_config.get("temperature"), max_completion_tokens=generation_config.get("max_tokens"), top_p=generation_config.get("top_p"), @@ -1696,7 +1700,6 @@ class _ChatOpenAIOptionalKwargs(TypedDict): max_retries: NotRequired[int] timeout: NotRequired[float] base_url: NotRequired[str] - api_key: NotRequired[SecretStr] class _GenerationConfig(TypedDict): From 1a54752110a2ebff51088ea5f31b5143ccac1970 Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 14:00:56 +0200 Subject: [PATCH 3/8] Upgrade openai agents --- pyagentspec/constraints/constraints.txt | 2 +- pyagentspec/constraints/constraints_dev.txt | 2 +- pyagentspec/setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyagentspec/constraints/constraints.txt b/pyagentspec/constraints/constraints.txt index 0426bed5..ae4d4264 100644 --- a/pyagentspec/constraints/constraints.txt +++ b/pyagentspec/constraints/constraints.txt @@ -30,7 +30,7 @@ agent-framework-core==1.13.0 agent-framework-openai==1.12.0 # OpenAI Agents adapter -openai-agents==0.6.9 +openai-agents==0.19.4 libcst==1.8.5 # Evaluation diff --git a/pyagentspec/constraints/constraints_dev.txt b/pyagentspec/constraints/constraints_dev.txt index 0426bed5..ae4d4264 100644 --- a/pyagentspec/constraints/constraints_dev.txt +++ b/pyagentspec/constraints/constraints_dev.txt @@ -30,7 +30,7 @@ agent-framework-core==1.13.0 agent-framework-openai==1.12.0 # OpenAI Agents adapter -openai-agents==0.6.9 +openai-agents==0.19.4 libcst==1.8.5 # Evaluation diff --git a/pyagentspec/setup.py b/pyagentspec/setup.py index dd20c6de..30a4cb36 100644 --- a/pyagentspec/setup.py +++ b/pyagentspec/setup.py @@ -103,7 +103,7 @@ def read(file_name): ], "openai-agents": [ # 3rd party dependencies (imported in code) - "openai-agents>=0.6.9", + "openai-agents>=0.19.0", "libcst>=1.5,<2", "httpx>0.28.0", # 4rth party dependencies From 259463920d7c7620c8fbc74ce93e6b29e0cc6289 Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 14:55:24 +0200 Subject: [PATCH 4/8] Generalization --- .../src/pyagentspec/adapters/langgraph/_langgraphconverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py index c8d0d4c6..069ea273 100644 --- a/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/langgraph/_langgraphconverter.py @@ -1670,7 +1670,7 @@ def _create_chat_openai_model( # If api_key is None (or not passed), and the environment variable is not set, openai raises an error # Therefore, we set a fake API key to avoid raising an exception in case no key is required if api_key is None: - api_key = os.getenv("OPENAI_API_KEY", "EMPTY") + api_key = os.getenv("OPENAI_API_KEY", "EMPTY") or "EMPTY" optional_kwargs: _ChatOpenAIOptionalKwargs = {} max_retries = retry_config.get("max_retries") From b4b4056d503915596d4eee7f353d6daf7fc08bb1 Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 15:14:28 +0200 Subject: [PATCH 5/8] Remove negative test for api key in langgraph --- .../langgraph/test_agentspec_to_langraph.py | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py b/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py index c1b35287..7c077651 100644 --- a/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py +++ b/pyagentspec/tests/adapters/langgraph/test_agentspec_to_langraph.py @@ -220,29 +220,6 @@ def weather_agent_server_tool_openaicompatible_yaml(weather_agent_server_tool_ya ) -def test_weather_agent_with_server_tool_with_openaicompatible_llm_raises_without_api_key( - weather_agent_server_tool_openaicompatible_yaml: str, -) -> None: - """ - This test is checking the case of OpenAiCompatibleConfig. - The VllmConfig is already tested in all the tests above - """ - import openai - - from pyagentspec.adapters.langgraph import AgentSpecLoader - - old_value = os.environ.pop("OPENAI_API_KEY", None) - - try: - with pytest.raises(openai.OpenAIError, match="api_key"): - AgentSpecLoader(tool_registry={"get_weather": get_weather}).load_yaml( - weather_agent_server_tool_openaicompatible_yaml - ) - finally: - if old_value is not None: - os.environ["OPENAI_API_KEY"] = old_value - - @mock.patch.dict(os.environ, {"OPENAI_API_KEY": "MOCKED_KEY"}) @retry_test(max_attempts=3, wait_between_tries=2) def test_execute_weather_agent_with_server_tool_with_openaicompatible_llm( From 4586bf333ed7957a74af68889929d0ce79ad807c Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 15:51:27 +0200 Subject: [PATCH 6/8] Fix autogen --- .../src/pyagentspec/adapters/autogen/_autogenconverter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py b/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py index ea46d27d..3e15b9f9 100644 --- a/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py +++ b/pyagentspec/src/pyagentspec/adapters/autogen/_autogenconverter.py @@ -204,7 +204,8 @@ def _prepare_llm_args(agentspec_llm_: AgentSpecLlmConfig) -> Dict[str, Any]: if agentspec_llm_.url else None ), - api_key=agentspec_llm_.api_key if agentspec_llm_.api_key else None, + # Use a fake API key to avoid `openai` failing immediately at parsing if no key is needed + api_key=agentspec_llm_.api_key if agentspec_llm_.api_key else "EMPTY", model_info=_prepare_model_info(agentspec_llm_), ) From 0bef2aeed2d964791550aac65964c1bff0595ecb Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 16:39:45 +0200 Subject: [PATCH 7/8] Fix documentation --- .../adapter_agent_framework_quickstart.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py b/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py index 537cf3dc..961a804f 100644 --- a/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py +++ b/docs/pyagentspec/source/code_examples/adapter_agent_framework_quickstart.py @@ -48,7 +48,7 @@ def subtract(a: float, b: float) -> float: return a - b async def main(): - from agent_framework import TextContent + from agent_framework import Content loader = AgentSpecLoader(tool_registry={"subtraction-tool": subtract}) assistant = loader.load_json(agentspec_config) @@ -58,7 +58,7 @@ async def main(): break result = await assistant.run(user_input) agent_message = result.messages[-1].contents[-1] - if not isinstance(agent_message, TextContent): + if not isinstance(agent_message, Content): raise ValueError(f"Unexpected agent_message type {type(agent_message)}") print(f"AGENT >> {agent_message.text}") @@ -69,7 +69,7 @@ async def main(): # .. end-agentspec_to_runtime # .. start-runtime_to_agentspec # Create an Agent Framework Agent -from agent_framework import ChatAgent, tool +from agent_framework import Agent, tool from agent_framework.openai import OpenAIChatClient @tool() @@ -85,11 +85,11 @@ def get_weather(city: str) -> str: """ return f"The weather in {city} is sunny." -agent_framework_agent = ChatAgent( - chat_client=OpenAIChatClient( +agent_framework_agent = Agent( + client=OpenAIChatClient( api_key="ollama", base_url="url.to.agi.model", - model_id="agi_ollama_model", + model="agi_ollama_model", ), name="Weather Agent", instructions="You are a weather agent. Use the provided tool to get data related to the weather based on the city mentioned in the user query.", From 1fd9ff6f65c9d399d6e74fc33f5537c163ac0bf8 Mon Sep 17 00:00:00 2001 From: Cesare Bernardis Date: Wed, 5 Aug 2026 18:38:59 +0200 Subject: [PATCH 8/8] Changelog, fix docs --- docs/pyagentspec/source/changelog.rst | 8 ++++++++ .../source/code_examples/adapter_openai_quickstart.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/pyagentspec/source/changelog.rst b/docs/pyagentspec/source/changelog.rst index db693b2a..f606794e 100644 --- a/docs/pyagentspec/source/changelog.rst +++ b/docs/pyagentspec/source/changelog.rst @@ -7,6 +7,14 @@ Agent Spec |release| Improvements ^^^^^^^^^^^^ +* **Updated Microsoft Agent Framework dependency** + + Updated ``agent-framework`` to version ``1.13.0``. + +* **Updated OpenAI Agents SDK dependency** + + Updated ``openai-agents`` to version ``0.19.4``. + * **Sensitive field export opt-in** Serializers now support ``include_sensitive_fields`` to include sensitive field values in diff --git a/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py b/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py index 54ec20fc..ab3a0308 100644 --- a/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py +++ b/docs/pyagentspec/source/code_examples/adapter_openai_quickstart.py @@ -66,7 +66,7 @@ async def main(): # .. end-agentspec_to_runtime # .. start-runtime_to_agentspec # Create an OpenAI Agent -from agents.agent import Agent, function_tool +from agents import Agent, function_tool @function_tool def subtraction_tool(a: float, b: float) -> float: