mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix tool normalization and provider sample consolidation (#3953)
* Fix tool normalization and provider samples - restore callable/single-tool normalization paths and unset tool-choice behavior\n- consolidate and expand chat/provider samples (OpenAI/Azure/Anthropic/Ollama/Bedrock)\n- migrate Bedrock lazy import surface to agent_framework.amazon and move provider samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * small fix in sample * Finalize provider, samples, and core cleanup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CopilotTool passthrough in agent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix link --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
ed113f941c
commit
aab621f5eb
+2
@@ -75,6 +75,7 @@ async def main() -> None:
|
||||
if knowledge_base_name:
|
||||
# Use existing Knowledge Base - simplest approach
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
api_key=search_key,
|
||||
credential=AzureCliCredential() if not search_key else None,
|
||||
@@ -91,6 +92,7 @@ async def main() -> None:
|
||||
if not azure_openai_resource_url:
|
||||
raise ValueError("AZURE_OPENAI_RESOURCE_URL required when using index_name")
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
index_name=index_name,
|
||||
api_key=search_key,
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ async def main() -> None:
|
||||
# Create Azure AI Search context provider with semantic mode (recommended, fast)
|
||||
print("Using SEMANTIC mode (hybrid search + semantic ranking, fast)\n")
|
||||
search_provider = AzureAISearchContextProvider(
|
||||
source_id="search_provider",
|
||||
endpoint=search_endpoint,
|
||||
index_name=index_name,
|
||||
api_key=search_key, # Use api_key for API key auth, or credential for managed identity
|
||||
|
||||
@@ -39,7 +39,7 @@ async def main() -> None:
|
||||
name="FriendlyAssistant",
|
||||
instructions="You are a friendly assistant.",
|
||||
tools=retrieve_company_report,
|
||||
context_providers=[Mem0ContextProvider(user_id=user_id)],
|
||||
context_providers=[Mem0ContextProvider(source_id="mem0", user_id=user_id)],
|
||||
) as agent,
|
||||
):
|
||||
# First ask the agent to retrieve a company report with no previous context.
|
||||
|
||||
@@ -10,7 +10,9 @@ from azure.identity.aio import AzureCliCredential
|
||||
from mem0 import AsyncMemory
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def retrieve_company_report(company_code: str, detailed: bool) -> str:
|
||||
if company_code != "CNTS":
|
||||
@@ -42,7 +44,7 @@ async def main() -> None:
|
||||
name="FriendlyAssistant",
|
||||
instructions="You are a friendly assistant.",
|
||||
tools=retrieve_company_report,
|
||||
context_providers=[Mem0ContextProvider(user_id=user_id, mem0_client=local_mem0_client)],
|
||||
context_providers=[Mem0ContextProvider(source_id="mem0", user_id=user_id, mem0_client=local_mem0_client)],
|
||||
) as agent,
|
||||
):
|
||||
# First ask the agent to retrieve a company report with no previous context.
|
||||
|
||||
@@ -34,11 +34,14 @@ async def example_global_thread_scope() -> None:
|
||||
name="GlobalMemoryAssistant",
|
||||
instructions="You are an assistant that remembers user preferences across conversations.",
|
||||
tools=get_user_preferences,
|
||||
context_providers=[Mem0ContextProvider(
|
||||
user_id=user_id,
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
user_id=user_id,
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)
|
||||
],
|
||||
) as global_agent,
|
||||
):
|
||||
# Store some preferences in the global scope
|
||||
@@ -72,10 +75,13 @@ async def example_per_operation_thread_scope() -> None:
|
||||
name="ScopedMemoryAssistant",
|
||||
instructions="You are an assistant with thread-scoped memory.",
|
||||
tools=get_user_preferences,
|
||||
context_providers=[Mem0ContextProvider(
|
||||
user_id=user_id,
|
||||
scope_to_per_operation_thread_id=True, # Isolate memories per session
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
user_id=user_id,
|
||||
scope_to_per_operation_thread_id=True, # Isolate memories per session
|
||||
)
|
||||
],
|
||||
) as scoped_agent,
|
||||
):
|
||||
# Create a specific session for this scoped provider
|
||||
@@ -119,16 +125,22 @@ async def example_multiple_agents() -> None:
|
||||
AzureAIAgentClient(credential=credential).as_agent(
|
||||
name="PersonalAssistant",
|
||||
instructions="You are a personal assistant that helps with personal tasks.",
|
||||
context_providers=[Mem0ContextProvider(
|
||||
agent_id=agent_id_1,
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
agent_id=agent_id_1,
|
||||
)
|
||||
],
|
||||
) as personal_agent,
|
||||
AzureAIAgentClient(credential=credential).as_agent(
|
||||
name="WorkAssistant",
|
||||
instructions="You are a work assistant that helps with professional tasks.",
|
||||
context_providers=[Mem0ContextProvider(
|
||||
agent_id=agent_id_2,
|
||||
)],
|
||||
context_providers=[
|
||||
Mem0ContextProvider(
|
||||
source_id="mem0",
|
||||
agent_id=agent_id_2,
|
||||
)
|
||||
],
|
||||
) as work_agent,
|
||||
):
|
||||
# Store personal information
|
||||
|
||||
@@ -20,7 +20,8 @@ This folder contains an example demonstrating how to use the Redis context provi
|
||||
|
||||
1. A running Redis with RediSearch (Redis Stack or a managed service)
|
||||
2. Python environment with Agent Framework Redis extra installed
|
||||
3. Optional: OpenAI API key if using vector embeddings
|
||||
3. Azure AI Foundry project endpoint and Azure OpenAI Responses deployment
|
||||
4. Optional: OpenAI API key if using vector embeddings
|
||||
|
||||
### Install the package
|
||||
|
||||
@@ -50,6 +51,8 @@ See quickstart: `https://learn.microsoft.com/azure/redis/quickstart-create-manag
|
||||
|
||||
### Environment variables
|
||||
|
||||
- `AZURE_AI_PROJECT_ENDPOINT` (required): Azure AI Foundry project endpoint for `AzureOpenAIResponsesClient`
|
||||
- `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` (required): Azure OpenAI Responses deployment name
|
||||
- `OPENAI_API_KEY` (optional): Required only if you set `vectorizer_choice="openai"` to enable hybrid search.
|
||||
|
||||
### Provider configuration highlights
|
||||
@@ -70,19 +73,26 @@ The provider supports both full‑text only and hybrid vector search:
|
||||
2. Agent integration: teaches the agent a preference and verifies it is remembered across turns.
|
||||
3. Agent + tool: calls a sample tool (flight search) and then asks the agent to recall details remembered from the tool output.
|
||||
|
||||
It uses OpenAI for both chat (via `OpenAIChatClient`) and, in some steps, optional embeddings for hybrid search.
|
||||
It uses `AzureOpenAIResponsesClient` (Foundry project endpoint setup) for chat and, in some steps, optional OpenAI embeddings for hybrid search.
|
||||
|
||||
## How to run
|
||||
|
||||
1) Start Redis (see options above). For local default, ensure it's reachable at `redis://localhost:6379`.
|
||||
|
||||
2) Set your OpenAI key if using embeddings and for the chat client used in the sample:
|
||||
2) Set Azure Foundry/OpenAI responses environment variables:
|
||||
|
||||
```bash
|
||||
export AZURE_AI_PROJECT_ENDPOINT="https://<resource>.services.ai.azure.com/api/projects/<project>"
|
||||
export AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME="<deployment-name>"
|
||||
```
|
||||
|
||||
3) (Optional) Set your OpenAI key if using embeddings:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="<your key>"
|
||||
```
|
||||
|
||||
3) Run the example:
|
||||
4) Run the example:
|
||||
|
||||
```bash
|
||||
python redis_basics.py
|
||||
@@ -109,5 +119,6 @@ You should see the agent responses and, when using embeddings, context retrieved
|
||||
## Troubleshooting
|
||||
|
||||
- Ensure at least one of `application_id`, `agent_id`, `user_id`, or `thread_id` is set; the provider requires a scope.
|
||||
- Verify `AZURE_AI_PROJECT_ENDPOINT` and `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` are set for the chat client.
|
||||
- If using embeddings, verify `OPENAI_API_KEY` is set and reachable.
|
||||
- Make sure Redis exposes RediSearch (Redis Stack image or managed service with search enabled).
|
||||
|
||||
@@ -13,24 +13,25 @@ Requirements:
|
||||
|
||||
Environment Variables:
|
||||
- AZURE_REDIS_HOST: Your Azure Managed Redis host (e.g., myredis.redis.cache.windows.net)
|
||||
- OPENAI_API_KEY: Your OpenAI API key
|
||||
- OPENAI_CHAT_MODEL_ID: OpenAI model (e.g., gpt-4o-mini)
|
||||
- AZURE_AI_PROJECT_ENDPOINT: Your Azure AI Foundry project endpoint
|
||||
- AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: Azure OpenAI Responses deployment name
|
||||
- AZURE_USER_OBJECT_ID: Your Azure AD User Object ID for authentication
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisHistoryProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from azure.identity import AzureCliCredential
|
||||
from azure.identity.aio import AzureCliCredential as AsyncAzureCliCredential
|
||||
from redis.credentials import CredentialProvider
|
||||
|
||||
|
||||
class AzureCredentialProvider(CredentialProvider):
|
||||
"""Credential provider for Azure AD authentication with Redis Enterprise."""
|
||||
|
||||
def __init__(self, azure_credential: AzureCliCredential, user_object_id: str):
|
||||
def __init__(self, azure_credential: AsyncAzureCliCredential, user_object_id: str):
|
||||
self.azure_credential = azure_credential
|
||||
self.user_object_id = user_object_id
|
||||
|
||||
@@ -57,24 +58,26 @@ async def main() -> None:
|
||||
return
|
||||
|
||||
# Create Azure CLI credential provider (uses 'az login' credentials)
|
||||
azure_credential = AzureCliCredential()
|
||||
azure_credential = AsyncAzureCliCredential()
|
||||
credential_provider = AzureCredentialProvider(azure_credential, user_object_id)
|
||||
|
||||
session_id = "azure_test_session"
|
||||
|
||||
# Create Azure Redis history provider
|
||||
history_provider = RedisHistoryProvider(
|
||||
source_id="redis_memory",
|
||||
credential_provider=credential_provider,
|
||||
host=redis_host,
|
||||
port=10000,
|
||||
ssl=True,
|
||||
thread_id=session_id,
|
||||
key_prefix="chat_messages",
|
||||
max_messages=100,
|
||||
)
|
||||
|
||||
# Create chat client
|
||||
client = OpenAIChatClient()
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create agent with Azure Redis history provider
|
||||
agent = client.as_agent(
|
||||
|
||||
@@ -31,13 +31,16 @@ import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import Message, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity.
|
||||
# Use "always_require" in production; see samples/02-agents/tools/function_tool_with_approval.py
|
||||
# and samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def search_flights(origin_airport_code: str, destination_airport_code: str, detailed: bool = False) -> str:
|
||||
"""Simulated flight-search tool to demonstrate tool memory.
|
||||
@@ -88,6 +91,15 @@ def search_flights(origin_airport_code: str, destination_airport_code: str, deta
|
||||
)
|
||||
|
||||
|
||||
def create_chat_client() -> AzureOpenAIResponsesClient:
|
||||
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Walk through provider-only, agent integration, and tool-memory scenarios.
|
||||
|
||||
@@ -100,8 +112,8 @@ async def main() -> None:
|
||||
print("-" * 40)
|
||||
# Create a provider with partition scope and OpenAI embeddings
|
||||
|
||||
# Please set the OPENAI_API_KEY and OPENAI_CHAT_MODEL_ID environment variables to use the OpenAI vectorizer
|
||||
# Recommend default for OPENAI_CHAT_MODEL_ID is gpt-4o-mini
|
||||
# Please set OPENAI_API_KEY to use the OpenAI vectorizer.
|
||||
# For chat responses, also set AZURE_AI_PROJECT_ENDPOINT and AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME.
|
||||
|
||||
# We attach an embedding vectorizer so the provider can perform hybrid (text + vector)
|
||||
# retrieval. If you prefer text-only retrieval, instantiate RedisContextProvider without the
|
||||
@@ -115,6 +127,7 @@ async def main() -> None:
|
||||
# scope data for multi-tenant separation; thread_id (set later) narrows to a
|
||||
# specific conversation.
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics",
|
||||
application_id="matrix_of_kermits",
|
||||
@@ -170,6 +183,7 @@ async def main() -> None:
|
||||
)
|
||||
# Recreate a clean index so the next scenario starts fresh
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics_2",
|
||||
prefix="context_2",
|
||||
@@ -183,7 +197,7 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
# Create chat client for the agent
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = create_chat_client()
|
||||
# Create agent wired to the Redis context provider. The provider automatically
|
||||
# persists conversational details and surfaces relevant context on each turn.
|
||||
agent = client.as_agent(
|
||||
@@ -217,6 +231,7 @@ async def main() -> None:
|
||||
print("-" * 40)
|
||||
# Text-only provider (full-text search only). Omits vectorizer and related params.
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_basics_3",
|
||||
prefix="context_3",
|
||||
@@ -227,7 +242,7 @@ async def main() -> None:
|
||||
|
||||
# Create agent exposing the flight search tool. Tool outputs are captured by the
|
||||
# provider and become retrievable context for later turns.
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = create_chat_client()
|
||||
agent = client.as_agent(
|
||||
name="MemoryEnhancedAssistant",
|
||||
instructions=(
|
||||
|
||||
@@ -17,8 +17,9 @@ Run:
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
@@ -36,9 +37,8 @@ async def main() -> None:
|
||||
cache=EmbeddingsCache(name="openai_embeddings_cache", redis_url="redis://localhost:6379"),
|
||||
)
|
||||
|
||||
session_id = "test_session"
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_conversation",
|
||||
prefix="redis_conversation",
|
||||
@@ -49,11 +49,14 @@ async def main() -> None:
|
||||
vector_field_name="vector",
|
||||
vector_algorithm="hnsw",
|
||||
vector_distance_metric="cosine",
|
||||
thread_id=session_id,
|
||||
)
|
||||
|
||||
# Create chat client for the agent
|
||||
client = OpenAIChatClient(model_id=os.getenv("OPENAI_CHAT_MODEL_ID"), api_key=os.getenv("OPENAI_API_KEY"))
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
# Create agent wired to the Redis context provider. The provider automatically
|
||||
# persists conversational details and surfaces relevant context on each turn.
|
||||
agent = client.as_agent(
|
||||
|
||||
@@ -28,15 +28,24 @@ Run:
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.redis import RedisContextProvider
|
||||
from azure.identity import AzureCliCredential
|
||||
from redisvl.extensions.cache.embeddings import EmbeddingsCache
|
||||
from redisvl.utils.vectorize import OpenAITextVectorizer
|
||||
|
||||
# Please set the OPENAI_API_KEY and OPENAI_CHAT_MODEL_ID environment variables to use the OpenAI vectorizer
|
||||
# Recommend default for OPENAI_CHAT_MODEL_ID is gpt-4o-mini
|
||||
# Please set OPENAI_API_KEY to use the OpenAI vectorizer.
|
||||
# For chat responses, also set AZURE_AI_PROJECT_ENDPOINT and AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME.
|
||||
|
||||
|
||||
def create_chat_client() -> AzureOpenAIResponsesClient:
|
||||
"""Create an Azure OpenAI Responses client using a Foundry project endpoint."""
|
||||
return AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
|
||||
async def example_global_thread_scope() -> None:
|
||||
@@ -44,20 +53,15 @@ async def example_global_thread_scope() -> None:
|
||||
print("1. Global Thread Scope Example:")
|
||||
print("-" * 40)
|
||||
|
||||
global_thread_id = str(uuid.uuid4())
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_global",
|
||||
application_id="threads_demo_app",
|
||||
agent_id="threads_demo_agent",
|
||||
user_id="threads_demo_user",
|
||||
thread_id=global_thread_id,
|
||||
scope_to_per_operation_thread_id=False, # Share memories across all sessions
|
||||
)
|
||||
|
||||
@@ -97,10 +101,7 @@ async def example_per_operation_thread_scope() -> None:
|
||||
print("2. Per-Operation Thread Scope Example:")
|
||||
print("-" * 40)
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
vectorizer = OpenAITextVectorizer(
|
||||
model="text-embedding-ada-002",
|
||||
@@ -109,6 +110,7 @@ async def example_per_operation_thread_scope() -> None:
|
||||
)
|
||||
|
||||
provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_dynamic",
|
||||
# overwrite_redis_index=True,
|
||||
@@ -165,10 +167,7 @@ async def example_multiple_agents() -> None:
|
||||
print("3. Multiple Agents with Different Thread Configurations:")
|
||||
print("-" * 40)
|
||||
|
||||
client = OpenAIChatClient(
|
||||
model_id=os.getenv("OPENAI_CHAT_MODEL_ID", "gpt-4o-mini"),
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
)
|
||||
client = create_chat_client()
|
||||
|
||||
vectorizer = OpenAITextVectorizer(
|
||||
model="text-embedding-ada-002",
|
||||
@@ -177,6 +176,7 @@ async def example_multiple_agents() -> None:
|
||||
)
|
||||
|
||||
personal_provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_agents",
|
||||
application_id="threads_demo_app",
|
||||
@@ -195,6 +195,7 @@ async def example_multiple_agents() -> None:
|
||||
)
|
||||
|
||||
work_provider = RedisContextProvider(
|
||||
source_id="redis_context",
|
||||
redis_url="redis://localhost:6379",
|
||||
index_name="redis_threads_agents",
|
||||
application_id="threads_demo_app",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import Agent, AgentSession, BaseContextProvider, SessionContext, SupportsChatGetResponse
|
||||
from agent_framework.azure import AzureAIClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -15,19 +17,13 @@ class UserInfo(BaseModel):
|
||||
|
||||
|
||||
class UserInfoMemory(BaseContextProvider):
|
||||
def __init__(self, client: SupportsChatGetResponse, user_info: UserInfo | None = None, **kwargs: Any):
|
||||
def __init__(self, source_id: str = "user-info-memory", *, client: SupportsChatGetResponse, **kwargs: Any):
|
||||
"""Create the memory.
|
||||
|
||||
If you pass in kwargs, they will be attempted to be used to create a UserInfo object.
|
||||
"""
|
||||
super().__init__("user-info-memory")
|
||||
super().__init__(source_id)
|
||||
self._chat_client = client
|
||||
if user_info:
|
||||
self.user_info = user_info
|
||||
elif kwargs:
|
||||
self.user_info = UserInfo.model_validate(kwargs)
|
||||
else:
|
||||
self.user_info = UserInfo()
|
||||
|
||||
async def after_run(
|
||||
self,
|
||||
@@ -38,12 +34,15 @@ class UserInfoMemory(BaseContextProvider):
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Extract user information from messages after each agent call."""
|
||||
request_messages = context.get_messages()
|
||||
# ensure you get all the messages you want to parse from, including the input in this case.
|
||||
request_messages = context.get_messages(include_input=True, include_response=True)
|
||||
# Check if we need to extract user info from user messages
|
||||
user_messages = [msg for msg in request_messages if hasattr(msg, "role") and msg.role == "user"] # type: ignore
|
||||
|
||||
if (self.user_info.name is None or self.user_info.age is None) and user_messages:
|
||||
try:
|
||||
if (
|
||||
state[self.source_id]["user_info"].name is None or state[self.source_id]["user_info"].age is None
|
||||
) and user_messages:
|
||||
with suppress(Exception):
|
||||
# Use the chat client to extract structured information
|
||||
result = await self._chat_client.get_response(
|
||||
messages=request_messages, # type: ignore
|
||||
@@ -53,17 +52,12 @@ class UserInfoMemory(BaseContextProvider):
|
||||
)
|
||||
|
||||
# Update user info with extracted data
|
||||
try:
|
||||
with suppress(Exception):
|
||||
extracted = result.value
|
||||
if self.user_info.name is None and extracted.name:
|
||||
self.user_info.name = extracted.name
|
||||
if self.user_info.age is None and extracted.age:
|
||||
self.user_info.age = extracted.age
|
||||
except Exception:
|
||||
pass # Failed to extract, continue without updating
|
||||
|
||||
except Exception:
|
||||
pass # Failed to extract, continue without updating
|
||||
if state[self.source_id]["user_info"].name is None and extracted.name:
|
||||
state[self.source_id]["user_info"].name = extracted.name
|
||||
if state[self.source_id]["user_info"].age is None and extracted.age:
|
||||
state[self.source_id]["user_info"].age = extracted.age
|
||||
|
||||
async def before_run(
|
||||
self,
|
||||
@@ -74,55 +68,52 @@ class UserInfoMemory(BaseContextProvider):
|
||||
state: dict[str, Any],
|
||||
) -> None:
|
||||
"""Provide user information context before each agent call."""
|
||||
instructions: list[str] = []
|
||||
if state.setdefault(self.source_id, None) is None:
|
||||
state[self.source_id] = {"user_info": UserInfo()}
|
||||
|
||||
if self.user_info.name is None:
|
||||
instructions.append(
|
||||
"Ask the user for their name and politely decline to answer any questions until they provide it."
|
||||
)
|
||||
else:
|
||||
instructions.append(f"The user's name is {self.user_info.name}.")
|
||||
|
||||
if self.user_info.age is None:
|
||||
instructions.append(
|
||||
"Ask the user for their age and politely decline to answer any questions until they provide it."
|
||||
)
|
||||
else:
|
||||
instructions.append(f"The user's age is {self.user_info.age}.")
|
||||
|
||||
# Add context with additional instructions
|
||||
context.extend_instructions(self.source_id, " ".join(instructions))
|
||||
|
||||
def serialize(self) -> str:
|
||||
"""Serialize the user info for session persistence."""
|
||||
return self.user_info.model_dump_json()
|
||||
context.extend_instructions(
|
||||
self.source_id,
|
||||
"Ask the user for their name and politely decline to answer any questions until they provide it."
|
||||
if state[self.source_id]["user_info"].name is None
|
||||
else f"The user's name is {state[self.source_id]['user_info'].name}.",
|
||||
)
|
||||
context.extend_instructions(
|
||||
self.source_id,
|
||||
"Ask the user for their age and politely decline to answer any questions until they provide it."
|
||||
if state[self.source_id]["user_info"].age is None
|
||||
else f"The user's age is {state[self.source_id]['user_info'].age}.",
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
async with AzureCliCredential() as credential:
|
||||
client = AzureAIClient(credential=credential)
|
||||
client = AzureOpenAIResponsesClient(
|
||||
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
|
||||
deployment_name=os.environ["AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME"],
|
||||
credential=AzureCliCredential(),
|
||||
)
|
||||
|
||||
# Create the memory provider
|
||||
memory_provider = UserInfoMemory(client)
|
||||
context_name = "user-info-memory"
|
||||
|
||||
# Create the agent with memory
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a friendly assistant. Always address the user by their name.",
|
||||
context_providers=[memory_provider],
|
||||
) as agent:
|
||||
# Create a new session for the conversation
|
||||
session = agent.create_session()
|
||||
# Create the memory provider
|
||||
memory_provider = UserInfoMemory(context_name, client=client)
|
||||
|
||||
print(await agent.run("Hello, what is the square root of 9?", session=session))
|
||||
print(await agent.run("My name is Ruaidhrí", session=session))
|
||||
print(await agent.run("I am 20 years old", session=session))
|
||||
# Create the agent with memory
|
||||
async with Agent(
|
||||
client=client,
|
||||
instructions="You are a friendly assistant. Always address the user by their name.",
|
||||
context_providers=[memory_provider],
|
||||
) as agent:
|
||||
# Create a new session for the conversation
|
||||
session = agent.create_session()
|
||||
|
||||
# Access the memory component and inspect the memories
|
||||
if memory_provider:
|
||||
print()
|
||||
print(f"MEMORY - User Name: {memory_provider.user_info.name}")
|
||||
print(f"MEMORY - User Age: {memory_provider.user_info.age}")
|
||||
for msg in ["Hello, what is the square root of 9?", "My name is Ruaidhrí", "I am 20 years old"]:
|
||||
print(f"User: {msg}")
|
||||
print(f"Assistant: {await agent.run(msg, session=session)}")
|
||||
|
||||
# Access the memory component and inspect the memories
|
||||
print()
|
||||
print(f"MEMORY - User Name: {session.state[context_name]['user_info'].name}")
|
||||
print(f"MEMORY - User Age: {session.state[context_name]['user_info'].age}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user