Skip to content

Commit 559b0cd

Browse files
enhance title generation service to support both Foundry and Direct modes
1 parent 05f1c22 commit 559b0cd

1 file changed

Lines changed: 63 additions & 41 deletions

File tree

src/backend/services/title_service.py

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@
33
44
This service provides a dedicated agent for generating meaningful,
55
short 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

811
import logging
912
import re
1013
from typing import Optional
1114

12-
from agent_framework.azure import AzureOpenAIChatClient
15+
from agent_framework.azure import AzureOpenAIResponsesClient, AzureAIProjectAgentProvider
1316
from azure.identity import DefaultAzureCredential
17+
from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential
1418

1519
from settings import app_settings
1620

1721
logger = 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)
2324
TITLE_INSTRUCTIONS = """Summarize the conversation so far into a 4-word or less title.
2425
Do 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

143162
def 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

Comments
 (0)