Skip to content

Commit 32d6342

Browse files
authored
[ai.agentserver] add azure-ai-agentserver-agentframework and azure-ai-agentserver-langgraph to agentserver (#43861)
* add azure-ai-agentserver-core * update version number and readme * add agentframework * update doc settings * add langgraph * update readme links * disable breaking because of python version * minor update on tracing * disable breaking check becaues of incompatible python version * disable breaking and clean setting * set pyright * disable pyright and verifytypes * disable pyright and verifytypes * fix test.yml service name * fix af readme * update version * remove agentshosting * set readme header * fix json in sample readme * fixed af version * disable verify_types and bandit because required -core package has incompatible python version * pin af-core version * update readme and pyproject * remove commented role mapping * fix dependency * skip keyword check * add packages to ci
1 parent c75311a commit 32d6342

File tree

114 files changed

+6299
-0
lines changed

Some content is hidden

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

114 files changed

+6299
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Release History
2+
3+
## 1.0.0b1 (2025-11-07)
4+
5+
### Features Added
6+
7+
First version
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Copyright (c) Microsoft Corporation.
2+
3+
MIT License
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
include *.md
2+
include LICENSE
3+
recursive-include tests *.py
4+
recursive-include samples *.py *.md
5+
recursive-include doc *.rst *.md
6+
include azure/__init__.py
7+
include azure/ai/__init__.py
8+
include azure/ai/agentserver/__init__.py
9+
include azure/ai/agentserver/agentframework/py.typed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Azure AI Agent Server Adapter for Agent-framework Python
2+
3+
4+
5+
## Getting started
6+
7+
```bash
8+
pip install azure-ai-agentserver-agentframework
9+
```
10+
11+
12+
## Key concepts
13+
14+
Azure AI Agent Server wraps your Agent-framework agent, and host it on the cloud.
15+
16+
17+
## Examples
18+
19+
```python
20+
# your existing agent
21+
from my_framework_agent import my_awesome_agent
22+
23+
# agent framework utils
24+
from azure.ai.agentserver.agentframework import from_agent_framework
25+
26+
if __name__ == "__main__":
27+
# with this simple line, your agent will be hosted on http://localhost:8088
28+
from_agent_framework(my_awesome_agent).run()
29+
30+
```
31+
32+
## Troubleshooting
33+
34+
First run your agent with azure-ai-agentserver-agentframework locally.
35+
36+
If it works on local but failed on cloud. Check your logs in the application insight connected to your Azure AI Foundry Project.
37+
38+
39+
## Next steps
40+
41+
Please visit [Samples](https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/agentserver/azure-ai-agentserver-agentframework/samples) folder. There are several samples for you to build your agent with azure-ai-agentserver
42+
43+
44+
## Contributing
45+
46+
This project welcomes contributions and suggestions. Most contributions require
47+
you to agree to a Contributor License Agreement (CLA) declaring that you have
48+
the right to, and actually do, grant us the rights to use your contribution.
49+
For details, visit https://cla.microsoft.com.
50+
51+
When you submit a pull request, a CLA-bot will automatically determine whether
52+
you need to provide a CLA and decorate the PR appropriately (e.g., label,
53+
comment). Simply follow the instructions provided by the bot. You will only
54+
need to do this once across all repos using our CLA.
55+
56+
This project has adopted the
57+
[Microsoft Open Source Code of Conduct][code_of_conduct]. For more information,
58+
see the Code of Conduct FAQ or contact opencode@microsoft.com with any
59+
additional questions or comments.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
5+
6+
from ._version import VERSION
7+
8+
9+
def from_agent_framework(agent):
10+
from .agent_framework import AgentFrameworkCBAgent
11+
12+
return AgentFrameworkCBAgent(agent)
13+
14+
15+
__all__ = ["from_agent_framework"]
16+
__version__ = VERSION
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for license information.
5+
# Code generated by Microsoft (R) Python Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
7+
# --------------------------------------------------------------------------
8+
9+
VERSION = "1.0.0b1"
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
# pylint: disable=logging-fstring-interpolation
5+
from __future__ import annotations
6+
7+
import asyncio # pylint: disable=do-not-import-asyncio
8+
import os
9+
from typing import Any, AsyncGenerator, Union
10+
11+
from agent_framework import AgentProtocol
12+
from agent_framework.azure import AzureAIAgentClient # pylint: disable=no-name-in-module
13+
from opentelemetry import trace
14+
15+
from azure.ai.agentserver.core import AgentRunContext, FoundryCBAgent
16+
from azure.ai.agentserver.core.constants import Constants as AdapterConstants
17+
from azure.ai.agentserver.core.logger import get_logger
18+
from azure.ai.agentserver.core.models import (
19+
CreateResponse,
20+
Response as OpenAIResponse,
21+
ResponseStreamEvent,
22+
)
23+
from azure.ai.projects import AIProjectClient
24+
from azure.identity import DefaultAzureCredential
25+
26+
from .models.agent_framework_input_converters import AgentFrameworkInputConverter
27+
from .models.agent_framework_output_non_streaming_converter import (
28+
AgentFrameworkOutputNonStreamingConverter,
29+
)
30+
from .models.agent_framework_output_streaming_converter import AgentFrameworkOutputStreamingConverter
31+
from .models.constants import Constants
32+
33+
logger = get_logger()
34+
35+
36+
class AgentFrameworkCBAgent(FoundryCBAgent):
37+
"""
38+
Adapter class for integrating Agent Framework agents with the FoundryCB agent interface.
39+
40+
This class wraps an Agent Framework `AgentProtocol` instance and provides a unified interface
41+
for running agents in both streaming and non-streaming modes. It handles input and output
42+
conversion between the Agent Framework and the expected formats for FoundryCB agents.
43+
44+
Parameters:
45+
agent (AgentProtocol): An instance of an Agent Framework agent to be adapted.
46+
47+
Usage:
48+
- Instantiate with an Agent Framework agent.
49+
- Call `agent_run` with a `CreateResponse` request body to execute the agent.
50+
- Supports both streaming and non-streaming responses based on the `stream` flag.
51+
"""
52+
53+
def __init__(self, agent: AgentProtocol):
54+
super().__init__()
55+
self.agent = agent
56+
logger.info(f"Initialized AgentFrameworkCBAgent with agent: {type(agent).__name__}")
57+
58+
def _resolve_stream_timeout(self, request_body: CreateResponse) -> float:
59+
"""Resolve idle timeout for streaming updates.
60+
61+
Order of precedence:
62+
1) request_body.stream_timeout_s (if provided)
63+
2) env var Constants.AGENTS_ADAPTER_STREAM_TIMEOUT_S
64+
3) Constants.DEFAULT_STREAM_TIMEOUT_S
65+
66+
:param request_body: The CreateResponse request body.
67+
:type request_body: CreateResponse
68+
69+
:return: The resolved stream timeout in seconds.
70+
:rtype: float
71+
"""
72+
override = request_body.get("stream_timeout_s", None)
73+
if override is not None:
74+
return float(override)
75+
env_val = os.getenv(Constants.AGENTS_ADAPTER_STREAM_TIMEOUT_S)
76+
return float(env_val) if env_val is not None else float(Constants.DEFAULT_STREAM_TIMEOUT_S)
77+
78+
def init_tracing(self):
79+
exporter = os.environ.get(AdapterConstants.OTEL_EXPORTER_ENDPOINT)
80+
app_insights_conn_str = os.environ.get(AdapterConstants.APPLICATION_INSIGHTS_CONNECTION_STRING)
81+
project_endpoint = os.environ.get(AdapterConstants.AZURE_AI_PROJECT_ENDPOINT)
82+
83+
if project_endpoint:
84+
project_client = AIProjectClient(endpoint=project_endpoint, credential=DefaultAzureCredential())
85+
agent_client = AzureAIAgentClient(project_client=project_client)
86+
agent_client.setup_azure_ai_observability()
87+
elif exporter or app_insights_conn_str:
88+
os.environ["WORKFLOW_ENABLE_OTEL"] = "true"
89+
from agent_framework.observability import setup_observability
90+
91+
setup_observability(
92+
enable_sensitive_data=True,
93+
otlp_endpoint=exporter,
94+
applicationinsights_connection_string=app_insights_conn_str,
95+
)
96+
self.tracer = trace.get_tracer(__name__)
97+
98+
async def agent_run(
99+
self, context: AgentRunContext
100+
) -> Union[
101+
OpenAIResponse,
102+
AsyncGenerator[ResponseStreamEvent, Any],
103+
]:
104+
logger.info(f"Starting agent_run with stream={context.stream}")
105+
request_input = context.request.get("input")
106+
107+
input_converter = AgentFrameworkInputConverter()
108+
message = input_converter.transform_input(request_input)
109+
logger.debug(f"Transformed input message type: {type(message)}")
110+
111+
# Use split converters
112+
if context.stream:
113+
logger.info("Running agent in streaming mode")
114+
streaming_converter = AgentFrameworkOutputStreamingConverter(context)
115+
116+
async def stream_updates():
117+
update_count = 0
118+
timeout_s = self._resolve_stream_timeout(context.request)
119+
logger.info("Starting streaming with idle-timeout=%.2fs", timeout_s)
120+
for ev in streaming_converter.initial_events():
121+
yield ev
122+
123+
# Iterate with per-update timeout; terminate if idle too long
124+
aiter = self.agent.run_stream(message).__aiter__()
125+
while True:
126+
try:
127+
update = await asyncio.wait_for(aiter.__anext__(), timeout=timeout_s)
128+
except StopAsyncIteration:
129+
logger.debug("Agent streaming iterator finished (StopAsyncIteration)")
130+
break
131+
except asyncio.TimeoutError:
132+
logger.warning("Streaming idle timeout reached (%.1fs); terminating stream.", timeout_s)
133+
for ev in streaming_converter.completion_events():
134+
yield ev
135+
return
136+
update_count += 1
137+
transformed = streaming_converter.transform_output_for_streaming(update)
138+
for event in transformed:
139+
yield event
140+
for ev in streaming_converter.completion_events():
141+
yield ev
142+
logger.info("Streaming completed with %d updates", update_count)
143+
144+
return stream_updates()
145+
146+
# Non-streaming path
147+
logger.info("Running agent in non-streaming mode")
148+
non_streaming_converter = AgentFrameworkOutputNonStreamingConverter(context)
149+
result = await self.agent.run(message)
150+
logger.debug(f"Agent run completed, result type: {type(result)}")
151+
transformed_result = non_streaming_converter.transform_output_for_response(result)
152+
logger.info("Agent run and transformation completed successfully")
153+
return transformed_result

0 commit comments

Comments
 (0)