mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] Types API Review improvements (#3647)
* Replace Role and FinishReason classes with NewType + Literal
- Remove EnumLike metaclass from _types.py
- Replace Role class with NewType('Role', str) + RoleLiteral
- Replace FinishReason class with NewType('FinishReason', str) + FinishReasonLiteral
- Update all usages across codebase to use string literals
- Remove .value access patterns (direct string comparison now works)
- Add backward compatibility for legacy dict serialization format
- Update tests to reflect new string-based types
Addresses #3591, #3615
* Simplify ChatResponse and AgentResponse type hints (#3592)
- Remove overloads from ChatResponse.__init__
- Remove text parameter from ChatResponse.__init__
- Remove | dict[str, Any] from finish_reason and usage_details params
- Remove **kwargs from AgentResponse.__init__
- Both now accept ChatMessage | Sequence[ChatMessage] | None for messages
- Update docstrings and examples to reflect changes
- Fix tests that were using removed kwargs
- Fix Role type hint usage in ag-ui utils
* Remove text parameter from ChatResponseUpdate and AgentResponseUpdate (#3597)
- Remove text parameter from ChatResponseUpdate.__init__
- Remove text parameter from AgentResponseUpdate.__init__
- Remove **kwargs from both update classes
- Simplify contents parameter type to Sequence[Content] | None
- Update all usages to use contents=[Content.from_text(...)] pattern
- Fix imports in test files
- Update docstrings and examples
* Rename from_chat_response_updates to from_updates (#3593)
- ChatResponse.from_chat_response_updates → ChatResponse.from_updates
- ChatResponse.from_chat_response_generator → ChatResponse.from_update_generator
- AgentResponse.from_agent_run_response_updates → AgentResponse.from_updates
* Remove try_parse_value method from ChatResponse and AgentResponse (#3595)
- Remove try_parse_value method from ChatResponse
- Remove try_parse_value method from AgentResponse
- Remove try_parse_value calls from from_updates and from_update_generator methods
- Update samples to use try/except with response.value instead
- Update tests to use response.value pattern
- Users should now use response.value with try/except for safe parsing
* Add agent_id to AgentResponse and clarify author_name documentation (#3596)
- Add agent_id parameter to AgentResponse class
- Document that author_name is on ChatMessage objects, not responses
- Update ChatResponse docstring with author_name note
- Update AgentResponse docstring with author_name note
* Simplify ChatMessage.__init__ signature (#3618)
- Make contents a positional argument accepting Sequence[Content | str]
- Auto-convert strings in contents to TextContent
- Remove overloads, keep text kwarg for backward compatibility with serialization
- Update _parse_content_list to handle string items
- Update all usages across codebase to use new format: ChatMessage("role", ["text"])
* Allow Content as input on run and get_response
- Update prepare_messages and normalize_messages to accept Content
- Update type signatures in _agents.py and _clients.py
- Add tests for Content input handling
* Fix ChatMessage usage across packages and samples
Update all remaining ChatMessage(role=..., text=...) to use new
ChatMessage('role', ['text']) signature.
* Fix Role string usage and response format parsing
- Fix redis provider: remove .value access on string literals
- Fix durabletask ensure_response_format: set _response_format before accessing .value
* Fix ollama .value and ai_model_id issues, handle None in content list
- Fix ollama _chat_client: remove .value on string literals
- Fix ollama _chat_client: rename ai_model_id to model_id
- Fix _parse_content_list: skip None values gracefully
* Fix A2AAgent type signature to include Content
* Fix Role/FinishReason NewType dict annotations and improve test coverage to 95%
* Fix mypy errors for Role/FinishReason NewType usage
* Fix Role.TOOL and Role.ASSISTANT usage in _orchestrator_helpers.py
* Fix Role NewType usage in durabletask _models.py
This commit is contained in:
committed by
GitHub
Unverified
parent
ef798629e5
commit
838a7fd61d
@@ -4,8 +4,8 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework.anthropic import AnthropicClient
|
||||
from agent_framework import tool
|
||||
from agent_framework.anthropic import AnthropicClient
|
||||
|
||||
"""
|
||||
Anthropic Chat Agent Example
|
||||
@@ -13,6 +13,7 @@ Anthropic Chat Agent Example
|
||||
This sample demonstrates using Anthropic with an agent and a single custom tool.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent Basic Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates basic usage of AzureAIProjectAgentProvider.
|
||||
Shows both streaming and non-streaming responses with function tools.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -5,12 +5,12 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import AgentReference, PromptAgentDefinition
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Project Agent Provider Methods Example
|
||||
@@ -26,6 +26,7 @@ with different configurations, which is efficient for multi-agent scenarios.
|
||||
Each method returns a ChatAgent that can be used for conversations.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent Latest Version Example
|
||||
@@ -17,6 +17,7 @@ instead of creating a new agent version on each instantiation. The first call cr
|
||||
while subsequent calls with `get_agent()` reuse the latest agent version.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ import asyncio
|
||||
from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
HostedCodeInterpreterTool,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
+2
-1
@@ -4,11 +4,11 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent Existing Conversation Example
|
||||
@@ -16,6 +16,7 @@ Azure AI Agent Existing Conversation Example
|
||||
This sample demonstrates usage of AzureAIProjectAgentProvider with existing conversation created on service side.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIProjectAgentProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating Azure AI Agents with explicit configuration
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -25,10 +25,10 @@ async def handle_approvals_without_thread(query: str, agent: "AgentProtocol") ->
|
||||
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
|
||||
f" with arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
|
||||
new_inputs.append(ChatMessage("assistant", [user_input_needed]))
|
||||
user_approval = input("Approve function call? (y/n): ")
|
||||
new_inputs.append(
|
||||
ChatMessage(role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
ChatMessage("user", [user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
result = await agent.run(new_inputs, store=False)
|
||||
|
||||
@@ -41,12 +41,13 @@ async def main() -> None:
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
|
||||
if release_brief := result.try_parse_value(ReleaseBrief):
|
||||
try:
|
||||
release_brief = result.value
|
||||
print("Agent:")
|
||||
print(f"Feature: {release_brief.feature}")
|
||||
print(f"Benefit: {release_brief.benefit}")
|
||||
print(f"Launch date: {release_brief.launch_date}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Failed to parse response: {result.text}")
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent Basic Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates basic usage of AzureAIAgentsProvider to create agents w
|
||||
lifecycle management. Shows both streaming and non-streaming responses with function tools.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -5,11 +5,11 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent Provider Methods Example
|
||||
@@ -20,6 +20,7 @@ This sample demonstrates the methods available on the AzureAIAgentsProvider clas
|
||||
- as_agent(): Wrap an SDK Agent object without making HTTP calls
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
-1
@@ -7,7 +7,6 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileContent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
|
||||
+2
-1
@@ -5,11 +5,11 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent with Existing Thread Example
|
||||
@@ -18,6 +18,7 @@ This sample demonstrates working with pre-existing conversation threads
|
||||
by providing thread IDs for thread reuse patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating Azure AI Agents with explicit configuration
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-1
@@ -5,10 +5,10 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Agent with Function Tools Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates function tool integration with Azure AI Agents,
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -26,6 +27,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
+1
-1
@@ -34,9 +34,9 @@ To set up Bing Grounding:
|
||||
4. Copy the connection ID and set it as the BING_CONNECTION_ID environment variable
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
current_time = datetime.now(timezone.utc)
|
||||
|
||||
+6
-4
@@ -56,13 +56,14 @@ async def main() -> None:
|
||||
|
||||
result1 = await agent.run(query1)
|
||||
|
||||
if weather := result1.try_parse_value(WeatherInfo):
|
||||
try:
|
||||
weather = result1.value
|
||||
print("Agent:")
|
||||
print(f" Location: {weather.location}")
|
||||
print(f" Temperature: {weather.temperature}")
|
||||
print(f" Conditions: {weather.conditions}")
|
||||
print(f" Recommendation: {weather.recommendation}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Failed to parse response: {result1.text}")
|
||||
|
||||
# Request 2: Override response_format at runtime with CityInfo
|
||||
@@ -72,12 +73,13 @@ async def main() -> None:
|
||||
|
||||
result2 = await agent.run(query2, options={"response_format": CityInfo})
|
||||
|
||||
if city := result2.try_parse_value(CityInfo):
|
||||
try:
|
||||
city = result2.value
|
||||
print("Agent:")
|
||||
print(f" City: {city.city_name}")
|
||||
print(f" Population: {city.population}")
|
||||
print(f" Country: {city.country}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Failed to parse response: {result2.text}")
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, tool
|
||||
from agent_framework.azure import AzureAIAgentsProvider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates thread management with Azure AI Agents, comparing
|
||||
automatic thread creation with explicit thread management for persistent context.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Assistants Basic Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates basic usage of AzureOpenAIAssistantsClient with automat
|
||||
assistant lifecycle management, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-2
@@ -5,8 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential, get_bearer_token_provider
|
||||
from openai import AsyncAzureOpenAI
|
||||
@@ -19,6 +18,7 @@ This sample demonstrates working with pre-existing Azure OpenAI Assistants
|
||||
using existing assistant IDs rather than creating new ones.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Assistants with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating Azure OpenAI Assistants with explicit configur
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-2
@@ -5,8 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -18,6 +17,7 @@ This sample demonstrates function tool integration with Azure OpenAI Assistants,
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -27,6 +27,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates thread management with Azure OpenAI Assistants, compari
|
||||
automatic thread creation with explicit thread management for persistent context.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Chat Client Basic Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates basic usage of AzureOpenAIChatClient for direct chat-ba
|
||||
interactions, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Chat Client with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating Azure OpenAI Chat Client with explicit configu
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-2
@@ -5,8 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -18,6 +17,7 @@ This sample demonstrates function tool integration with Azure OpenAI Chat Client
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -27,6 +27,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageStore
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageStore, tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates thread management with Azure OpenAI Chat Client, compar
|
||||
automatic thread creation with explicit thread management for persistent context.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Responses Client Basic Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates basic usage of AzureOpenAIResponsesClient for structure
|
||||
response generation, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure OpenAI Responses Client with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating Azure OpenAI Responses Client with explicit co
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-2
@@ -5,8 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -18,6 +17,7 @@ This sample demonstrates function tool integration with Azure OpenAI Responses C
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -27,6 +27,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
+3
-3
@@ -30,10 +30,10 @@ async def handle_approvals_without_thread(query: str, agent: "AgentProtocol"):
|
||||
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
|
||||
f" with arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
|
||||
new_inputs.append(ChatMessage("assistant", [user_input_needed]))
|
||||
user_approval = input("Approve function call? (y/n): ")
|
||||
new_inputs.append(
|
||||
ChatMessage(role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
ChatMessage("user", [user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
result = await agent.run(new_inputs)
|
||||
@@ -71,7 +71,7 @@ async def handle_approvals_with_thread_streaming(query: str, agent: "AgentProtoc
|
||||
new_input_added = True
|
||||
while new_input_added:
|
||||
new_input_added = False
|
||||
new_input.append(ChatMessage(role="user", text=query))
|
||||
new_input.append(ChatMessage("user", [query]))
|
||||
async for update in agent.run_stream(new_input, thread=thread, store=True):
|
||||
if update.user_input_requests:
|
||||
for user_input_needed in update.user_input_requests:
|
||||
|
||||
+2
-2
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, ChatAgent, tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates thread management with Azure OpenAI Responses Client, c
|
||||
automatic thread creation with explicit thread management for persistent context.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -11,8 +11,6 @@ from agent_framework import (
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Content,
|
||||
Role,
|
||||
tool,
|
||||
)
|
||||
|
||||
"""
|
||||
@@ -77,8 +75,8 @@ class EchoAgent(BaseAgent):
|
||||
|
||||
if not normalized_messages:
|
||||
response_message = ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
contents=[Content.from_text(text="Hello! I'm a custom echo agent. Send me a message and I'll echo it back.")],
|
||||
"assistant",
|
||||
[Content.from_text(text="Hello! I'm a custom echo agent. Send me a message and I'll echo it back.")],
|
||||
)
|
||||
else:
|
||||
# For simplicity, echo the last user message
|
||||
@@ -88,7 +86,7 @@ class EchoAgent(BaseAgent):
|
||||
else:
|
||||
echo_text = f"{self.echo_prefix}[Non-text message received]"
|
||||
|
||||
response_message = ChatMessage(role=Role.ASSISTANT, contents=[Content.from_text(text=echo_text)])
|
||||
response_message = ChatMessage("assistant", [Content.from_text(text=echo_text)])
|
||||
|
||||
# Notify the thread of new messages if provided
|
||||
if thread is not None:
|
||||
@@ -134,7 +132,7 @@ class EchoAgent(BaseAgent):
|
||||
|
||||
yield AgentResponseUpdate(
|
||||
contents=[Content.from_text(text=chunk_text)],
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
)
|
||||
|
||||
# Small delay to simulate streaming
|
||||
@@ -142,7 +140,7 @@ class EchoAgent(BaseAgent):
|
||||
|
||||
# Notify the thread of the complete response if provided
|
||||
if thread is not None:
|
||||
complete_response = ChatMessage(role=Role.ASSISTANT, contents=[Content.from_text(text=response_text)])
|
||||
complete_response = ChatMessage("assistant", [Content.from_text(text=response_text)])
|
||||
await self._notify_thread_of_new_messages(thread, normalized_messages, complete_response)
|
||||
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ from agent_framework import (
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
Role,
|
||||
use_chat_middleware,
|
||||
use_function_invocation,
|
||||
tool,
|
||||
)
|
||||
from agent_framework._clients import TOptions_co
|
||||
|
||||
@@ -68,7 +66,7 @@ class EchoingChatClient(BaseChatClient[TOptions_co], Generic[TOptions_co]):
|
||||
# Echo the last user message
|
||||
last_user_message = None
|
||||
for message in reversed(messages):
|
||||
if message.role == Role.USER:
|
||||
if message.role == "user":
|
||||
last_user_message = message
|
||||
break
|
||||
|
||||
@@ -77,7 +75,7 @@ class EchoingChatClient(BaseChatClient[TOptions_co], Generic[TOptions_co]):
|
||||
else:
|
||||
response_text = f"{self.prefix} [No text message found]"
|
||||
|
||||
response_message = ChatMessage(role=Role.ASSISTANT, contents=[Content.from_text(text=response_text)])
|
||||
response_message = ChatMessage("assistant", [Content.from_text(text=response_text)])
|
||||
|
||||
return ChatResponse(
|
||||
messages=[response_message],
|
||||
@@ -104,7 +102,7 @@ class EchoingChatClient(BaseChatClient[TOptions_co], Generic[TOptions_co]):
|
||||
for char in response_text:
|
||||
yield ChatResponseUpdate(
|
||||
contents=[Content.from_text(text=char)],
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
response_id=f"echo-stream-resp-{random.randint(1000, 9999)}",
|
||||
model_id="echo-model-v1",
|
||||
)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
from agent_framework import tool
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
|
||||
"""
|
||||
Ollama Agent Basic Example
|
||||
@@ -18,6 +18,7 @@ https://ollama.com/
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time(location: str) -> str:
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
from agent_framework import tool
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
|
||||
"""
|
||||
Ollama Chat Client Example
|
||||
@@ -18,6 +18,7 @@ https://ollama.com/
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import ChatMessage, Content, Role
|
||||
from agent_framework import ChatMessage, Content
|
||||
from agent_framework.ollama import OllamaChatClient
|
||||
|
||||
"""
|
||||
@@ -33,7 +33,7 @@ async def test_image() -> None:
|
||||
image_uri = create_sample_image()
|
||||
|
||||
message = ChatMessage(
|
||||
role=Role.USER,
|
||||
role="user",
|
||||
contents=[
|
||||
Content.from_text(text="What's in this image?"),
|
||||
Content.from_uri(uri=image_uri, media_type="image/png"),
|
||||
|
||||
@@ -5,8 +5,8 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
"""
|
||||
Ollama with OpenAI Chat Client Example
|
||||
@@ -20,6 +20,7 @@ Environment Variables:
|
||||
- OLLAMA_MODEL: The model name to use (e.g., "mistral", "llama3.2", "phi3")
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistants Basic Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates basic usage of OpenAIAssistantProvider with automatic
|
||||
assistant lifecycle management, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistant Provider Methods Example
|
||||
@@ -19,6 +19,7 @@ This sample demonstrates the methods available on the OpenAIAssistantProvider cl
|
||||
- as_agent(): Wrap an SDK Assistant object without making HTTP calls
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistants with Existing Assistant Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates working with pre-existing OpenAI Assistants
|
||||
using the provider's get_agent() and as_agent() methods.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,10 +5,10 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistants with Explicit Settings Example
|
||||
@@ -17,6 +17,7 @@ This sample demonstrates creating OpenAI Assistants with explicit configuration
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-1
@@ -6,10 +6,10 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistants with Function Tools Example
|
||||
@@ -18,6 +18,7 @@ This sample demonstrates function tool integration with OpenAI Assistants,
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -27,6 +28,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
+6
-4
@@ -59,13 +59,14 @@ async def main() -> None:
|
||||
|
||||
result1 = await agent.run(query1)
|
||||
|
||||
if weather := result1.try_parse_value(WeatherInfo):
|
||||
try:
|
||||
weather = result1.value
|
||||
print("Agent:")
|
||||
print(f" Location: {weather.location}")
|
||||
print(f" Temperature: {weather.temperature}")
|
||||
print(f" Conditions: {weather.conditions}")
|
||||
print(f" Recommendation: {weather.recommendation}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Failed to parse response: {result1.text}")
|
||||
|
||||
# Request 2: Override response_format at runtime with CityInfo
|
||||
@@ -75,12 +76,13 @@ async def main() -> None:
|
||||
|
||||
result2 = await agent.run(query2, options={"response_format": CityInfo})
|
||||
|
||||
if city := result2.try_parse_value(CityInfo):
|
||||
try:
|
||||
city = result2.value
|
||||
print("Agent:")
|
||||
print(f" City: {city.city_name}")
|
||||
print(f" Population: {city.population}")
|
||||
print(f" Country: {city.country}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Failed to parse response: {result2.text}")
|
||||
finally:
|
||||
await client.beta.assistants.delete(agent.id)
|
||||
|
||||
@@ -5,8 +5,7 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, tool
|
||||
from agent_framework.openai import OpenAIAssistantProvider
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import Field
|
||||
@@ -18,6 +17,7 @@ This sample demonstrates thread management with OpenAI Assistants, showing
|
||||
persistent conversation threads and context preservation across interactions.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,8 +4,8 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
"""
|
||||
OpenAI Chat Client Basic Example
|
||||
@@ -14,6 +14,7 @@ This sample demonstrates basic usage of OpenAIChatClient for direct chat-based
|
||||
interactions, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+2
-1
@@ -5,9 +5,9 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Chat Client with Explicit Settings Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates creating OpenAI Chat Client with explicit configuration
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-2
@@ -5,8 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates function tool integration with OpenAI Chat Client,
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -26,6 +26,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageStore
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, ChatAgent, ChatMessageStore, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -16,6 +15,7 @@ This sample demonstrates thread management with OpenAI Chat Client, showing
|
||||
conversation threads and message history preservation across interactions.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -16,6 +15,7 @@ This sample demonstrates basic usage of OpenAIResponsesClient for structured
|
||||
response generation, showing both streaming and non-streaming responses.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ from agent_framework import (
|
||||
CodeInterpreterToolResultContent,
|
||||
Content,
|
||||
HostedCodeInterpreterTool,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
|
||||
|
||||
+2
-1
@@ -5,9 +5,9 @@ import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Responses Client with Explicit Settings Example
|
||||
@@ -16,6 +16,7 @@ This sample demonstrates creating OpenAI Responses Client with explicit configur
|
||||
settings rather than relying on environment variable defaults.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
+3
-2
@@ -5,8 +5,7 @@ from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -17,6 +16,7 @@ This sample demonstrates function tool integration with OpenAI Responses Client,
|
||||
showing both agent-level and query-level tool configuration patterns.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -26,6 +26,7 @@ def get_weather(
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time() -> str:
|
||||
"""Get the current UTC time."""
|
||||
|
||||
+3
-3
@@ -29,10 +29,10 @@ async def handle_approvals_without_thread(query: str, agent: "AgentProtocol"):
|
||||
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
|
||||
f" with arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
|
||||
new_inputs.append(ChatMessage("assistant", [user_input_needed]))
|
||||
user_approval = input("Approve function call? (y/n): ")
|
||||
new_inputs.append(
|
||||
ChatMessage(role="user", contents=[user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
ChatMessage("user", [user_input_needed.to_function_approval_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
result = await agent.run(new_inputs)
|
||||
@@ -70,7 +70,7 @@ async def handle_approvals_with_thread_streaming(query: str, agent: "AgentProtoc
|
||||
new_input_added = True
|
||||
while new_input_added:
|
||||
new_input_added = False
|
||||
new_input.append(ChatMessage(role="user", text=query))
|
||||
new_input.append(ChatMessage("user", [query]))
|
||||
async for update in agent.run_stream(new_input, thread=thread, store=True):
|
||||
if update.user_input_requests:
|
||||
for user_input_needed in update.user_input_requests:
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import AgentThread, ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import AgentThread, ChatAgent, tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
|
||||
@@ -16,6 +15,7 @@ This sample demonstrates thread management with OpenAI Responses Client, showing
|
||||
persistent conversation context and simplified response handling.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -11,12 +11,13 @@ Prerequisites: set `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_CHAT_DEPLOYMENT_NAM
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AgentFunctionApp, AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from agent_framework import tool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(location: str) -> dict[str, Any]:
|
||||
@@ -32,6 +33,7 @@ def get_weather(location: str) -> dict[str, Any]:
|
||||
logger.info(f"✓ [TOOL RESULT] {result}")
|
||||
return result
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def calculate_tip(bill_amount: float, tip_percentage: float = 15.0) -> dict[str, Any]:
|
||||
"""Calculate tip amount and total bill."""
|
||||
|
||||
+1
-1
@@ -8,9 +8,9 @@ as a message broker. It enables clients to disconnect and reconnect without losi
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from collections.abc import AsyncIterator
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
|
||||
@@ -153,13 +153,12 @@ def _get_weather_recommendation(condition: str) -> str:
|
||||
|
||||
if "rain" in condition_lower or "drizzle" in condition_lower:
|
||||
return "Bring an umbrella and waterproof jacket. Consider indoor activities for backup."
|
||||
elif "fog" in condition_lower:
|
||||
if "fog" in condition_lower:
|
||||
return "Morning visibility may be limited. Plan outdoor sightseeing for afternoon."
|
||||
elif "cold" in condition_lower:
|
||||
if "cold" in condition_lower:
|
||||
return "Layer up with warm clothing. Hot drinks and cozy cafés recommended."
|
||||
elif "hot" in condition_lower or "warm" in condition_lower:
|
||||
if "hot" in condition_lower or "warm" in condition_lower:
|
||||
return "Stay hydrated and use sunscreen. Plan strenuous activities for cooler morning hours."
|
||||
elif "thunder" in condition_lower or "storm" in condition_lower:
|
||||
if "thunder" in condition_lower or "storm" in condition_lower:
|
||||
return "Keep an eye on weather updates. Have indoor alternatives ready."
|
||||
else:
|
||||
return "Pleasant conditions expected. Great day for outdoor exploration!"
|
||||
return "Pleasant conditions expected. Great day for outdoor exploration!"
|
||||
|
||||
+8
-6
@@ -102,9 +102,10 @@ def spam_detection_orchestration(context: DurableOrchestrationContext) -> Genera
|
||||
options={"response_format": SpamDetectionResult},
|
||||
)
|
||||
|
||||
spam_result = spam_result_raw.try_parse_value(SpamDetectionResult)
|
||||
if spam_result is None:
|
||||
raise ValueError("Failed to parse spam detection result")
|
||||
try:
|
||||
spam_result = spam_result_raw.value
|
||||
except Exception as ex:
|
||||
raise ValueError("Failed to parse spam detection result") from ex
|
||||
|
||||
if spam_result.is_spam:
|
||||
result = yield context.call_activity("handle_spam_email", spam_result.reason) # type: ignore[misc]
|
||||
@@ -125,9 +126,10 @@ def spam_detection_orchestration(context: DurableOrchestrationContext) -> Genera
|
||||
options={"response_format": EmailResponse},
|
||||
)
|
||||
|
||||
email_result = email_result_raw.try_parse_value(EmailResponse)
|
||||
if email_result is None:
|
||||
raise ValueError("Failed to parse email response")
|
||||
try:
|
||||
email_result = email_result_raw.value
|
||||
except Exception as ex:
|
||||
raise ValueError("Failed to parse email response") from ex
|
||||
|
||||
result = yield context.call_activity("send_email", email_result.response) # type: ignore[misc]
|
||||
return result
|
||||
|
||||
+7
-6
@@ -137,11 +137,11 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext)
|
||||
context.set_custom_status(
|
||||
"Content rejected by human reviewer. Incorporating feedback and regenerating..."
|
||||
)
|
||||
|
||||
|
||||
# Check if we've exhausted attempts
|
||||
if attempt >= payload.max_review_attempts:
|
||||
break
|
||||
|
||||
|
||||
rewrite_prompt = (
|
||||
"The content was rejected by a human reviewer. Please rewrite the article incorporating their feedback.\n\n"
|
||||
f"Human Feedback: {approval_payload.feedback or 'No feedback provided.'}"
|
||||
@@ -152,9 +152,10 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext)
|
||||
options={"response_format": GeneratedContent},
|
||||
)
|
||||
|
||||
content = rewritten_raw.try_parse_value(GeneratedContent)
|
||||
if content is None:
|
||||
raise ValueError("Agent returned no content after rewrite.")
|
||||
try:
|
||||
content = rewritten_raw.value
|
||||
except Exception as ex:
|
||||
raise ValueError("Agent returned no content after rewrite.") from ex
|
||||
else:
|
||||
context.set_custom_status(
|
||||
f"Human approval timed out after {payload.approval_timeout_hours} hour(s). Treating as rejection."
|
||||
@@ -162,7 +163,7 @@ def content_generation_hitl_orchestration(context: DurableOrchestrationContext)
|
||||
raise TimeoutError(
|
||||
f"Human approval timed out after {payload.approval_timeout_hours} hour(s)."
|
||||
)
|
||||
|
||||
|
||||
# If we exit the loop without returning, max attempts were exhausted
|
||||
context.set_custom_status("Max review attempts exhausted.")
|
||||
raise RuntimeError(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure AI Chat Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Demonstrates direct AzureAIChatClient usage for chat interactions with Azure AI
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIAssistantsClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure Assistants Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Demonstrates direct AzureAssistantsClient usage for chat interactions with Azure
|
||||
Shows function calling capabilities and automatic assistant creation.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,10 +4,10 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
Azure Chat Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Demonstrates direct AzureChatClient usage for chat interactions with Azure OpenA
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,8 +4,7 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatResponse
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatResponse, tool
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from azure.identity import AzureCliCredential
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -17,6 +16,7 @@ Demonstrates direct AzureResponsesClient usage for structured response generatio
|
||||
Shows function calling capabilities with custom business logic.
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -42,19 +42,21 @@ async def main() -> None:
|
||||
stream = True
|
||||
print(f"User: {message}")
|
||||
if stream:
|
||||
response = await ChatResponse.from_chat_response_generator(
|
||||
response = await ChatResponse.from_update_generator(
|
||||
client.get_streaming_response(message, tools=get_weather, options={"response_format": OutputStruct}),
|
||||
output_format_type=OutputStruct,
|
||||
)
|
||||
if result := response.try_parse_value(OutputStruct):
|
||||
try:
|
||||
result = response.value
|
||||
print(f"Assistant: {result}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Assistant: {response.text}")
|
||||
else:
|
||||
response = await client.get_response(message, tools=get_weather, options={"response_format": OutputStruct})
|
||||
if result := response.try_parse_value(OutputStruct):
|
||||
try:
|
||||
result = response.value
|
||||
print(f"Assistant: {result}")
|
||||
else:
|
||||
except Exception:
|
||||
print(f"Assistant: {response.text}")
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIAssistantsClient
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Assistants Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Shows function calling capabilities and automatic assistant creation.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,9 +4,9 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Chat Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Shows function calling capabilities with custom business logic.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -4,9 +4,9 @@ import asyncio
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.openai import OpenAIResponsesClient
|
||||
from pydantic import Field
|
||||
from agent_framework import tool
|
||||
|
||||
"""
|
||||
OpenAI Responses Client Direct Usage Example
|
||||
@@ -16,6 +16,7 @@ Shows function calling capabilities with custom business logic.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from agent_framework.mem0 import Mem0Provider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework import tool
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from agent_framework.mem0 import Mem0Provider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from mem0 import AsyncMemory
|
||||
from agent_framework import tool
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from agent_framework import tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from agent_framework.mem0 import Mem0Provider
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from agent_framework import tool
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
|
||||
@@ -30,13 +30,13 @@ Run:
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agent_framework import ChatMessage, Role
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatMessage, tool
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework_redis._provider import RedisProvider
|
||||
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/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def search_flights(origin_airport_code: str, destination_airport_code: str, detailed: bool = False) -> str:
|
||||
@@ -128,9 +128,9 @@ async def main() -> None:
|
||||
|
||||
# Build sample chat messages to persist to Redis
|
||||
messages = [
|
||||
ChatMessage(role=Role.USER, text="runA CONVO: User Message"),
|
||||
ChatMessage(role=Role.ASSISTANT, text="runA CONVO: Assistant Message"),
|
||||
ChatMessage(role=Role.SYSTEM, text="runA CONVO: System Message"),
|
||||
ChatMessage("user", ["runA CONVO: User Message"]),
|
||||
ChatMessage("assistant", ["runA CONVO: Assistant Message"]),
|
||||
ChatMessage("system", ["runA CONVO: System Message"]),
|
||||
]
|
||||
|
||||
# Declare/start a conversation/thread and write messages under 'runA'.
|
||||
@@ -142,7 +142,7 @@ async def main() -> None:
|
||||
# Retrieve relevant memories for a hypothetical model call. The provider uses
|
||||
# the current request messages as the retrieval query and returns context to
|
||||
# be injected into the model's instructions.
|
||||
ctx = await provider.invoking([ChatMessage(role=Role.SYSTEM, text="B: Assistant Message")])
|
||||
ctx = await provider.invoking([ChatMessage("system", ["B: Assistant Message"])])
|
||||
|
||||
# Inspect retrieved memories that would be injected into instructions
|
||||
# (Debug-only output so you can verify retrieval works as expected.)
|
||||
|
||||
@@ -39,7 +39,7 @@ class UserInfoMemory(ContextProvider):
|
||||
) -> None:
|
||||
"""Extract user information from messages after each agent call."""
|
||||
# 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.value == "user"] # type: ignore
|
||||
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:
|
||||
@@ -52,11 +52,14 @@ class UserInfoMemory(ContextProvider):
|
||||
)
|
||||
|
||||
# Update user info with extracted data
|
||||
if extracted := result.try_parse_value(UserInfo):
|
||||
try:
|
||||
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
|
||||
|
||||
@@ -20,10 +20,11 @@ async def main():
|
||||
agent = AgentFactory(client_kwargs={"credential": AzureCliCredential()}).create_agent_from_yaml(yaml_str)
|
||||
# use the agent
|
||||
response = await agent.run("Why is the sky blue, answer in Dutch?")
|
||||
# Use try_parse_value() for safe parsing - returns None if no response_format or parsing fails
|
||||
if parsed := response.try_parse_value():
|
||||
# Use response.value with try/except for safe parsing
|
||||
try:
|
||||
parsed = response.value
|
||||
print("Agent response:", parsed.model_dump_json(indent=2))
|
||||
else:
|
||||
except Exception:
|
||||
print("Agent response:", response.text)
|
||||
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ async def main():
|
||||
agent = AgentFactory().create_agent_from_yaml(yaml_str)
|
||||
# use the agent
|
||||
response = await agent.run("Why is the sky blue, answer in Dutch?")
|
||||
# Use try_parse_value() for safe parsing - returns None if no response_format or parsing fails
|
||||
if parsed := response.try_parse_value():
|
||||
# Use response.value with try/except for safe parsing
|
||||
try:
|
||||
parsed = response.value
|
||||
print("Agent response:", parsed)
|
||||
else:
|
||||
except Exception:
|
||||
print("Agent response:", response.text)
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from agent_framework import (
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
tool,
|
||||
)
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Never
|
||||
|
||||
@@ -8,12 +8,12 @@ Make sure to run 'az login' before starting devui.
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, tool
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
@@ -24,6 +24,7 @@ def get_weather(
|
||||
temperature = 22
|
||||
return f"The weather in {location} is {conditions[0]} with a high of {temperature}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_forecast(
|
||||
location: Annotated[str, Field(description="The location to get the forecast for.")],
|
||||
|
||||
@@ -10,8 +10,7 @@ import logging
|
||||
import os
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent, Executor, WorkflowBuilder, WorkflowContext, handler
|
||||
from agent_framework import tool
|
||||
from agent_framework import ChatAgent, Executor, WorkflowBuilder, WorkflowContext, handler, tool
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.devui import serve
|
||||
from typing_extensions import Never
|
||||
@@ -29,6 +28,7 @@ def get_weather(
|
||||
temperature = 53
|
||||
return f"The weather in {location} is {conditions[0]} with a high of {temperature}°C."
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_time(
|
||||
timezone: Annotated[str, "The timezone to get time for."] = "UTC",
|
||||
|
||||
@@ -27,7 +27,6 @@ from agent_framework import (
|
||||
WorkflowContext,
|
||||
handler,
|
||||
response_handler,
|
||||
tool,
|
||||
)
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Never
|
||||
|
||||
@@ -14,10 +14,9 @@ from agent_framework import (
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
FunctionInvocationContext,
|
||||
Role,
|
||||
tool,
|
||||
chat_middleware,
|
||||
function_middleware,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework_devui import register_cleanup
|
||||
@@ -43,7 +42,7 @@ async def security_filter_middleware(
|
||||
|
||||
# Check only the last message (most recent user input)
|
||||
last_message = context.messages[-1] if context.messages else None
|
||||
if last_message and last_message.role == Role.USER and last_message.text:
|
||||
if last_message and last_message.role == "user" and last_message.text:
|
||||
message_lower = last_message.text.lower()
|
||||
for term in blocked_terms:
|
||||
if term in message_lower:
|
||||
@@ -58,7 +57,7 @@ async def security_filter_middleware(
|
||||
async def blocked_stream() -> AsyncIterable[ChatResponseUpdate]:
|
||||
yield ChatResponseUpdate(
|
||||
contents=[Content.from_text(text=error_message)],
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
)
|
||||
|
||||
context.result = blocked_stream()
|
||||
@@ -67,7 +66,7 @@ async def security_filter_middleware(
|
||||
context.result = ChatResponse(
|
||||
messages=[
|
||||
ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
role="assistant",
|
||||
text=error_message,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -40,12 +40,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
dts_client = DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -53,7 +53,7 @@ def get_client(
|
||||
token_credential=credential,
|
||||
log_handler=log_handler
|
||||
)
|
||||
|
||||
|
||||
return DurableAIAgentClient(dts_client)
|
||||
|
||||
|
||||
@@ -66,12 +66,12 @@ def run_client(agent_client: DurableAIAgentClient) -> None:
|
||||
# Get a reference to the Joker agent
|
||||
logger.debug("Getting reference to Joker agent...")
|
||||
joker = agent_client.get_agent("Joker")
|
||||
|
||||
|
||||
# Create a new thread for the conversation
|
||||
thread = joker.get_new_thread()
|
||||
logger.debug(f"Thread ID: {thread.session_id}")
|
||||
logger.info("Start chatting with the Joker agent! (Type 'exit' to quit)")
|
||||
|
||||
|
||||
# Interactive conversation loop
|
||||
while True:
|
||||
# Get user input
|
||||
@@ -80,33 +80,33 @@ def run_client(agent_client: DurableAIAgentClient) -> None:
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
logger.info("\nExiting...")
|
||||
break
|
||||
|
||||
|
||||
# Check for exit command
|
||||
if user_message.lower() == "exit":
|
||||
logger.info("Goodbye!")
|
||||
break
|
||||
|
||||
|
||||
# Skip empty messages
|
||||
if not user_message:
|
||||
continue
|
||||
|
||||
|
||||
# Send message to agent and get response
|
||||
try:
|
||||
response = joker.run(user_message, thread=thread)
|
||||
logger.info(f"Joker: {response.text} \n")
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting response: {e}")
|
||||
|
||||
|
||||
logger.info("Conversation completed.")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Agent Client...")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
agent_client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
|
||||
@@ -15,40 +15,40 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging (must be after imports to override their basicConfig)
|
||||
logging.basicConfig(level=logging.INFO, force=True)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Durable Task Agent Sample (Combined Worker + Client)...")
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
|
||||
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
agent_client = get_client(log_handler=silent_handler)
|
||||
|
||||
|
||||
try:
|
||||
# Run client interactions using helper function
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during agent interaction: {e}")
|
||||
|
||||
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
|
||||
@@ -52,12 +52,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -78,42 +78,42 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
|
||||
# Create and register the Joker agent
|
||||
logger.debug("Creating and registering Joker agent...")
|
||||
joker_agent = create_joker_agent()
|
||||
agent_worker.add_agent(joker_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agent: {joker_agent.name}")
|
||||
logger.debug(f" Entity name: dafx-{joker_agent.name}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Agent Worker...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agents
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
logger.info("Worker is ready and listening for requests...")
|
||||
logger.info("Press Ctrl+C to stop.")
|
||||
logger.info("")
|
||||
|
||||
|
||||
try:
|
||||
# Start the worker (this blocks until stopped)
|
||||
worker.start()
|
||||
|
||||
|
||||
# Keep the worker running
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
|
||||
logger.debug("Worker stopped")
|
||||
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
dts_client = DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -54,7 +54,7 @@ def get_client(
|
||||
token_credential=credential,
|
||||
log_handler=log_handler
|
||||
)
|
||||
|
||||
|
||||
return DurableAIAgentClient(dts_client)
|
||||
|
||||
|
||||
@@ -65,45 +65,45 @@ def run_client(agent_client: DurableAIAgentClient) -> None:
|
||||
agent_client: The DurableAIAgentClient instance
|
||||
"""
|
||||
logger.debug("Testing WeatherAgent")
|
||||
|
||||
|
||||
# Get reference to WeatherAgent
|
||||
weather_agent = agent_client.get_agent("WeatherAgent")
|
||||
weather_thread = weather_agent.get_new_thread()
|
||||
|
||||
|
||||
logger.debug(f"Created weather conversation thread: {weather_thread.session_id}")
|
||||
|
||||
|
||||
# Test WeatherAgent
|
||||
weather_message = "What is the weather in Seattle?"
|
||||
logger.info(f"User: {weather_message}")
|
||||
|
||||
|
||||
weather_response = weather_agent.run(weather_message, thread=weather_thread)
|
||||
logger.info(f"WeatherAgent: {weather_response.text} \n")
|
||||
|
||||
|
||||
logger.debug("Testing MathAgent")
|
||||
|
||||
|
||||
# Get reference to MathAgent
|
||||
math_agent = agent_client.get_agent("MathAgent")
|
||||
math_thread = math_agent.get_new_thread()
|
||||
|
||||
|
||||
logger.debug(f"Created math conversation thread: {math_thread.session_id}")
|
||||
|
||||
|
||||
# Test MathAgent
|
||||
math_message = "Calculate a 20% tip on a $50 bill"
|
||||
logger.info(f"User: {math_message}")
|
||||
|
||||
|
||||
math_response = math_agent.run(math_message, thread=math_thread)
|
||||
logger.info(f"MathAgent: {math_response.text} \n")
|
||||
|
||||
|
||||
logger.debug("Both agents completed successfully!")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Client...")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
agent_client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
|
||||
@@ -15,10 +15,9 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging
|
||||
@@ -29,26 +28,26 @@ logger = logging.getLogger(__name__)
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Sample (Combined Worker + Client)...")
|
||||
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
agent_client = get_client(log_handler=silent_handler)
|
||||
|
||||
|
||||
try:
|
||||
# Run client interactions using helper function
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during agent interaction: {e}")
|
||||
|
||||
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
|
||||
@@ -101,12 +101,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -127,43 +127,43 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
|
||||
# Create and register both agents
|
||||
logger.debug("Creating and registering agents...")
|
||||
weather_agent = create_weather_agent()
|
||||
math_agent = create_math_agent()
|
||||
|
||||
|
||||
agent_worker.add_agent(weather_agent)
|
||||
agent_worker.add_agent(math_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agents: {weather_agent.name}, {math_agent.name}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Worker...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agents
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
logger.info("Worker is ready and listening for requests...")
|
||||
logger.info("Press Ctrl+C to stop. \n")
|
||||
|
||||
|
||||
try:
|
||||
# Start the worker (this blocks until stopped)
|
||||
worker.start()
|
||||
|
||||
|
||||
# Keep the worker running
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
|
||||
logger.info("Worker stopped")
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import redis.asyncio as aioredis
|
||||
from agent_framework.azure import DurableAIAgentClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
|
||||
|
||||
from redis_stream_response_handler import RedisStreamResponseHandler
|
||||
|
||||
# Configure logging
|
||||
@@ -71,12 +70,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
dts_client = DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -84,7 +83,7 @@ def get_client(
|
||||
token_credential=credential,
|
||||
log_handler=log_handler
|
||||
)
|
||||
|
||||
|
||||
return DurableAIAgentClient(dts_client)
|
||||
|
||||
|
||||
@@ -100,33 +99,33 @@ async def stream_from_redis(thread_id: str, cursor: str | None = None) -> None:
|
||||
logger.debug(f"To manually check Redis, run: redis-cli XLEN {stream_key}")
|
||||
if cursor:
|
||||
logger.info(f"Resuming from cursor: {cursor}")
|
||||
|
||||
|
||||
async with await get_stream_handler() as stream_handler:
|
||||
logger.info(f"Stream handler created, starting to read...")
|
||||
logger.info("Stream handler created, starting to read...")
|
||||
try:
|
||||
chunk_count = 0
|
||||
async for chunk in stream_handler.read_stream(thread_id, cursor):
|
||||
chunk_count += 1
|
||||
logger.debug(f"Received chunk #{chunk_count}: error={chunk.error}, is_done={chunk.is_done}, text_len={len(chunk.text) if chunk.text else 0}")
|
||||
|
||||
|
||||
if chunk.error:
|
||||
logger.error(f"Stream error: {chunk.error}")
|
||||
break
|
||||
|
||||
|
||||
if chunk.is_done:
|
||||
print("\n✓ Response complete!", flush=True)
|
||||
logger.info(f"Stream completed after {chunk_count} chunks")
|
||||
break
|
||||
|
||||
|
||||
if chunk.text:
|
||||
# Print directly to console with flush for immediate display
|
||||
print(chunk.text, end='', flush=True)
|
||||
|
||||
print(chunk.text, end="", flush=True)
|
||||
|
||||
if chunk_count == 0:
|
||||
logger.warning("No chunks received from Redis stream!")
|
||||
logger.warning(f"Check Redis manually: redis-cli XLEN {stream_key}")
|
||||
logger.warning(f"View stream contents: redis-cli XREAD STREAMS {stream_key} 0")
|
||||
|
||||
|
||||
except Exception as ex:
|
||||
logger.error(f"Error reading from Redis: {ex}", exc_info=True)
|
||||
|
||||
@@ -140,47 +139,47 @@ def run_client(agent_client: DurableAIAgentClient) -> None:
|
||||
# Get a reference to the TravelPlanner agent
|
||||
logger.debug("Getting reference to TravelPlanner agent...")
|
||||
travel_planner = agent_client.get_agent("TravelPlanner")
|
||||
|
||||
|
||||
# Create a new thread for the conversation
|
||||
thread = travel_planner.get_new_thread()
|
||||
if not thread.session_id:
|
||||
logger.error("Failed to create a new thread with session ID!")
|
||||
return
|
||||
|
||||
|
||||
key = thread.session_id.key
|
||||
logger.info(f"Thread ID: {key}")
|
||||
|
||||
|
||||
# Get user input
|
||||
print("\nEnter your travel planning request:")
|
||||
user_message = input("> ").strip()
|
||||
|
||||
|
||||
if not user_message:
|
||||
logger.warning("No input provided. Using default message.")
|
||||
user_message = "Plan a 3-day trip to Tokyo with emphasis on culture and food"
|
||||
|
||||
|
||||
logger.info(f"\nYou: {user_message}\n")
|
||||
logger.info("TravelPlanner (streaming from Redis):")
|
||||
logger.info("-" * 80)
|
||||
|
||||
|
||||
# Start the agent run with wait_for_response=False for non-blocking execution
|
||||
# This signals the agent to start processing without waiting for completion
|
||||
# The agent will execute in the background and write chunks to Redis
|
||||
travel_planner.run(user_message, thread=thread, options={"wait_for_response": False})
|
||||
|
||||
|
||||
# Stream the response from Redis
|
||||
# This demonstrates that the client can stream from Redis while
|
||||
# the agent is still processing (or after it completes)
|
||||
asyncio.run(stream_from_redis(str(key)))
|
||||
|
||||
|
||||
logger.info("\nDemo completed!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Create the client
|
||||
client = get_client()
|
||||
|
||||
|
||||
# Run the demo
|
||||
run_client(client)
|
||||
|
||||
+1
-1
@@ -8,9 +8,9 @@ as a message broker. It enables clients to disconnect and reconnect without losi
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from collections.abc import AsyncIterator
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
|
||||
|
||||
@@ -20,40 +20,40 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging (must be after imports to override their basicConfig)
|
||||
logging.basicConfig(level=logging.INFO, force=True)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Durable Task Agent Sample with Redis Streaming...")
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
|
||||
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents and callbacks using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
agent_client = get_client(log_handler=silent_handler)
|
||||
|
||||
|
||||
try:
|
||||
# Run client interactions using helper function
|
||||
run_client(agent_client)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during agent interaction: {e}")
|
||||
|
||||
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
|
||||
@@ -153,13 +153,12 @@ def _get_weather_recommendation(condition: str) -> str:
|
||||
|
||||
if "rain" in condition_lower or "drizzle" in condition_lower:
|
||||
return "Bring an umbrella and waterproof jacket. Consider indoor activities for backup."
|
||||
elif "fog" in condition_lower:
|
||||
if "fog" in condition_lower:
|
||||
return "Morning visibility may be limited. Plan outdoor sightseeing for afternoon."
|
||||
elif "cold" in condition_lower:
|
||||
if "cold" in condition_lower:
|
||||
return "Layer up with warm clothing. Hot drinks and cozy cafés recommended."
|
||||
elif "hot" in condition_lower or "warm" in condition_lower:
|
||||
if "hot" in condition_lower or "warm" in condition_lower:
|
||||
return "Stay hydrated and use sunscreen. Plan strenuous activities for cooler morning hours."
|
||||
elif "thunder" in condition_lower or "storm" in condition_lower:
|
||||
if "thunder" in condition_lower or "storm" in condition_lower:
|
||||
return "Keep an eye on weather updates. Have indoor alternatives ready."
|
||||
else:
|
||||
return "Pleasant conditions expected. Great day for outdoor exploration!"
|
||||
return "Pleasant conditions expected. Great day for outdoor exploration!"
|
||||
|
||||
@@ -27,7 +27,6 @@ from agent_framework.azure import (
|
||||
)
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
|
||||
from redis_stream_response_handler import RedisStreamResponseHandler
|
||||
from tools import get_local_events, get_weather_forecast
|
||||
|
||||
@@ -186,12 +185,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -212,34 +211,34 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Create the Redis streaming callback
|
||||
redis_callback = RedisStreamCallback()
|
||||
|
||||
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker, callback=redis_callback)
|
||||
|
||||
|
||||
# Create and register the TravelPlanner agent
|
||||
logger.debug("Creating and registering TravelPlanner agent...")
|
||||
travel_agent = create_travel_agent()
|
||||
agent_worker.add_agent(travel_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agent: {travel_agent.name}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Agent Worker with Redis Streaming...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agent and callback
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
worker.start()
|
||||
|
||||
|
||||
try:
|
||||
# Keep the worker running
|
||||
while True:
|
||||
|
||||
+12
-12
@@ -41,12 +41,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -63,32 +63,32 @@ def run_client(client: DurableTaskSchedulerClient) -> None:
|
||||
client: The DurableTaskSchedulerClient instance
|
||||
"""
|
||||
logger.debug("Starting single agent chaining orchestration...")
|
||||
|
||||
|
||||
# Start the orchestration
|
||||
instance_id = client.schedule_new_orchestration( # type: ignore
|
||||
orchestrator="single_agent_chaining_orchestration",
|
||||
input="",
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"Orchestration started with instance ID: {instance_id}")
|
||||
logger.debug("Waiting for orchestration to complete...")
|
||||
|
||||
|
||||
# Retrieve the final state
|
||||
metadata = client.wait_for_orchestration_completion(
|
||||
instance_id=instance_id,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
|
||||
if metadata and metadata.runtime_status.name == "COMPLETED":
|
||||
result = metadata.serialized_output
|
||||
|
||||
|
||||
logger.debug("Orchestration completed successfully!")
|
||||
|
||||
|
||||
# Parse and display the result
|
||||
if result:
|
||||
final_text = json.loads(result)
|
||||
logger.info("Final refined sentence: %s \n", final_text)
|
||||
|
||||
|
||||
elif metadata:
|
||||
logger.error(f"Orchestration ended with status: {metadata.runtime_status.name}")
|
||||
if metadata.serialized_output:
|
||||
@@ -100,10 +100,10 @@ def run_client(client: DurableTaskSchedulerClient) -> None:
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Single Agent Chaining Orchestration Client...")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
run_client(client)
|
||||
except Exception as e:
|
||||
|
||||
+7
-8
@@ -21,10 +21,9 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging
|
||||
@@ -35,22 +34,22 @@ logger = logging.getLogger(__name__)
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Single Agent Orchestration Chaining Sample...")
|
||||
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents and orchestrations using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
client = get_client(log_handler=silent_handler)
|
||||
|
||||
|
||||
logger.debug("CLIENT: Starting orchestration...")
|
||||
|
||||
|
||||
# Run the client in the same process
|
||||
try:
|
||||
run_client(client)
|
||||
@@ -60,7 +59,7 @@ def main():
|
||||
logger.exception(f"Error during orchestration: {e}")
|
||||
finally:
|
||||
logger.debug("Worker stopping...")
|
||||
|
||||
|
||||
logger.debug("")
|
||||
logger.debug("Sample completed")
|
||||
|
||||
|
||||
+25
-25
@@ -11,15 +11,15 @@ Prerequisites:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
|
||||
from agent_framework import AgentResponse, ChatAgent
|
||||
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.task import OrchestrationContext, Task
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
from durabletask.task import OrchestrationContext, Task
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -42,7 +42,7 @@ def create_writer_agent() -> "ChatAgent":
|
||||
"You refine short pieces of text. When given an initial sentence you enhance it;\n"
|
||||
"when given an improved sentence you polish it further."
|
||||
)
|
||||
|
||||
|
||||
return AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent(
|
||||
name=WRITER_AGENT_NAME,
|
||||
instructions=instructions,
|
||||
@@ -78,18 +78,18 @@ def single_agent_chaining_orchestration(
|
||||
str: The final refined text from the second agent run
|
||||
"""
|
||||
logger.debug("[Orchestration] Starting single agent chaining...")
|
||||
|
||||
|
||||
# Wrap the orchestration context to access agents
|
||||
agent_context = DurableAIAgentOrchestrationContext(context)
|
||||
|
||||
|
||||
# Get the writer agent using the agent context
|
||||
writer = agent_context.get_agent(WRITER_AGENT_NAME)
|
||||
|
||||
|
||||
# Create a new thread for the conversation - this will be shared across both runs
|
||||
writer_thread = writer.get_new_thread()
|
||||
|
||||
|
||||
logger.debug(f"[Orchestration] Created thread: {writer_thread.session_id}")
|
||||
|
||||
|
||||
prompt = "Write a concise inspirational sentence about learning."
|
||||
# First run: Generate an initial inspirational sentence
|
||||
logger.info("[Orchestration] First agent run: Generating initial sentence about: %s", prompt)
|
||||
@@ -98,21 +98,21 @@ def single_agent_chaining_orchestration(
|
||||
thread=writer_thread,
|
||||
)
|
||||
logger.info(f"[Orchestration] Initial response: {initial_response.text}")
|
||||
|
||||
|
||||
# Second run: Refine the initial response on the same thread
|
||||
improved_prompt = (
|
||||
f"Improve this further while keeping it under 25 words: "
|
||||
f"{initial_response.text}"
|
||||
)
|
||||
|
||||
|
||||
logger.info("[Orchestration] Second agent run: Refining the sentence: %s", improved_prompt)
|
||||
refined_response = yield writer.run(
|
||||
messages=improved_prompt,
|
||||
thread=writer_thread,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"[Orchestration] Refined response: {refined_response.text}")
|
||||
|
||||
|
||||
logger.debug("[Orchestration] Chaining complete")
|
||||
return refined_response.text
|
||||
|
||||
@@ -134,12 +134,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -160,45 +160,45 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
|
||||
# Create and register the Writer agent
|
||||
logger.debug("Creating and registering Writer agent...")
|
||||
writer_agent = create_writer_agent()
|
||||
agent_worker.add_agent(writer_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agent: {writer_agent.name}")
|
||||
|
||||
|
||||
# Register the orchestration function
|
||||
logger.debug("Registering orchestration function...")
|
||||
worker.add_orchestrator(single_agent_chaining_orchestration) # type: ignore
|
||||
logger.debug(f"✓ Registered orchestration: {single_agent_chaining_orchestration.__name__}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Single Agent Chaining Worker with Orchestration...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agents and orchestrations
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
logger.debug("Worker is ready and listening for requests...")
|
||||
logger.debug("Press Ctrl+C to stop.")
|
||||
|
||||
|
||||
try:
|
||||
# Start the worker (this blocks until stopped)
|
||||
worker.start()
|
||||
|
||||
|
||||
# Keep the worker running
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
|
||||
logger.debug("Worker stopped")
|
||||
|
||||
|
||||
|
||||
+11
-11
@@ -41,12 +41,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -68,25 +68,25 @@ def run_client(client: DurableTaskSchedulerClient, prompt: str = "What is temper
|
||||
orchestrator="multi_agent_concurrent_orchestration",
|
||||
input=prompt,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"Orchestration started with instance ID: {instance_id}")
|
||||
logger.debug("Waiting for orchestration to complete...")
|
||||
|
||||
|
||||
# Retrieve the final state
|
||||
metadata = client.wait_for_orchestration_completion(
|
||||
instance_id=instance_id,
|
||||
)
|
||||
|
||||
|
||||
if metadata and metadata.runtime_status.name == "COMPLETED":
|
||||
result = metadata.serialized_output
|
||||
|
||||
|
||||
logger.debug("Orchestration completed successfully!")
|
||||
|
||||
|
||||
# Parse and display the result
|
||||
if result:
|
||||
result_json = json.loads(result) if isinstance(result, str) else result
|
||||
logger.info("Orchestration Results:\n%s", json.dumps(result_json, indent=2))
|
||||
|
||||
|
||||
elif metadata:
|
||||
logger.error(f"Orchestration ended with status: {metadata.runtime_status.name}")
|
||||
if metadata.serialized_output:
|
||||
@@ -98,10 +98,10 @@ def run_client(client: DurableTaskSchedulerClient, prompt: str = "What is temper
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Orchestration Client...")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
run_client(client)
|
||||
except Exception as e:
|
||||
|
||||
+6
-7
@@ -18,10 +18,9 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
# Configure logging
|
||||
@@ -32,20 +31,20 @@ logger = logging.getLogger(__name__)
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Orchestration Sample (Combined Worker + Client)...")
|
||||
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents and orchestrations using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
client = get_client(log_handler=silent_handler)
|
||||
|
||||
|
||||
# Define the prompt
|
||||
prompt = "What is temperature?"
|
||||
logger.debug("CLIENT: Starting orchestration...")
|
||||
@@ -55,7 +54,7 @@ def main():
|
||||
run_client(client, prompt)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during sample execution: {e}")
|
||||
|
||||
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
|
||||
+29
-29
@@ -11,16 +11,16 @@ Prerequisites:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import AgentResponse, ChatAgent
|
||||
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.task import OrchestrationContext, when_all, Task
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
from durabletask.task import OrchestrationContext, Task, when_all
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
@@ -68,44 +68,44 @@ def multi_agent_concurrent_orchestration(context: OrchestrationContext, prompt:
|
||||
Returns:
|
||||
dict: Dictionary with 'physicist' and 'chemist' response texts
|
||||
"""
|
||||
|
||||
|
||||
logger.info(f"[Orchestration] Starting concurrent execution for prompt: {prompt}")
|
||||
|
||||
|
||||
# Wrap the orchestration context to access agents
|
||||
agent_context = DurableAIAgentOrchestrationContext(context)
|
||||
|
||||
|
||||
# Get agents using the agent context (returns DurableAIAgent proxies)
|
||||
physicist = agent_context.get_agent(PHYSICIST_AGENT_NAME)
|
||||
chemist = agent_context.get_agent(CHEMIST_AGENT_NAME)
|
||||
|
||||
|
||||
# Create separate threads for each agent
|
||||
physicist_thread = physicist.get_new_thread()
|
||||
chemist_thread = chemist.get_new_thread()
|
||||
|
||||
|
||||
logger.debug(f"[Orchestration] Created threads - Physicist: {physicist_thread.session_id}, Chemist: {chemist_thread.session_id}")
|
||||
|
||||
|
||||
# Create tasks from agent.run() calls - these return DurableAgentTask instances
|
||||
physicist_task = physicist.run(messages=str(prompt), thread=physicist_thread)
|
||||
chemist_task = chemist.run(messages=str(prompt), thread=chemist_thread)
|
||||
|
||||
|
||||
logger.debug("[Orchestration] Created agent tasks, executing concurrently...")
|
||||
|
||||
|
||||
# Execute both tasks concurrently using when_all
|
||||
# The DurableAgentTask instances wrap the underlying entity calls
|
||||
task_results = yield when_all([physicist_task, chemist_task])
|
||||
|
||||
|
||||
logger.debug("[Orchestration] Both agents completed")
|
||||
|
||||
|
||||
# Extract results from the tasks - DurableAgentTask yields AgentResponse
|
||||
physicist_result: AgentResponse = task_results[0]
|
||||
chemist_result: AgentResponse = task_results[1]
|
||||
|
||||
|
||||
result = {
|
||||
"physicist": physicist_result.text,
|
||||
"chemist": chemist_result.text,
|
||||
}
|
||||
|
||||
logger.debug(f"[Orchestration] Aggregated results ready")
|
||||
|
||||
logger.debug("[Orchestration] Aggregated results ready")
|
||||
return result
|
||||
|
||||
|
||||
@@ -126,12 +126,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -152,48 +152,48 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
|
||||
# Create and register both agents
|
||||
logger.debug("Creating and registering agents...")
|
||||
physicist_agent = create_physicist_agent()
|
||||
chemist_agent = create_chemist_agent()
|
||||
|
||||
|
||||
agent_worker.add_agent(physicist_agent)
|
||||
agent_worker.add_agent(chemist_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agents: {physicist_agent.name}, {chemist_agent.name}")
|
||||
|
||||
|
||||
# Register the orchestration function
|
||||
logger.debug("Registering orchestration function...")
|
||||
worker.add_orchestrator(multi_agent_concurrent_orchestration) # type: ignore
|
||||
logger.debug(f"✓ Registered orchestration: {multi_agent_concurrent_orchestration.__name__}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Multi-Agent Worker with Orchestration...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agents and orchestrations
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
logger.debug("Worker is ready and listening for requests...")
|
||||
logger.debug("Press Ctrl+C to stop.")
|
||||
|
||||
|
||||
try:
|
||||
# Start the worker (this blocks until stopped)
|
||||
worker.start()
|
||||
|
||||
|
||||
# Keep the worker running
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
|
||||
logger.debug("Worker stopped")
|
||||
|
||||
|
||||
|
||||
+17
-17
@@ -39,12 +39,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -70,36 +70,36 @@ def run_client(
|
||||
"email_id": email_id,
|
||||
"email_content": email_content,
|
||||
}
|
||||
|
||||
|
||||
logger.debug("Starting spam detection orchestration...")
|
||||
|
||||
|
||||
# Start the orchestration with the email payload
|
||||
instance_id = client.schedule_new_orchestration( # type: ignore
|
||||
orchestrator="spam_detection_orchestration",
|
||||
input=payload,
|
||||
)
|
||||
|
||||
|
||||
logger.debug(f"Orchestration started with instance ID: {instance_id}")
|
||||
logger.debug("Waiting for orchestration to complete...")
|
||||
|
||||
|
||||
# Retrieve the final state
|
||||
metadata = client.wait_for_orchestration_completion(
|
||||
instance_id=instance_id,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
|
||||
if metadata and metadata.runtime_status.name == "COMPLETED":
|
||||
result = metadata.serialized_output
|
||||
|
||||
|
||||
logger.debug("Orchestration completed successfully!")
|
||||
|
||||
|
||||
# Parse and display the result
|
||||
if result:
|
||||
# Remove quotes if present
|
||||
if result.startswith('"') and result.endswith('"'):
|
||||
result = result[1:-1]
|
||||
logger.info(f"Result: {result}")
|
||||
|
||||
|
||||
elif metadata:
|
||||
logger.error(f"Orchestration ended with status: {metadata.runtime_status.name}")
|
||||
if metadata.serialized_output:
|
||||
@@ -111,29 +111,29 @@ def run_client(
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task Spam Detection Orchestration Client...")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
# Test with a legitimate email
|
||||
logger.info("TEST 1: Legitimate Email")
|
||||
|
||||
|
||||
run_client(
|
||||
client,
|
||||
email_id="email-001",
|
||||
email_content="Hello! I wanted to reach out about our upcoming project meeting scheduled for next week."
|
||||
)
|
||||
|
||||
|
||||
# Test with a spam email
|
||||
logger.info("TEST 2: Spam Email")
|
||||
|
||||
|
||||
run_client(
|
||||
client,
|
||||
email_id="email-002",
|
||||
email_content="URGENT! You've won $1,000,000! Click here now to claim your prize! Limited time offer! Don't miss out!"
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during orchestration: {e}")
|
||||
finally:
|
||||
|
||||
+10
-11
@@ -18,10 +18,9 @@ To run this sample:
|
||||
|
||||
import logging
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Import helper functions from worker and client modules
|
||||
from client import get_client, run_client
|
||||
from dotenv import load_dotenv
|
||||
from worker import get_worker, setup_worker
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -34,43 +33,43 @@ logger = logging.getLogger()
|
||||
def main():
|
||||
"""Main entry point - runs both worker and client in single process."""
|
||||
logger.debug("Starting Durable Task Spam Detection Orchestration Sample (Combined Worker + Client)...")
|
||||
|
||||
|
||||
silent_handler = logging.NullHandler()
|
||||
# Create and start the worker using helper function and context manager
|
||||
with get_worker(log_handler=silent_handler) as dts_worker:
|
||||
# Register agents, orchestrations, and activities using helper function
|
||||
setup_worker(dts_worker)
|
||||
|
||||
|
||||
# Start the worker
|
||||
dts_worker.start()
|
||||
logger.debug("Worker started and listening for requests...")
|
||||
|
||||
|
||||
# Create the client using helper function
|
||||
client = get_client(log_handler=silent_handler)
|
||||
logger.debug("CLIENT: Starting orchestration tests...")
|
||||
|
||||
|
||||
try:
|
||||
# Test 1: Legitimate email
|
||||
# logger.info("TEST 1: Legitimate Email")
|
||||
|
||||
|
||||
run_client(
|
||||
client,
|
||||
email_id="email-001",
|
||||
email_content="Hello! I wanted to reach out about our upcoming project meeting scheduled for next week."
|
||||
)
|
||||
|
||||
|
||||
# Test 2: Spam email
|
||||
logger.info("TEST 2: Spam Email")
|
||||
|
||||
|
||||
run_client(
|
||||
client,
|
||||
email_id="email-002",
|
||||
email_content="URGENT! You've won $1,000,000! Click here now to claim your prize! Limited time offer! Don't miss out!"
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error during sample execution: {e}")
|
||||
|
||||
|
||||
logger.debug("Sample completed. Worker shutting down...")
|
||||
|
||||
|
||||
|
||||
+36
-36
@@ -11,16 +11,16 @@ Prerequisites:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from typing import Any, cast
|
||||
|
||||
from agent_framework import AgentResponse, ChatAgent
|
||||
from agent_framework.azure import AzureOpenAIChatClient, DurableAIAgentOrchestrationContext, DurableAIAgentWorker
|
||||
from azure.identity import AzureCliCredential, DefaultAzureCredential
|
||||
from durabletask.task import ActivityContext, OrchestrationContext, Task
|
||||
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
|
||||
from durabletask.task import ActivityContext, OrchestrationContext, Task
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
# Configure logging
|
||||
@@ -118,24 +118,24 @@ def spam_detection_orchestration(context: OrchestrationContext, payload_raw: Any
|
||||
str: Result message from activity functions
|
||||
"""
|
||||
logger.debug("[Orchestration] Starting spam detection orchestration")
|
||||
|
||||
|
||||
# Validate input
|
||||
if not isinstance(payload_raw, dict):
|
||||
raise ValueError("Email data is required")
|
||||
|
||||
|
||||
try:
|
||||
payload = EmailPayload.model_validate(payload_raw)
|
||||
except ValidationError as exc:
|
||||
raise ValueError(f"Invalid email payload: {exc}") from exc
|
||||
|
||||
|
||||
logger.debug(f"[Orchestration] Processing email ID: {payload.email_id}")
|
||||
|
||||
|
||||
# Wrap the orchestration context to access agents
|
||||
agent_context = DurableAIAgentOrchestrationContext(context)
|
||||
|
||||
|
||||
# Get spam detection agent
|
||||
spam_agent = agent_context.get_agent(SPAM_AGENT_NAME)
|
||||
|
||||
|
||||
# Run spam detection
|
||||
spam_prompt = (
|
||||
"Analyze this email for spam content and return a JSON response with 'is_spam' (boolean) "
|
||||
@@ -143,52 +143,52 @@ def spam_detection_orchestration(context: OrchestrationContext, payload_raw: Any
|
||||
f"Email ID: {payload.email_id}\n"
|
||||
f"Content: {payload.email_content}"
|
||||
)
|
||||
|
||||
|
||||
logger.info("[Orchestration] Running spam detection agent: %s", spam_prompt)
|
||||
spam_result_task = spam_agent.run(
|
||||
messages=spam_prompt,
|
||||
options={"response_format": SpamDetectionResult},
|
||||
)
|
||||
|
||||
|
||||
spam_result_raw: AgentResponse = yield spam_result_task
|
||||
spam_result = cast(SpamDetectionResult, spam_result_raw.value)
|
||||
|
||||
|
||||
logger.info("[Orchestration] Spam detection result: is_spam=%s", spam_result.is_spam)
|
||||
|
||||
|
||||
# Branch based on spam detection result
|
||||
if spam_result.is_spam:
|
||||
logger.debug("[Orchestration] Email is spam, handling...")
|
||||
result_task: Task[str] = context.call_activity("handle_spam_email", input=spam_result.reason)
|
||||
result: str = yield result_task
|
||||
return result
|
||||
|
||||
|
||||
# Email is legitimate - draft a response
|
||||
logger.debug("[Orchestration] Email is legitimate, drafting response...")
|
||||
|
||||
|
||||
email_agent = agent_context.get_agent(EMAIL_AGENT_NAME)
|
||||
|
||||
|
||||
email_prompt = (
|
||||
"Draft a professional response to this email. Return a JSON response with a 'response' field "
|
||||
"containing the reply:\n\n"
|
||||
f"Email ID: {payload.email_id}\n"
|
||||
f"Content: {payload.email_content}"
|
||||
)
|
||||
|
||||
|
||||
logger.info("[Orchestration] Running email assistant agent: %s", email_prompt)
|
||||
email_result_task = email_agent.run(
|
||||
messages=email_prompt,
|
||||
options={"response_format": EmailResponse},
|
||||
)
|
||||
|
||||
|
||||
email_result_raw: AgentResponse = yield email_result_task
|
||||
email_result = cast(EmailResponse, email_result_raw.value)
|
||||
|
||||
|
||||
logger.debug("[Orchestration] Email response drafted, sending...")
|
||||
result_task: Task[str] = context.call_activity("send_email", input=email_result.response)
|
||||
result: str = yield result_task
|
||||
|
||||
logger.info("Sent Email: %s", result)
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -209,12 +209,12 @@ def get_worker(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerWorker(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -235,55 +235,55 @@ def setup_worker(worker: DurableTaskSchedulerWorker) -> DurableAIAgentWorker:
|
||||
"""
|
||||
# Wrap it with the agent worker
|
||||
agent_worker = DurableAIAgentWorker(worker)
|
||||
|
||||
|
||||
# Create and register both agents
|
||||
logger.debug("Creating and registering agents...")
|
||||
spam_agent = create_spam_agent()
|
||||
email_agent = create_email_agent()
|
||||
|
||||
|
||||
agent_worker.add_agent(spam_agent)
|
||||
agent_worker.add_agent(email_agent)
|
||||
|
||||
|
||||
logger.debug(f"✓ Registered agents: {spam_agent.name}, {email_agent.name}")
|
||||
|
||||
|
||||
# Register activity functions
|
||||
logger.debug("Registering activity functions...")
|
||||
worker.add_activity(handle_spam_email) # type: ignore[arg-type]
|
||||
worker.add_activity(send_email) # type: ignore[arg-type]
|
||||
logger.debug(f"✓ Registered activity: handle_spam_email")
|
||||
logger.debug(f"✓ Registered activity: send_email")
|
||||
|
||||
logger.debug("✓ Registered activity: handle_spam_email")
|
||||
logger.debug("✓ Registered activity: send_email")
|
||||
|
||||
# Register the orchestration function
|
||||
logger.debug("Registering orchestration function...")
|
||||
worker.add_orchestrator(spam_detection_orchestration) # type: ignore[arg-type]
|
||||
logger.debug(f"✓ Registered orchestration: {spam_detection_orchestration.__name__}")
|
||||
|
||||
|
||||
return agent_worker
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point for the worker process."""
|
||||
logger.debug("Starting Durable Task Spam Detection Worker with Orchestration...")
|
||||
|
||||
|
||||
# Create a worker using the helper function
|
||||
worker = get_worker()
|
||||
|
||||
|
||||
# Setup worker with agents, orchestrations, and activities
|
||||
setup_worker(worker)
|
||||
|
||||
|
||||
logger.debug("Worker is ready and listening for requests...")
|
||||
logger.debug("Press Ctrl+C to stop.")
|
||||
|
||||
|
||||
try:
|
||||
# Start the worker (this blocks until stopped)
|
||||
worker.start()
|
||||
|
||||
|
||||
# Keep the worker running
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Worker shutdown initiated")
|
||||
|
||||
|
||||
logger.debug("Worker stopped")
|
||||
|
||||
|
||||
|
||||
+52
-53
@@ -45,12 +45,12 @@ def get_client(
|
||||
"""
|
||||
taskhub_name = taskhub or os.getenv("TASKHUB", "default")
|
||||
endpoint_url = endpoint or os.getenv("ENDPOINT", "http://localhost:8080")
|
||||
|
||||
|
||||
logger.debug(f"Using taskhub: {taskhub_name}")
|
||||
logger.debug(f"Using endpoint: {endpoint_url}")
|
||||
|
||||
|
||||
credential = None if endpoint_url == "http://localhost:8080" else DefaultAzureCredential()
|
||||
|
||||
|
||||
return DurableTaskSchedulerClient(
|
||||
host_address=endpoint_url,
|
||||
secure_channel=endpoint_url != "http://localhost:8080",
|
||||
@@ -70,16 +70,16 @@ def _log_completion_result(
|
||||
"""
|
||||
if metadata and metadata.runtime_status.name == "COMPLETED":
|
||||
result = metadata.serialized_output
|
||||
|
||||
logger.debug(f"Orchestration completed successfully!")
|
||||
|
||||
|
||||
logger.debug("Orchestration completed successfully!")
|
||||
|
||||
if result:
|
||||
try:
|
||||
result_dict = json.loads(result)
|
||||
logger.info("Final Result: %s", json.dumps(result_dict, indent=2))
|
||||
except json.JSONDecodeError:
|
||||
logger.debug(f"Result: {result}")
|
||||
|
||||
|
||||
elif metadata:
|
||||
logger.error(f"Orchestration ended with status: {metadata.runtime_status.name}")
|
||||
if metadata.serialized_output:
|
||||
@@ -105,7 +105,7 @@ def _wait_and_log_completion(
|
||||
instance_id=instance_id,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
|
||||
_log_completion_result(metadata)
|
||||
|
||||
|
||||
@@ -127,18 +127,18 @@ def send_approval(
|
||||
"approved": approved,
|
||||
"feedback": feedback
|
||||
}
|
||||
|
||||
|
||||
logger.debug(f"Sending {'APPROVAL' if approved else 'REJECTION'} to instance {instance_id}")
|
||||
if feedback:
|
||||
logger.debug(f"Feedback: {feedback}")
|
||||
|
||||
|
||||
# Raise the external event
|
||||
client.raise_orchestration_event(
|
||||
instance_id=instance_id,
|
||||
event_name=HUMAN_APPROVAL_EVENT,
|
||||
data=approval_data
|
||||
)
|
||||
|
||||
|
||||
logger.debug("Event sent successfully")
|
||||
|
||||
|
||||
@@ -160,14 +160,14 @@ def wait_for_notification(
|
||||
True if notification detected, False if timeout
|
||||
"""
|
||||
logger.debug("Waiting for orchestration to reach notification point...")
|
||||
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout_seconds:
|
||||
try:
|
||||
metadata = client.get_orchestration_state(
|
||||
instance_id=instance_id,
|
||||
)
|
||||
|
||||
|
||||
if metadata:
|
||||
# Check if we're waiting for approval by examining custom status
|
||||
if metadata.serialized_custom_status:
|
||||
@@ -183,19 +183,19 @@ def wait_for_notification(
|
||||
if metadata.serialized_custom_status.lower().startswith("requesting human feedback"):
|
||||
logger.debug("Orchestration is requesting human feedback")
|
||||
return True
|
||||
|
||||
|
||||
# Check for terminal states
|
||||
if metadata.runtime_status.name == "COMPLETED":
|
||||
logger.debug("Orchestration already completed")
|
||||
return False
|
||||
elif metadata.runtime_status.name == "FAILED":
|
||||
if metadata.runtime_status.name == "FAILED":
|
||||
logger.error("Orchestration failed")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.debug(f"Status check: {e}")
|
||||
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
logger.warning("Timeout waiting for notification")
|
||||
return False
|
||||
|
||||
@@ -208,94 +208,93 @@ def run_interactive_client(client: DurableTaskSchedulerClient) -> None:
|
||||
"""
|
||||
# Get user inputs
|
||||
logger.debug("Content Generation - Human-in-the-Loop")
|
||||
|
||||
|
||||
topic = input("Enter the topic for content generation: ").strip()
|
||||
if not topic:
|
||||
topic = "The benefits of cloud computing"
|
||||
logger.info(f"Using default topic: {topic}")
|
||||
|
||||
|
||||
max_attempts_str = input("Enter max review attempts (default: 3): ").strip()
|
||||
max_review_attempts = int(max_attempts_str) if max_attempts_str else 3
|
||||
|
||||
|
||||
timeout_hours_str = input("Enter approval timeout in hours (default: 5): ").strip()
|
||||
timeout_hours = float(timeout_hours_str) if timeout_hours_str else 5.0
|
||||
approval_timeout_seconds = int(timeout_hours * 3600)
|
||||
|
||||
|
||||
payload = {
|
||||
"topic": topic,
|
||||
"max_review_attempts": max_review_attempts,
|
||||
"approval_timeout_seconds": approval_timeout_seconds
|
||||
}
|
||||
|
||||
|
||||
logger.debug(f"Configuration: Topic={topic}, Max attempts={max_review_attempts}, Timeout={timeout_hours}h")
|
||||
|
||||
|
||||
# Start the orchestration
|
||||
logger.debug("Starting content generation orchestration...")
|
||||
instance_id = client.schedule_new_orchestration( # type: ignore
|
||||
orchestrator="content_generation_hitl_orchestration",
|
||||
input=payload,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"Orchestration started with instance ID: {instance_id}")
|
||||
|
||||
|
||||
# Review loop
|
||||
attempt = 1
|
||||
while attempt <= max_review_attempts:
|
||||
logger.info(f"Review Attempt {attempt}/{max_review_attempts}")
|
||||
|
||||
|
||||
# Wait for orchestration to reach notification point
|
||||
logger.debug("Waiting for content generation...")
|
||||
if not wait_for_notification(client, instance_id, timeout_seconds=120):
|
||||
logger.error("Failed to receive notification. Orchestration may have completed or failed.")
|
||||
break
|
||||
|
||||
|
||||
logger.info("Content is ready for review! Please review the content in the worker logs.")
|
||||
|
||||
|
||||
# Get user decision
|
||||
while True:
|
||||
decision = input("Do you approve this content? (yes/no): ").strip().lower()
|
||||
if decision in ['yes', 'y', 'no', 'n']:
|
||||
if decision in ["yes", "y", "no", "n"]:
|
||||
break
|
||||
logger.info("Please enter 'yes' or 'no'")
|
||||
|
||||
approved = decision in ['yes', 'y']
|
||||
|
||||
|
||||
approved = decision in ["yes", "y"]
|
||||
|
||||
if approved:
|
||||
logger.debug("Sending approval...")
|
||||
send_approval(client, instance_id, approved=True)
|
||||
logger.info("Approval sent. Waiting for orchestration to complete...")
|
||||
_wait_and_log_completion(client, instance_id, timeout=60)
|
||||
break
|
||||
else:
|
||||
feedback = input("Enter feedback for improvement: ").strip()
|
||||
if not feedback:
|
||||
feedback = "Please revise the content."
|
||||
|
||||
logger.debug("Sending rejection with feedback...")
|
||||
send_approval(client, instance_id, approved=False, feedback=feedback)
|
||||
logger.info("Rejection sent. Content will be regenerated...")
|
||||
|
||||
attempt += 1
|
||||
|
||||
if attempt > max_review_attempts:
|
||||
logger.info(f"Maximum review attempts ({max_review_attempts}) reached.")
|
||||
_wait_and_log_completion(client, instance_id, timeout=30)
|
||||
break
|
||||
|
||||
# Small pause before next iteration
|
||||
time.sleep(2)
|
||||
feedback = input("Enter feedback for improvement: ").strip()
|
||||
if not feedback:
|
||||
feedback = "Please revise the content."
|
||||
|
||||
logger.debug("Sending rejection with feedback...")
|
||||
send_approval(client, instance_id, approved=False, feedback=feedback)
|
||||
logger.info("Rejection sent. Content will be regenerated...")
|
||||
|
||||
attempt += 1
|
||||
|
||||
if attempt > max_review_attempts:
|
||||
logger.info(f"Maximum review attempts ({max_review_attempts}) reached.")
|
||||
_wait_and_log_completion(client, instance_id, timeout=30)
|
||||
break
|
||||
|
||||
# Small pause before next iteration
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main entry point for the client application."""
|
||||
logger.debug("Starting Durable Task HITL Content Generation Client")
|
||||
|
||||
|
||||
# Create client using helper function
|
||||
client = get_client()
|
||||
|
||||
|
||||
try:
|
||||
run_interactive_client(client)
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Interrupted by user")
|
||||
except Exception as e:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user