Skip to content

Commit d7ecc62

Browse files
implement token provider for Azure OpenAI authentication in orchestrator and title service
1 parent e51b782 commit d7ecc62

2 files changed

Lines changed: 53 additions & 27 deletions

File tree

src/backend/orchestrator.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
from agent_framework import (
2525
Agent,
2626
Message,
27+
WorkflowEvent,
28+
WorkflowEventType,
29+
WorkflowRunState,
2730
)
2831
from agent_framework.orchestrations import HandoffBuilder, HandoffAgentUserRequest
2932
from agent_framework.openai import OpenAIChatCompletionClient
@@ -45,6 +48,11 @@
4548
# Token endpoint for Azure Cognitive Services (used for Azure OpenAI)
4649
TOKEN_ENDPOINT = "https://cognitiveservices.azure.com/.default"
4750

51+
# Event type constants for type-safe dispatch (avoids string typos)
52+
EVENT_STATUS: WorkflowEventType = "status"
53+
EVENT_REQUEST_INFO: WorkflowEventType = "request_info"
54+
EVENT_OUTPUT: WorkflowEventType = "output"
55+
4856

4957
# Harmful content patterns to detect in USER INPUT before processing
5058
# This provides proactive content safety by blocking harmful requests at the input layer
@@ -538,6 +546,11 @@ def _get_chat_client(self):
538546
if not azure_endpoint:
539547
raise ValueError("AZURE_OPENAI_ENDPOINT is required for Foundry mode chat completions")
540548

549+
def get_token() -> str:
550+
"""Token provider callable - invoked for each request to ensure fresh tokens."""
551+
token = self._credential.get_token(TOKEN_ENDPOINT)
552+
return token.token
553+
541554
model_deployment = app_settings.ai_foundry.model_deployment or app_settings.azure_openai.gpt_model
542555
api_version = app_settings.azure_openai.api_version
543556

@@ -546,20 +559,25 @@ def _get_chat_client(self):
546559
azure_endpoint=azure_endpoint,
547560
model=model_deployment,
548561
api_version=api_version,
549-
credential=self._credential,
562+
credential=get_token,
550563
)
551564
else:
552565
# Azure OpenAI Direct mode
553566
endpoint = app_settings.azure_openai.endpoint
554567
if not endpoint:
555568
raise ValueError("AZURE_OPENAI_ENDPOINT is not configured")
556569

557-
logger.info("Using Azure OpenAI Direct mode with credential")
570+
def get_token() -> str:
571+
"""Token provider callable - invoked for each request to ensure fresh tokens."""
572+
token = self._credential.get_token(TOKEN_ENDPOINT)
573+
return token.token
574+
575+
logger.info("Using Azure OpenAI Direct mode with ad_token_provider")
558576
self._chat_client = OpenAIChatCompletionClient(
559577
azure_endpoint=endpoint,
560578
model=app_settings.azure_openai.gpt_model,
561579
api_version=app_settings.azure_openai.api_version,
562-
credential=self._credential,
580+
credential=get_token,
563581
)
564582
return self._chat_client
565583

