Python: added create_agent and removed need for Field in annotations (#380)

* added create_agent

* add minimal sample

* allow multiple annotations

* improved docstring
This commit is contained in:
Eduard van Valkenburg
2025-08-08 19:55:20 +02:00
committed by GitHub
Unverified
parent 757c48e367
commit 8f27e63df6
5 changed files with 86 additions and 16 deletions
@@ -4,7 +4,7 @@ import asyncio
from abc import ABC, abstractmethod
from collections.abc import AsyncIterable, Awaitable, Callable, MutableMapping, MutableSequence, Sequence
from functools import wraps
from typing import Any, Generic, Literal, Protocol, TypeVar, runtime_checkable
from typing import TYPE_CHECKING, Any, Generic, Literal, Protocol, TypeVar, runtime_checkable
from pydantic import BaseModel
@@ -23,6 +23,9 @@ from ._types import (
GeneratedEmbeddings,
)
if TYPE_CHECKING:
from ._agents import ChatClientAgent
TInput = TypeVar("TInput", contravariant=True)
TEmbedding = TypeVar("TEmbedding")
TChatClientBase = TypeVar("TChatClientBase", bound="ChatClientBase")
@@ -641,6 +644,36 @@ class ChatClientBase(AFBaseModel, ABC):
"""
return None
def create_agent(
self,
*,
name: str,
instructions: str,
tools: AITool
| list[AITool]
| Callable[..., Any]
| list[Callable[..., Any]]
| MutableMapping[str, Any]
| list[MutableMapping[str, Any]]
| None = None,
**kwargs: Any,
) -> "ChatClientAgent":
"""Create an agent with the given name and instructions.
Args:
name: The name of the agent.
instructions: The instructions for the agent.
tools: Optional list of tools to associate with the agent.
**kwargs: Additional keyword arguments to pass to the agent.
See ChatClientAgent for all the available options.
Returns:
An instance of ChatClientAgent.
"""
from ._agents import ChatClientAgent
return ChatClientAgent(chat_client=self, name=name, instructions=instructions, tools=tools, **kwargs)
# region: Embedding Client
+22 -3
View File
@@ -4,10 +4,10 @@ import inspect
from collections.abc import Awaitable, Callable
from functools import wraps
from time import perf_counter
from typing import Any, Generic, Protocol, TypeVar, runtime_checkable
from typing import Annotated, Any, Generic, Protocol, TypeVar, get_args, get_origin, runtime_checkable
from opentelemetry import metrics, trace
from pydantic import BaseModel, create_model
from pydantic import BaseModel, Field, create_model
from ._logging import get_logger
from .telemetry import GenAIAttributes, start_as_current_span
@@ -137,6 +137,25 @@ class AIFunction(AITool, Generic[ArgsT, ReturnT]):
logger.info("Function completed. Duration: %fs", duration)
def _parse_annotation(annotation: Any) -> Any:
"""Parse a type annotation and return the corresponding type.
If the second annotation (after the type) is a string, then we convert that to a pydantic Field description.
The rest are returned as-is, allowing for multiple annotations.
"""
origin = get_origin(annotation)
if origin is not None:
args = get_args(annotation)
# For other generics, return the origin type (e.g., list for List[int])
if len(args) > 1 and isinstance(args[1], str):
# Create a new Annotated type with the updated Field
args_list = list(args)
if len(args_list) == 2:
return Annotated[args_list[0], Field(description=args_list[1])]
return Annotated[args_list[0], Field(description=args_list[1]), tuple(args_list[2:])]
return annotation
def ai_function(
func: Callable[..., ReturnT | Awaitable[ReturnT]] | None = None,
*,
@@ -174,7 +193,7 @@ def ai_function(
sig = inspect.signature(f)
fields = {
pname: (
param.annotation if param.annotation is not inspect.Parameter.empty else str,
_parse_annotation(param.annotation) if param.annotation is not inspect.Parameter.empty else str,
param.default if param.default is not inspect.Parameter.empty else ...,
)
for pname, param in sig.parameters.items()
@@ -4,7 +4,6 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import ChatClientAgent
from agent_framework.foundry import FoundryChatClient
from pydantic import Field
@@ -23,8 +22,8 @@ async def non_streaming_example() -> None:
# Since no Agent ID is provided, the agent will be automatically created
# and deleted after getting a response
async with ChatClientAgent(
chat_client=FoundryChatClient(),
async with FoundryChatClient().create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
@@ -40,8 +39,8 @@ async def streaming_example() -> None:
# Since no Agent ID is provided, the agent will be automatically created
# and deleted after getting a response
async with ChatClientAgent(
chat_client=FoundryChatClient(),
async with FoundryChatClient().create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
) as agent:
@@ -4,13 +4,11 @@ import asyncio
from random import randint
from typing import Annotated
from agent_framework import ChatClientAgent
from agent_framework.openai import OpenAIChatClient
from pydantic import Field
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
location: Annotated[str, "The location to get the weather for."],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
@@ -21,8 +19,8 @@ async def non_streaming_example() -> None:
"""Example of non-streaming response (get the complete result at once)."""
print("=== Non-streaming Response Example ===")
agent = ChatClientAgent(
chat_client=OpenAIChatClient(),
agent = OpenAIChatClient().create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -37,8 +35,8 @@ async def streaming_example() -> None:
"""Example of streaming response (get results as they are generated)."""
print("=== Streaming Response Example ===")
agent = ChatClientAgent(
chat_client=OpenAIChatClient(),
agent = OpenAIChatClient().create_agent(
name="WeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)
@@ -0,0 +1,21 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
from random import randint
from typing import Annotated
from agent_framework.openai import OpenAIChatClient
def get_weather(
location: Annotated[str, "The location to get the weather for."],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
agent = OpenAIChatClient().create_agent(
name="WeatherAgent", instructions="You are a helpful weather agent.", tools=get_weather
)
print(asyncio.run(agent.run("What's the weather like in Seattle?")))