33
44This service provides a dedicated agent for generating meaningful,
55short titles for chat conversations based on the user's first message.
6+
7+ Supports both Azure AI Foundry mode (pre-created agents) and
8+ Azure OpenAI Direct mode (in-memory agents).
69"""
710
811import logging
912import re
1013from typing import Optional
1114
12- from agent_framework .azure import AzureOpenAIChatClient
15+ from agent_framework .azure import AzureOpenAIResponsesClient , AzureAIProjectAgentProvider
1316from azure .identity import DefaultAzureCredential
17+ from azure .identity .aio import DefaultAzureCredential as AsyncDefaultAzureCredential
1418
1519from settings import app_settings
1620
1721logger = logging .getLogger (__name__ )
1822
19- # Token endpoint for Azure OpenAI authentication
20- TOKEN_ENDPOINT = "https://cognitiveservices.azure.com/.default"
21-
2223# Title generation instructions (from MS reference accelerator)
2324TITLE_INSTRUCTIONS = """Summarize the conversation so far into a 4-word or less title.
2425Do not use any quotation marks or punctuation.
@@ -31,49 +32,67 @@ class TitleService:
3132 def __init__ (self ):
3233 self ._agent = None
3334 self ._initialized = False
34- self ._credential = None
35+ self ._use_foundry = app_settings .ai_foundry .use_foundry
36+ self ._provider = None # AzureAIProjectAgentProvider (Foundry mode only)
37+
38+ async def initialize (self ) -> None :
39+ """Initialize the title generation agent.
40+
41+ Foundry mode: retrieves pre-created TitleAgent via
42+ AzureAIProjectAgentProvider.get_agent(name=...).
3543
36- def initialize (self ) -> None :
37- """Initialize the title generation agent."""
44+ Direct mode: creates in-memory agent via
45+ AzureOpenAIResponsesClient.as_agent(name=..., instructions=...).
46+ """
3847 if self ._initialized :
3948 return
4049
4150 try :
42- self ._credential = DefaultAzureCredential ()
43- use_foundry = app_settings .ai_foundry .use_foundry
51+ if self ._use_foundry :
52+ # --- Foundry mode: retrieve pre-created TitleAgent ---
53+ project_endpoint = app_settings .ai_foundry .project_endpoint
54+ if not project_endpoint :
55+ logger .warning ("Title service: AZURE_AI_PROJECT_ENDPOINT not configured, using fallback" )
56+ return
57+
58+ agent_name = app_settings .ai_foundry .agent_names .get ("title" )
59+ if not agent_name :
60+ logger .warning ("Title service: AGENT_NAME_TITLE not configured, using fallback" )
61+ return
62+
63+ async_credential = AsyncDefaultAzureCredential ()
64+ self ._provider = AzureAIProjectAgentProvider (
65+ project_endpoint = project_endpoint ,
66+ credential = async_credential ,
67+ )
68+
69+ logger .info (f"Retrieving TitleAgent from Foundry project: { agent_name } " )
70+ self ._agent = await self ._provider .get_agent (name = agent_name )
71+ logger .info ("TitleAgent retrieved from Foundry project" )
4472
45- if use_foundry :
46- # Azure AI Foundry mode
47- endpoint = app_settings .azure_openai .endpoint
48- deployment = app_settings .ai_foundry .model_deployment or app_settings .azure_openai .gpt_model
4973 else :
50- # Azure OpenAI Direct mode
74+ # --- Direct mode: create in-memory agent ---
5175 endpoint = app_settings .azure_openai .endpoint
52- deployment = app_settings .azure_openai .gpt_model
76+ if not endpoint :
77+ logger .warning ("Title service: Azure OpenAI endpoint not configured, using fallback" )
78+ return
5379
54- if not endpoint :
55- logger .warning ("Title service: Azure OpenAI endpoint not configured, title generation disabled" )
56- return
57-
58- api_version = app_settings .azure_openai .api_version
59-
60- # Create token provider function
61- def get_token () -> str :
62- """Token provider callable - invoked for each request to ensure fresh tokens."""
63- token = self ._credential .get_token (TOKEN_ENDPOINT )
64- return token .token
65-
66- chat_client = AzureOpenAIChatClient (
67- endpoint = endpoint ,
68- deployment_name = deployment ,
69- api_version = api_version ,
70- ad_token_provider = get_token ,
71- )
72-
73- self ._agent = chat_client .create_agent (
74- name = "title_agent" ,
75- instructions = TITLE_INSTRUCTIONS ,
76- )
80+ deployment = app_settings .azure_openai .gpt_model
81+ api_version = app_settings .azure_openai .api_version
82+
83+ credential = DefaultAzureCredential ()
84+ chat_client = AzureOpenAIResponsesClient (
85+ endpoint = endpoint ,
86+ deployment_name = deployment ,
87+ api_version = api_version ,
88+ credential = credential ,
89+ )
90+
91+ self ._agent = chat_client .as_agent (
92+ name = "title_agent" ,
93+ instructions = TITLE_INSTRUCTIONS ,
94+ )
95+ logger .info ("TitleAgent created in Direct mode" )
7796
7897 self ._initialized = True
7998
@@ -103,7 +122,7 @@ async def generate_title(self, first_user_message: str) -> str:
103122 return "New Conversation"
104123
105124 if not self ._initialized :
106- self .initialize ()
125+ await self .initialize ()
107126
108127 if self ._agent is None :
109128 logger .warning ("Title generation: agent not available, using fallback" )
@@ -141,9 +160,12 @@ async def generate_title(self, first_user_message: str) -> str:
141160
142161
143162def get_title_service () -> TitleService :
144- """Get or create the singleton title service instance."""
163+ """Get or create the singleton title service instance.
164+
165+ Note: initialize() is async and is called lazily by generate_title()
166+ on first use.
167+ """
145168 global _title_service
146169 if _title_service is None :
147170 _title_service = TitleService ()
148- _title_service .initialize ()
149171 return _title_service
0 commit comments