Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions docs/pyagentspec/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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}")

Expand All @@ -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()
Expand All @@ -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.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions pyagentspec/constraints/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ 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
openai-agents==0.19.4
libcst==1.8.5

# Evaluation
Expand Down
5 changes: 3 additions & 2 deletions pyagentspec/constraints/constraints_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ 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
openai-agents==0.19.4
libcst==1.8.5

# Evaluation
Expand Down
15 changes: 7 additions & 8 deletions pyagentspec/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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."
Expand All @@ -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,
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -213,7 +219,7 @@ def _agent_convert_to_agentspec(
llm_config = cast(
LlmConfig,
self.convert(
chat_agent.chat_client,
chat_agent.client,
referenced_objects,
),
)
Expand Down
35 changes: 18 additions & 17 deletions pyagentspec/src/pyagentspec/adapters/agent_framework/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Original file line number Diff line number Diff line change
Expand Up @@ -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_),
)

Expand Down
Loading
Loading