Skip to content

Commit dec9ecb

Browse files
glharperhowieleungLiudmila MolkovaM-Hietala
authored
[AI] [Agents] v1.1.0b2 (#41325)
* [AI] [Agents] add is_valid_connection_id check to connection tool init * Howie/async func b2 (#41349) * use as* `asyncio.gather` is used to make function tool calls in paral… (#41311) * use as* `asyncio.gather` is used to make function tool calls in parallel for `async` scenario. * Fixed version * update * update version * run black * fix change_context usages (#41362) * fixing tracing for streaming when not using event handler (#41424) * fixing tracing for streaming when not using event handler * fixing mypy warnings * fixes * changelog * Update CHANGELOG.md * Howie/samples with proj client (#41426) * Update samples with project client * more samples * update samples * UPDATE * update * change tooling samples * black and readme update * update pip install * Update changelog * change is_valid_connection_id to _is_valid_connection_id and run black * fixed lint * revert dev req * Update README.md * update * fix readme * added a new sample * Update CHANGELOG.md with new release date --------- Co-authored-by: Howie Leung <[email protected]> Co-authored-by: Liudmila Molkova <[email protected]> Co-authored-by: M-Hietala <[email protected]>
1 parent 5f40509 commit dec9ecb

File tree

82 files changed

+2067
-1677
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2067
-1677
lines changed

sdk/ai/azure-ai-agents/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
# Release History
44

5+
## 1.1.0b2 (2025-06-09)
6+
7+
### Bugs Fixed
8+
9+
- `asyncio.gather` is used to make function tool calls in parallel for `async` scenario.
10+
- Adding instrumentation for create_thread_and_run.
11+
- Fixed a tracing related bug that caused process_thread_run span to not appear when streaming is used without event handler.
12+
13+
### Sample updates
14+
- Changed all samples to use `AIProjectClient` which is recommended to specify endpoint and credential.
15+
- Added `sample_agents_stream_iteration_with_functions.py`
16+
517
## 1.1.0b1 (2025-05-20)
618

719
### Features Added

sdk/ai/azure-ai-agents/README.md

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,11 @@ Here is an example of how to create an Agent:
124124
<!-- SNIPPET:sample_agents_basics.create_agent -->
125125

126126
```python
127-
128-
agent = agents_client.create_agent(
129-
model=os.environ["MODEL_DEPLOYMENT_NAME"],
130-
name="my-agent",
131-
instructions="You are helpful agent",
132-
)
127+
agent = agents_client.create_agent(
128+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
129+
name="my-agent",
130+
instructions="You are helpful agent",
131+
)
133132
```
134133

135134
<!-- END SNIPPET -->
@@ -300,7 +299,8 @@ conn_id = os.environ["AZURE_BING_CONNECTION_ID"]
300299
bing = BingGroundingTool(connection_id=conn_id)
301300

302301
# Create agent with the bing tool and process agent run
303-
with agents_client:
302+
with project_client:
303+
agents_client = project_client.agents
304304
agent = agents_client.create_agent(
305305
model=os.environ["MODEL_DEPLOYMENT_NAME"],
306306
name="my-agent",
@@ -330,7 +330,14 @@ ai_search = AzureAISearchTool(
330330
)
331331

332332
# Create agent with AI search tool and process agent run
333-
with agents_client:
333+
project_client = AIProjectClient(
334+
endpoint=os.environ["PROJECT_ENDPOINT"],
335+
credential=DefaultAzureCredential(),
336+
)
337+
338+
with project_client:
339+
agents_client = project_client.agents
340+
334341
agent = agents_client.create_agent(
335342
model=os.environ["MODEL_DEPLOYMENT_NAME"],
336343
name="my-agent",
@@ -586,7 +593,7 @@ Below is an example of how to create an Azure Logic App utility tool and registe
586593
```python
587594

588595
# Create the agents client
589-
agents_client = AgentsClient(
596+
project_client = AIProjectClient(
590597
endpoint=os.environ["PROJECT_ENDPOINT"],
591598
credential=DefaultAzureCredential(),
592599
)
@@ -647,7 +654,9 @@ openapi_tool.add_definition(
647654
)
648655

649656
# Create agent with OpenApi tool and process agent run
650-
with agents_client:
657+
with project_client:
658+
agents_client = project_client.agents
659+
651660
agent = agents_client.create_agent(
652661
model=os.environ["MODEL_DEPLOYMENT_NAME"],
653662
name="my-agent",
@@ -676,7 +685,9 @@ print(conn_id)
676685
fabric = FabricTool(connection_id=conn_id)
677686

678687
# Create an Agent with the Fabric tool and process an Agent run
679-
with agents_client:
688+
with project_client:
689+
agents_client = project_client.agents
690+
680691
agent = agents_client.create_agent(
681692
model=os.environ["MODEL_DEPLOYMENT_NAME"],
682693
name="my-agent",
@@ -907,7 +918,7 @@ message = agents_client.messages.create(
907918

908919
To process your message, you can use `runs.create`, `runs.create_and_process`, or `runs.stream`.
909920

910-
`create_run` requests the Agent to process the message without polling for the result. If you are using `function tools` regardless as `toolset` or not, your code is responsible for polling for the result and acknowledging the status of `Run`. When the status is `requires_action`, your code is responsible for calling the function tools. For a code sample, visit [`sample_agents_functions.py`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-agents/samples/agents_tools/sample_agents_functions.py).
921+
`runs.create` requests the Agent to process the message without polling for the result. If you are using `function tools`, your code is responsible for polling for the result and acknowledging the status of `Run`. When the status is `requires_action`, your code is responsible for calling the function tools. For a code sample, visit [`sample_agents_functions.py`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-agents/samples/agents_tools/sample_agents_functions.py).
911922

912923
Here is an example of `runs.create` and poll until the run is completed:
913924

@@ -925,10 +936,20 @@ while run.status in ["queued", "in_progress", "requires_action"]:
925936

926937
<!-- END SNIPPET -->
927938

928-
To have the SDK poll on your behalf and call `function tools`, use the `create_and_process` method. Note that `function tools` will only be invoked if they are provided as `toolset` during the `create_agent` call.
939+
To have the SDK poll on your behalf and call `function tools`, supply your function implementations through `enable_auto_function_calls` along with `runs.create_and_process` method .
929940

930941
Here is an example:
931942

943+
```python
944+
functions = FunctionTool(user_functions)
945+
946+
toolset = ToolSet()
947+
toolset.add(functions)
948+
949+
# To enable tool calls executed automatically
950+
agents_client.enable_auto_function_calls(toolset)
951+
```
952+
932953
<!-- SNIPPET:sample_agents_run_with_toolset.create_and_process_run -->
933954

934955
```python
@@ -937,9 +958,9 @@ run = agents_client.runs.create_and_process(thread_id=thread.id, agent_id=agent.
937958

938959
<!-- END SNIPPET -->
939960

940-
With streaming, polling need not be considered. If `function tools` are provided as `toolset` during the `create_agent` call, they will be invoked by the SDK.
961+
With streaming, polling need not be considered. If `function tools` were added to the agents, you should decide to have the function tools called manually or automatically. Please visit [`manual function call sample`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-agents/samples/agents_streaming/sample_agents_stream_eventhandler_with_functions.py) or [`automatic function call sample`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-agents/samples/agents_streaming/sample_agents_stream_iteration_with_toolset.py).
941962

942-
Here is an example of streaming:
963+
Here is a basic example of streaming:
943964

944965
<!-- SNIPPET:sample_agents_basics_stream_iteration.iterate_stream -->
945966

@@ -1171,7 +1192,8 @@ scenario = os.path.basename(__file__)
11711192
tracer = trace.get_tracer(__name__)
11721193

11731194
with tracer.start_as_current_span(scenario):
1174-
with agents_client:
1195+
with project_client:
1196+
agents_client = project_client.agents
11751197
```
11761198

11771199
<!-- END SNIPPET -->
@@ -1281,4 +1303,4 @@ additional questions or comments.
12811303
[azure_sub]: https://azure.microsoft.com/free/
12821304
[evaluators]: https://learn.microsoft.com/azure/ai-studio/how-to/develop/evaluate-sdk
12831305
[azure_ai_evaluation]: https://learn.microsoft.com/python/api/overview/azure/ai-evaluation-readme
1284-
[evaluator_library]: https://learn.microsoft.com/azure/ai-studio/how-to/evaluate-generative-ai-app#view-and-manage-the-evaluators-in-the-evaluator-library
1306+
[evaluator_library]: https://learn.microsoft.com/azure/ai-studio/how-to/evaluate-generative-ai-app#view-and-manage-the-evaluators-in-the-evaluator-library

sdk/ai/azure-ai-agents/azure/ai/agents/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
VERSION = "1.1.0b1"
9+
VERSION = "1.1.0b2"

sdk/ai/azure-ai-agents/azure/ai/agents/models/_patch.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -852,15 +852,25 @@ def execute(self, tool_call: Any) -> Any:
852852
class ConnectionTool(Tool[ToolDefinitionT]):
853853
"""
854854
A tool that requires connection ids.
855-
Used as base class for Bing Grounding, Sharepoint, and Microsoft Fabric
855+
Used as base class for Sharepoint and Microsoft Fabric
856856
"""
857857

858858
def __init__(self, connection_id: str):
859859
"""
860860
Initialize ConnectionTool with a connection_id.
861861
862862
:param connection_id: Connection ID used by tool. All connection tools allow only one connection.
863+
:raises ValueError: If the connection id is invalid.
863864
"""
865+
if not _is_valid_connection_id(connection_id):
866+
raise ValueError(
867+
"Connection ID '"
868+
+ connection_id
869+
+ "' does not fit the format:"
870+
+ "'/subscriptions/<subscription_id>/resourceGroups/<resource_group_name>/"
871+
+ "providers/<provider_name>/accounts/<account_name>/projects/<project_name>/connections/<connection_name>'"
872+
)
873+
864874
self.connection_ids = [ToolConnection(connection_id=connection_id)]
865875

866876
@property
@@ -890,10 +900,21 @@ def __init__(self, connection_id: str, market: str = "", set_lang: str = "", cou
890900
:param set_lang: The language to use for user interface strings when calling Bing API.
891901
:param count: The number of search results to return in the Bing API response.
892902
:param freshness: Filter search results by a specific time range.
893-
894-
.. seealso::
903+
:raises ValueError: If the connection id is invalid.
904+
905+
.. seealso::
895906
`Bing Web Search API Query Parameters <https://learn.microsoft.com/bing/search-apis/bing-web-search/reference/query-parameters>`_
896907
"""
908+
909+
if not _is_valid_connection_id(connection_id):
910+
raise ValueError(
911+
"Connection ID '"
912+
+ connection_id
913+
+ "' does not fit the format:"
914+
+ "'/subscriptions/<subscription_id>/resourceGroups/<resource_group_name>/"
915+
+ "providers/<provider_name>/accounts/<account_name>/projects/<project_name>/connections/<connection_name>'"
916+
)
917+
897918
self._search_configurations = [
898919
BingGroundingSearchConfiguration(
899920
connection_id=connection_id, market=market, set_lang=set_lang, count=count, freshness=freshness
@@ -1356,29 +1377,27 @@ def validate_tool_type(self, tool: Tool) -> None:
13561377
+ "Please use AsyncFunctionTool instead and provide sync and/or async function(s)."
13571378
)
13581379

1380+
async def _execute_single_tool_call(self, tool_call: Any):
1381+
try:
1382+
tool = self.get_tool(AsyncFunctionTool)
1383+
output = await tool.execute(tool_call)
1384+
return {"tool_call_id": tool_call.id, "output": str(output)}
1385+
except Exception as e: # pylint: disable=broad-exception-caught
1386+
return {"tool_call_id": tool_call.id, "output": str(e)}
1387+
13591388
async def execute_tool_calls(self, tool_calls: List[Any]) -> Any:
13601389
"""
1361-
Execute a tool of the specified type with the provided tool calls.
1390+
Execute a tool of the specified type with the provided tool calls concurrently.
13621391
13631392
:param List[Any] tool_calls: A list of tool calls to execute.
13641393
:return: The output of the tool operations.
13651394
:rtype: Any
13661395
"""
1367-
tool_outputs = []
13681396

1369-
for tool_call in tool_calls:
1370-
try:
1371-
if tool_call.type == "function":
1372-
tool = self.get_tool(AsyncFunctionTool)
1373-
output = await tool.execute(tool_call)
1374-
tool_output = {
1375-
"tool_call_id": tool_call.id,
1376-
"output": str(output),
1377-
}
1378-
tool_outputs.append(tool_output)
1379-
except Exception as e: # pylint: disable=broad-exception-caught
1380-
tool_output = {"tool_call_id": tool_call.id, "output": str(e)}
1381-
tool_outputs.append(tool_output)
1397+
# Execute all tool calls concurrently
1398+
tool_outputs = await asyncio.gather(
1399+
*[self._execute_single_tool_call(tc) for tc in tool_calls if tc.type == "function"]
1400+
)
13821401

13831402
return tool_outputs
13841403

@@ -1823,6 +1842,30 @@ def __exit__(self, exc_type, exc_val, exc_tb):
18231842
close_method()
18241843

18251844

1845+
def _is_valid_connection_id(connection_id: str) -> bool:
1846+
"""
1847+
Validates if a string matches the Azure connection resource ID format.
1848+
1849+
The expected format is:
1850+
"/subscriptions/<AZURE_SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/
1851+
providers/<AZURE_PROVIDER>/accounts/<ACCOUNT_NAME>/projects/<PROJECT_NAME>/
1852+
connections/<CONNECTION_NAME>"
1853+
1854+
:param connection_id: The connection ID string to validate
1855+
:type connection_id: str
1856+
:return: True if the string matches the expected format, False otherwise
1857+
:rtype: bool
1858+
"""
1859+
pattern = (
1860+
r"^/subscriptions/[^/]+/resourceGroups/[^/]+/providers/[^/]+/accounts/[^/]+/projects/[^/]+/connections/[^/]+$"
1861+
)
1862+
1863+
# Check if the string matches the pattern
1864+
if re.match(pattern, connection_id):
1865+
return True
1866+
return False
1867+
1868+
18261869
__all__: List[str] = [
18271870
"AgentEventHandler",
18281871
"AgentRunStream",

0 commit comments

Comments
 (0)