db620d33df
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
145 lines
4.8 KiB
Python
145 lines
4.8 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
"""AG-UI server example with server-side tools."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
from agent_framework import Agent, tool
|
|
from agent_framework.ag_ui import add_agent_framework_fastapi_endpoint
|
|
from agent_framework.openai import OpenAIChatCompletionClient
|
|
from dotenv import load_dotenv
|
|
from fastapi import Depends, FastAPI, HTTPException, Security
|
|
from fastapi.security import APIKeyHeader
|
|
|
|
load_dotenv()
|
|
|
|
# Enable debug logging
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Read required configuration
|
|
endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
|
|
model = os.environ.get("AZURE_OPENAI_MODEL")
|
|
|
|
if not endpoint:
|
|
raise ValueError("AZURE_OPENAI_ENDPOINT environment variable is required")
|
|
if not model:
|
|
raise ValueError("AZURE_OPENAI_MODEL environment variable is required")
|
|
|
|
|
|
# ============================================================================
|
|
# AUTHENTICATION EXAMPLE
|
|
# ============================================================================
|
|
# This demonstrates how to secure the AG-UI endpoint with API key authentication.
|
|
# In production, you should use a more robust authentication mechanism such as:
|
|
# - OAuth 2.0 / OpenID Connect
|
|
# - JWT tokens with proper validation
|
|
# - Azure AD / Entra ID integration
|
|
# - Your organization's identity provider
|
|
#
|
|
# The API key should be stored securely (e.g., Azure Key Vault, environment variables)
|
|
# and rotated regularly.
|
|
# ============================================================================
|
|
|
|
# API key header configuration
|
|
API_KEY_HEADER = APIKeyHeader(name="X-API-Key", auto_error=False)
|
|
|
|
# Get the expected API key from environment variable
|
|
# In production, use a secrets manager like Azure Key Vault
|
|
EXPECTED_API_KEY = os.environ.get("AG_UI_API_KEY")
|
|
|
|
|
|
async def verify_api_key(api_key: str | None = Security(API_KEY_HEADER)) -> None:
|
|
"""Verify the API key provided in the request header.
|
|
|
|
Args:
|
|
api_key: The API key from the X-API-Key header
|
|
|
|
Raises:
|
|
HTTPException: If the API key is missing or invalid
|
|
"""
|
|
if not EXPECTED_API_KEY:
|
|
# If no API key is configured, log a warning but allow the request
|
|
# This maintains backward compatibility but warns about the security risk
|
|
logger.warning(
|
|
"AG_UI_API_KEY environment variable not set. "
|
|
"The endpoint is accessible without authentication. "
|
|
"Set AG_UI_API_KEY to enable API key authentication."
|
|
)
|
|
return
|
|
|
|
if not api_key:
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="Missing API key. Provide X-API-Key header.",
|
|
)
|
|
|
|
if api_key != EXPECTED_API_KEY:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="Invalid API key.",
|
|
)
|
|
|
|
|
|
# Server-side tool (executes on server)
|
|
@tool(description="Get the time zone for a location.")
|
|
def get_time_zone(location: str) -> str:
|
|
"""Get the time zone for a location.
|
|
|
|
Args:
|
|
location: The city or location name
|
|
"""
|
|
print(f"[SERVER] get_time_zone tool called with location: {location}")
|
|
timezone_data = {
|
|
"seattle": "Pacific Time (UTC-8)",
|
|
"san francisco": "Pacific Time (UTC-8)",
|
|
"new york": "Eastern Time (UTC-5)",
|
|
"london": "Greenwich Mean Time (UTC+0)",
|
|
}
|
|
result = timezone_data.get(location.lower(), f"Time zone data not available for {location}")
|
|
print(f"[SERVER] get_time_zone returning: {result}")
|
|
return result
|
|
|
|
|
|
# Create the AI agent with ONLY server-side tools
|
|
# IMPORTANT: Do NOT include tools that the client provides!
|
|
# In this example:
|
|
# - get_time_zone: SERVER-ONLY tool (only server has this)
|
|
# - get_weather: CLIENT-ONLY tool (client provides this, server should NOT include it)
|
|
# The client will send get_weather tool metadata so the LLM knows about it,
|
|
# and the function invocation mixin on AGUIChatClient will execute it client-side.
|
|
# This matches the .NET AG-UI hybrid execution pattern.
|
|
agent = Agent(
|
|
name="AGUIAssistant",
|
|
instructions="You are a helpful assistant. Use get_weather for weather and get_time_zone for time zones.",
|
|
client=OpenAIChatCompletionClient(
|
|
azure_endpoint=endpoint,
|
|
model=model,
|
|
),
|
|
tools=[get_time_zone], # ONLY server-side tools
|
|
)
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(title="AG-UI Server")
|
|
|
|
# Register the AG-UI endpoint with authentication
|
|
# The dependencies parameter accepts FastAPI Depends() objects that run before the handler
|
|
add_agent_framework_fastapi_endpoint(
|
|
app,
|
|
agent,
|
|
"/",
|
|
dependencies=[Depends(verify_api_key)],
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(app, host="127.0.0.1", port=5100, log_level="debug", access_log=True)
|