Skip to content

Commit a2a9922

Browse files
authored
Add hosted agent samples (#2205)
1 parent aba3076 commit a2a9922

File tree

16 files changed

+437
-48
lines changed

16 files changed

+437
-48
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OPENAI_CHAT_MODEL_ID="gpt-4o-2024-08-06"
2+
OPENAI_API_KEY="your-openai-api-key"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY . user_agent/
6+
WORKDIR /app/user_agent
7+
8+
RUN if [ -f requirements.txt ]; then \
9+
pip install -r requirements.txt; \
10+
else \
11+
echo "No requirements.txt found"; \
12+
fi
13+
14+
EXPOSE 8088
15+
16+
CMD ["python", "main.py"]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Hosted Agents with Hosted MCP Demo
2+
3+
This demo showcases an agent that has access to a MCP tool that can talk to the Microsoft Learn documentation platform, hosted as an agent endpoint running locally in a Docker container.
4+
5+
## What the Project Does
6+
7+
This project demonstrates how to:
8+
9+
- Create an agent with a hosted MCP tool using the Agent Framework
10+
- Host the agent as an agent endpoint running in a Docker container
11+
12+
## Prerequisites
13+
14+
- OpenAI API access and credentials
15+
- Required environment variables (see Configuration section)
16+
17+
## Configuration
18+
19+
Follow the `.env.example` file to set up the necessary environment variables for OpenAI.
20+
21+
## Docker Deployment
22+
23+
Build and run using Docker:
24+
25+
```bash
26+
# Build the Docker image
27+
docker build -t hosted-agent-mcp .
28+
29+
# Run the container
30+
docker run -p 8088:8088 hosted-agent-mcp
31+
```
32+
33+
> If you update the environment variables in the `.env` file or change the code or the dockerfile, make sure to rebuild the Docker image to apply the changes.
34+
35+
## Testing the Agent
36+
37+
Once the agent is running, you can test it by sending queries that contain the trigger keywords. For example:
38+
39+
```bash
40+
curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses -d '{"input": "How to create an Azure storage account using az cli?","stream":false}'
41+
```
42+
43+
Expected response:
44+
45+
```bash
46+
{"object":"response","metadata":{},"agent":null,"conversation":{"id":"conv_6Y7osWAQ1ASyUZ7Ze0LL6dgPubmQv52jHb7G9QDqpV5yakc3ay"},"type":"message","role":"assistant","temperature":1.0,"top_p":1.0,"user":"","id":"resp_Vfd6mdmnmTZ2RNirwfldfqldWLhaxD6fO2UkXsVUg1jYJgftL9","created_at":1763075575,"output":[{"id":"msg_6Y7osWAQ1ASyUZ7Ze0PwiK2V4Bb7NOPaaEpQoBvFRZ5h6OfW4u","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"To create an Azure Storage account using the Azure CLI, you'll need to follow these steps:\n\n1. **Install Azure CLI**: Make sure the Azure CLI is installed on your machine. You can download it from [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli).\n\n2. **Log in to Azure**: Open your terminal or command prompt and use the following command to log in to your Azure account:\n\n ```bash\n az login\n ```\n\n This command will open a web browser where you can log in with your Azure account credentials. If you're using a service principal, you would use `az login --service-principal ...` with the appropriate parameters.\n\n3. **Select the Subscription**: If you have multiple Azure subscriptions, set the default subscription that you want to use:\n\n ```bash\n az account set --subscription \"Your Subscription Name\"\n ```\n\n4. **Create a Resource Group**: If you don’t already have a resource group, create one using:\n\n ```bash\n az group create --name myResourceGroup --location eastus\n ```\n\n Replace `myResourceGroup` and `eastus` with your desired resource group name and location.\n\n5. **Create the Storage Account**: Use the following command to create the storage account:\n\n ```bash\n az storage account create --name mystorageaccount --resource-group myResourceGroup --location eastus --sku Standard_LRS\n ```\n\n Replace `mystorageaccount` with a unique name for your storage account. The storage account name must be between 3 and 24 characters in length, and may contain numbers and lowercase letters only. You can also choose other `--sku` options like `Standard_GRS`, `Standard_RAGRS`, `Standard_ZRS`, `Premium_LRS`, based on your redundancy and performance needs.\n\nBy following these steps, you'll create a new Azure Storage account in the specified resource group and location with the specified SKU.","annotations":[],"logprobs":[]}]}],"parallel_tool_calls":true,"status":"completed"}
47+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
4+
from agent_framework import HostedMCPTool
5+
from agent_framework.openai import OpenAIChatClient
6+
from azure.ai.agentserver.agentframework import from_agent_framework # pyright: ignore[reportUnknownVariableType]
7+
8+
9+
def main():
10+
# Create an Agent using the OpenAI Chat Client with a MCP Tool that connects to Microsoft Learn MCP
11+
agent = OpenAIChatClient().create_agent(
12+
name="DocsAgent",
13+
instructions="You are a helpful assistant that can help with microsoft documentation questions.",
14+
tools=HostedMCPTool(
15+
name="Microsoft Learn MCP",
16+
url="https://learn.microsoft.com/api/mcp",
17+
),
18+
)
19+
20+
# Run the agent as a hosted agent
21+
from_agent_framework(agent).run()
22+
23+
24+
if __name__ == "__main__":
25+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
azure-ai-agentserver-agentframework==1.0.0b3
2+
agent-framework
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OPENAI_CHAT_MODEL_ID="gpt-4o-2024-08-06"
2+
OPENAI_API_KEY="your-openai-api-key"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY . user_agent/
6+
WORKDIR /app/user_agent
7+
8+
RUN if [ -f requirements.txt ]; then \
9+
pip install -r requirements.txt; \
10+
else \
11+
echo "No requirements.txt found"; \
12+
fi
13+
14+
EXPOSE 8088
15+
16+
CMD ["python", "main.py"]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Hosted Agents with Text Search RAG Demo
2+
3+
This demo showcases an agent that uses Retrieval-Augmented Generation (RAG) with text search capabilities that will be hosted as an agent endpoint running locally in a Docker container.
4+
5+
## What the Project Does
6+
7+
This project demonstrates how to:
8+
9+
- Build a customer support agent using the Agent Framework
10+
- Implement a custom `TextSearchContextProvider` that simulates document retrieval
11+
- Host the agent as an agent endpoint running in a Docker container
12+
13+
The agent responds to customer inquiries about:
14+
15+
- **Return & Refund Policies** - Triggered by keywords: "return", "refund"
16+
- **Shipping Information** - Triggered by keyword: "shipping"
17+
- **Product Care Instructions** - Triggered by keywords: "tent", "fabric"
18+
19+
## Prerequisites
20+
21+
- OpenAI API access and credentials
22+
- Required environment variables (see Configuration section)
23+
24+
## Configuration
25+
26+
Follow the `.env.example` file to set up the necessary environment variables for OpenAI.
27+
28+
## Docker Deployment
29+
30+
Build and run using Docker:
31+
32+
```bash
33+
# Build the Docker image
34+
docker build -t hosted-agent-rag .
35+
36+
# Run the container
37+
docker run -p 8088:8088 hosted-agent-rag
38+
```
39+
40+
> If you update the environment variables in the `.env` file or change the code or the dockerfile, make sure to rebuild the Docker image to apply the changes.
41+
42+
## Testing the Agent
43+
44+
Once the agent is running, you can test it by sending queries that contain the trigger keywords. For example:
45+
46+
```bash
47+
curl -sS -H "Content-Type: application/json" -X POST http://localhost:8088/responses -d '{"input": "What is the return policy","stream":false}'
48+
```
49+
50+
Expected response:
51+
52+
```bash
53+
{"object":"response","metadata":{},"agent":null,"conversation":{"id":"conv_2GbSxDpJJ89B6N4FQkKhrHaz78Hjtxy9b30JEPuY9YFjJM0uw3"},"type":"message","role":"assistant","temperature":1.0,"top_p":1.0,"user":"","id":"resp_Bvffxq0iIzlVkx2I8x7hV4fglm9RBPWfMCpNtEpDT6ciV2IG6z","created_at":1763071467,"output":[{"id":"msg_2GbSxDpJJ89B6N4FQknLsnxkwwFS2FULJqRV9jMey2BOXljqUz","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"As of the most recent update, Contoso Outdoors' return policy allows customers to return products within 30 days of purchase for a full refund or exchange, provided the items are in their original condition and packaging. However, make sure to check your purchase receipt or the company's website for the most updated and specific details, as policies can vary by location and may change over time.","annotations":[],"logprobs":[]}]}],"parallel_tool_calls":true,"status":"completed"}
54+
```
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import json
4+
import sys
5+
from collections.abc import MutableSequence
6+
from dataclasses import dataclass
7+
from typing import Any
8+
9+
from agent_framework import ChatMessage, Context, ContextProvider, Role
10+
from agent_framework.openai import OpenAIChatClient
11+
from azure.ai.agentserver.agentframework import from_agent_framework # pyright: ignore[reportUnknownVariableType]
12+
13+
if sys.version_info >= (3, 12):
14+
from typing import override
15+
else:
16+
from typing_extensions import override
17+
18+
19+
@dataclass
20+
class TextSearchResult:
21+
source_name: str
22+
source_link: str
23+
text: str
24+
25+
26+
class TextSearchContextProvider(ContextProvider):
27+
"""A simple context provider that simulates text search results based on keywords in the user's message."""
28+
29+
def _get_most_recent_message(self, messages: ChatMessage | MutableSequence[ChatMessage]) -> ChatMessage:
30+
"""Helper method to extract the most recent message from the input."""
31+
if isinstance(messages, ChatMessage):
32+
return messages
33+
if messages:
34+
return messages[-1]
35+
raise ValueError("No messages provided")
36+
37+
@override
38+
async def invoking(self, messages: ChatMessage | MutableSequence[ChatMessage], **kwargs: Any) -> Context:
39+
message = self._get_most_recent_message(messages)
40+
query = message.text.lower()
41+
42+
results: list[TextSearchResult] = []
43+
if "return" in query and "refund" in query:
44+
results.append(
45+
TextSearchResult(
46+
source_name="Contoso Outdoors Return Policy",
47+
source_link="https://contoso.com/policies/returns",
48+
text=(
49+
"Customers may return any item within 30 days of delivery. "
50+
"Items should be unused and include original packaging. "
51+
"Refunds are issued to the original payment method within 5 business days of inspection."
52+
),
53+
)
54+
)
55+
56+
if "shipping" in query:
57+
results.append(
58+
TextSearchResult(
59+
source_name="Contoso Outdoors Shipping Guide",
60+
source_link="https://contoso.com/help/shipping",
61+
text=(
62+
"Standard shipping is free on orders over $50 and typically arrives in 3-5 business days "
63+
"within the continental United States. Expedited options are available at checkout."
64+
),
65+
)
66+
)
67+
68+
if "tent" in query or "fabric" in query:
69+
results.append(
70+
TextSearchResult(
71+
source_name="TrailRunner Tent Care Instructions",
72+
source_link="https://contoso.com/manuals/trailrunner-tent",
73+
text=(
74+
"Clean the tent fabric with lukewarm water and a non-detergent soap. "
75+
"Allow it to air dry completely before storage and avoid prolonged UV "
76+
"exposure to extend the lifespan of the waterproof coating."
77+
),
78+
)
79+
)
80+
81+
if not results:
82+
return Context()
83+
84+
return Context(
85+
messages=[
86+
ChatMessage(
87+
role=Role.USER, text="\n\n".join(json.dumps(result.__dict__, indent=2) for result in results)
88+
)
89+
]
90+
)
91+
92+
93+
def main():
94+
# Create an Agent using the OpenAI Chat Client
95+
agent = OpenAIChatClient().create_agent(
96+
name="SupportSpecialist",
97+
instructions=(
98+
"You are a helpful support specialist for Contoso Outdoors. "
99+
"Answer questions using the provided context and cite the source document when available."
100+
),
101+
context_providers=TextSearchContextProvider(),
102+
)
103+
104+
# Run the agent as a hosted agent
105+
from_agent_framework(agent).run()
106+
107+
108+
if __name__ == "__main__":
109+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
azure-ai-agentserver-agentframework==1.0.0b3
2+
agent-framework

0 commit comments

Comments
 (0)