@@ -738,35 +756,35 @@ async def process_message(
738756
events.append(event)
739757

740758
# Handle different event types from the workflow
741-
if event.type == "status":
759+
if event.type == EVENT_STATUS:
760+
status_name = event.state.name if event.state else str(event.data)
742761
yield {
743762
"type": "status",
744-
"content": event.state.name if hasattr(event, 'state') else str(event.data),
763+
"content": status_name,
745764
"is_final": False,
746765
"metadata": {"conversation_id": conversation_id}
747766
}
748767

749-
elif event.type == "request_info":
768+
elif event.type == EVENT_REQUEST_INFO:
750769
# Workflow is requesting user input
751770
if isinstance(event.data, HandoffAgentUserRequest):
752-
# Extract conversation history from agent_response.messages (updated API)
753-
messages = event.data.agent_response.messages if hasattr(event.data, 'agent_response') and event.data.agent_response else []
754-
if not isinstance(messages, list):
755-
messages = [messages] if messages else []
771+
# Extract conversation history from agent_response.messages
772+
agent_resp = event.data.agent_response
773+
messages = list(agent_resp.messages) if agent_resp and agent_resp.messages else []
756774

757775
conversation_text = "\n".join([
758776
f"{msg.author_name or msg.role.value}: {msg.text}"
759777
for msg in messages
760778
])
761779

762780
# Get the last message content and filter any system prompt leakage
763-
last_msg_content = messages[-1].text if messages else (event.data.agent_response.text if hasattr(event.data, 'agent_response') and event.data.agent_response else "")
781+
last_msg_content = messages[-1].text if messages else (agent_resp.text if agent_resp else "")
764782
last_msg_content = _filter_system_prompt_from_response(last_msg_content)
765-
last_msg_agent = messages[-1].author_name if messages and hasattr(messages[-1], 'author_name') else "unknown"
783+
last_msg_agent = messages[-1].author_name if messages else "unknown"
766784

767785
yield {
768786
"type": "agent_response",
769-
"agent": last_msg_agent,
787+
"agent": last_msg_agent or "unknown",
770788
"content": last_msg_content,
771789
"conversation_history": conversation_text,
772790
"is_final": False,
@@ -775,8 +793,7 @@ async def process_message(
775793
"metadata": {"conversation_id": conversation_id}
776794
}
777795

778-
elif event.type == "output":
779-
# Final output from the workflow
796+
elif event.type == EVENT_OUTPUT:
780797
conversation = cast(list[Message], event.data)
781798
if isinstance(conversation, list) and conversation:
782799
# Get the last assistant message as the final response
@@ -843,37 +860,37 @@ async def send_user_response(
843860
try:
844861
responses = {request_id: user_response}
845862
async for event in self._workflow.send_responses_streaming(responses):
846-
if event.type == "status":
863+
if event.type == EVENT_STATUS:
864+
status_name = event.state.name if event.state else str(event.data)
847865
yield {
848866
"type": "status",
849-
"content": event.state.name if hasattr(event, 'state') else str(event.data),
867+
"content": status_name,
850868
"is_final": False,
851869
"metadata": {"conversation_id": conversation_id}
852870
}
853871

854-
elif event.type == "request_info":
872+
elif event.type == EVENT_REQUEST_INFO:
855873
if isinstance(event.data, HandoffAgentUserRequest):
856-
# Get messages from agent_response (updated API)
857-
messages = event.data.agent_response.messages if hasattr(event.data, 'agent_response') and event.data.agent_response else []
858-
if not isinstance(messages, list):
859-
messages = [messages] if messages else []
874+
# Get messages from agent_response
875+
agent_resp = event.data.agent_response
876+
messages = list(agent_resp.messages) if agent_resp and agent_resp.messages else []
860877

861878
# Get the last message content and filter any system prompt leakage
862-
last_msg_content = messages[-1].text if messages else (event.data.agent_response.text if hasattr(event.data, 'agent_response') and event.data.agent_response else "")
879+
last_msg_content = messages[-1].text if messages else (agent_resp.text if agent_resp else "")
863880
last_msg_content = _filter_system_prompt_from_response(last_msg_content)
864-
last_msg_agent = messages[-1].author_name if messages and hasattr(messages[-1], 'author_name') else "unknown"
881+
last_msg_agent = messages[-1].author_name if messages else "unknown"
865882

866883
yield {
867884
"type": "agent_response",
868-
"agent": last_msg_agent,
885+
"agent": last_msg_agent or "unknown",
869886
"content": last_msg_content,
870887
"is_final": False,
871888
"requires_user_input": True,
872889
"request_id": event.request_id,
873890
"metadata": {"conversation_id": conversation_id}
874891
}
875892

876-
elif event.type == "output":
893+
elif event.type == EVENT_OUTPUT:
877894
conversation = cast(list[Message], event.data)
878895
if isinstance(conversation, list) and conversation:
879896
assistant_messages = [

src/backend/services/title_service.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
logger = logging.getLogger(__name__)
1919

20+
# Token endpoint for Azure OpenAI authentication
21+
TOKEN_ENDPOINT = "https://cognitiveservices.azure.com/.default"
22+
2023
# Title generation instructions (from MS reference accelerator)
2124
TITLE_INSTRUCTIONS = """Summarize the conversation so far into a 4-word or less title.
2225
Do not use any quotation marks or punctuation.
@@ -55,11 +58,17 @@ def initialize(self) -> None:
5558

5659
api_version = app_settings.azure_openai.api_version
5760

61+
# Create token provider function
62+
def get_token() -> str:
63+
"""Token provider callable - invoked for each request to ensure fresh tokens."""
64+
token = self._credential.get_token(TOKEN_ENDPOINT)
65+
return token.token
66+
5867
chat_client = OpenAIChatCompletionClient(
5968
azure_endpoint=endpoint,
6069
model=deployment,
6170
api_version=api_version,
62-
credential=self._credential,
71+
credential=get_token,
6372
)
6473

6574
self._agent = Agent(

0 commit comments

Comments
 (0)