Skip to content

Commit a838c20

Browse files
authored
Merge pull request #13 from Azure-Samples/gk/entra-login
Refactors app for container app hosting
2 parents 562f2d9 + 77e2cdc commit a838c20

38 files changed

+1672
-264
lines changed

azure.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ services:
1313
web:
1414
project: ./frontend
1515
language: js
16-
host: staticwebapp
17-
dist: dist
16+
host: containerapp
17+
docker:
18+
path: ./Dockerfile
19+
context: ./frontend
1820
infra:
1921
provider: bicep
2022
path: infra

backend/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ COPY pyproject.toml poetry.lock ./
1616
# Configure poetry: Don't create a virtual environment
1717
RUN poetry config virtualenvs.create false
1818

19-
# Install dependencies
20-
RUN poetry install --no-dev
19+
# Install dependencies (without installing the current project)
20+
RUN poetry install --only=main --no-root
2121

2222
# Copy application code
2323
COPY . .

backend/app/agents/curator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def _extract_company(self, filename: str) -> str:
7878
else:
7979
return "Unknown"
8080

81-
def _extract_year(self, filename: str) -> int:
81+
def _extract_year(self, filename: str) -> str:
8282
import re
8383
year_match = re.search(r'20\d{2}', filename)
84-
return int(year_match.group()) if year_match else 2024
84+
return str(year_match.group()) if year_match else "2024"

backend/app/agents/orchestrator.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from semantic_kernel.kernel import Kernel
22
from semantic_kernel.agents import ChatCompletionAgent
3-
from semantic_kernel.agents.orchestration.sequential import SequentialOrchestration
3+
# from semantic_kernel.agents.orchestration.sequential import SequentialOrchestration # Not available in semantic_kernel 1.3.0
44
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
55
from semantic_kernel.contents import ChatMessageContent
66
from typing import List, Dict, AsyncIterator, Optional
@@ -98,7 +98,7 @@ async def create_plan(self, request: Dict) -> List[str]:
9898

9999
return ["RetrieverAgent", "WriterAgent"]
100100

101-
async def create_sk_orchestration(self, mode: str) -> Optional[SequentialOrchestration]:
101+
async def create_sk_orchestration(self, mode: str) -> Optional[object]:
102102
"""Create SK SequentialOrchestration based on mode using registry config or fallback"""
103103
await self.initialize_sk_agents()
104104

@@ -127,14 +127,16 @@ async def create_sk_orchestration(self, mode: str) -> Optional[SequentialOrchest
127127
return None
128128

129129
try:
130-
orchestration = SequentialOrchestration(
131-
members=agent_sequence,
132-
name=f"{mode}_orchestration",
133-
description=orchestration_config.get("description", f"Sequential orchestration for {mode} workflow") if orchestration_config else f"Sequential orchestration for {mode} workflow"
134-
)
135-
136-
self.orchestrations[mode] = orchestration
137-
return orchestration
130+
# orchestration = SequentialOrchestration(
131+
# members=agent_sequence,
132+
# name=f"{mode}_orchestration",
133+
# description=orchestration_config.get("description", f"Sequential orchestration for {mode} workflow") if orchestration_config else f"Sequential orchestration for {mode} workflow"
134+
# )
135+
#
136+
# self.orchestrations[mode] = orchestration
137+
# return orchestration
138+
print(f"SK orchestration not available in semantic_kernel 1.3.0, using fallback")
139+
return None
138140
except Exception as e:
139141
print(f"Error creating SK orchestration: {e}")
140142
return None

backend/app/agents/retriever.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def __init__(self, kernel):
1818
from azure.search.documents.agent.aio import KnowledgeAgentRetrievalClient
1919
self.agent_client = KnowledgeAgentRetrievalClient(
2020
endpoint=settings.search_endpoint,
21-
credential=AzureKeyCredential(settings.search_admin_key)
21+
credential=AzureKeyCredential(settings.search_admin_key),
22+
agent_name="retriever_agent"
2223
)
2324
self.use_agentic_retrieval = True
2425
except ImportError:

backend/app/agents/verifier.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
from typing import List, Dict
22
import re
3-
from openai import AsyncAzureOpenAI
43
from ..core.config import settings
4+
from ..services.azure_services import azure_service_manager
55

66
class VerifierAgent:
77
def __init__(self, kernel):
88
self.kernel = kernel
9-
self.client = AsyncAzureOpenAI(
10-
azure_endpoint=settings.openai_endpoint.split('/openai/deployments')[0],
11-
api_key=settings.openai_key,
12-
api_version="2025-01-01-preview"
13-
)
9+
self.client = None
10+
11+
async def _ensure_client(self):
12+
if self.client is None:
13+
self.client = azure_service_manager.async_openai_client
1414

1515
async def get_response(self, retrieved_docs: List[Dict], query: str) -> str:
16+
await self._ensure_client()
1617
verified_docs = await self.invoke(retrieved_docs, query)
1718
avg_confidence = sum(doc['confidence'] for doc in verified_docs) / len(verified_docs) if verified_docs else 0
1819
return f"Verified {len(verified_docs)} documents with average confidence: {avg_confidence:.2f}"
@@ -23,6 +24,7 @@ async def invoke_stream(self, retrieved_docs: List[Dict], query: str):
2324
yield f"Verified: {doc['company']} {doc['year']} (confidence: {doc['confidence']:.2f})\n"
2425

2526
async def invoke(self, retrieved_docs: List[Dict], query: str) -> List[Dict]:
27+
await self._ensure_client()
2628
verified_docs = []
2729

2830
for doc in retrieved_docs:

backend/app/agents/writer.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
from typing import List, Dict, AsyncIterator
22
import re
33
import asyncio
4-
from openai import AsyncAzureOpenAI
54
from ..core.config import settings
5+
from ..services.azure_services import azure_service_manager
66

77
class WriterAgent:
88
def __init__(self, kernel):
99
self.kernel = kernel
10-
self.client = AsyncAzureOpenAI(
11-
azure_endpoint=settings.openai_endpoint.split('/openai/deployments')[0],
12-
api_key=settings.openai_key,
13-
api_version="2025-01-01-preview"
14-
)
10+
self.client = None
11+
12+
async def _ensure_client(self):
13+
if self.client is None:
14+
self.client = azure_service_manager.async_openai_client
1515

1616
async def get_response(self, retrieved_docs: List[Dict], query: str) -> str:
17+
await self._ensure_client()
1718
response_parts = []
1819
async for chunk in self.invoke_stream(retrieved_docs, query):
1920
response_parts.append(chunk)
2021
return ''.join(response_parts)
2122

2223
async def invoke_stream(self, retrieved_docs: List[Dict], query: str) -> AsyncIterator[str]:
24+
await self._ensure_client()
2325
try:
2426
context = self._format_context(retrieved_docs)
2527

backend/app/api/chat.py

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from ..core.globals import initialize_kernel, get_agent_registry
1515
from ..auth.middleware import get_current_user
1616
from ..services.agentic_vector_rag_service import agentic_rag_service
17+
from ..services.azure_ai_agents_service import azure_ai_agents_service
1718
from ..services.token_usage_tracker import token_tracker
19+
from ..services.azure_services import get_azure_service_manager
1820

1921
router = APIRouter()
2022

@@ -61,6 +63,15 @@ async def generate():
6163
if not agentic_rag_service.search_client:
6264
await agentic_rag_service.initialize()
6365

66+
azure_service_manager = await get_azure_service_manager()
67+
user_message = {
68+
"role": "user",
69+
"content": request.prompt,
70+
"timestamp": datetime.utcnow().isoformat(),
71+
"mode": request.mode
72+
}
73+
await azure_service_manager.save_session_history(session_id, user_message)
74+
6475
yield f"data: {json.dumps({'type': 'metadata', 'session_id': session_id, 'mode': request.mode, 'timestamp': datetime.utcnow().isoformat()})}\n\n"
6576

6677
if request.mode == "agentic-rag":
@@ -103,6 +114,16 @@ async def generate():
103114
}
104115
yield f"data: {json.dumps({'type': 'metadata', 'processing': processing_metadata})}\n\n"
105116

117+
assistant_message = {
118+
"role": "assistant",
119+
"content": answer,
120+
"timestamp": datetime.utcnow().isoformat(),
121+
"citations": citations,
122+
"token_usage": token_usage,
123+
"processing_metadata": processing_metadata
124+
}
125+
await azure_service_manager.save_session_history(session_id, assistant_message)
126+
106127
yield f"data: {json.dumps({'type': 'done', 'session_id': session_id})}\n\n"
107128

108129
except Exception as e:
@@ -161,9 +182,9 @@ async def process_fast_rag(prompt: str, session_id: str) -> Dict[str, Any]:
161182
"citations": citations,
162183
"query_rewrites": [prompt], # No rewrites in fast mode
163184
"token_usage": {
164-
"prompt_tokens": len(prompt.split()),
165-
"completion_tokens": len(answer.split()),
166-
"total_tokens": len(prompt.split()) + len(answer.split())
185+
"prompt_tokens": 0, # Fast RAG doesn't use LLM, so no tokens
186+
"completion_tokens": 0,
187+
"total_tokens": 0
167188
},
168189
"processing_time_ms": 0, # Will be calculated by caller
169190
"retrieval_method": "fast_rag",
@@ -180,38 +201,34 @@ async def process_fast_rag(prompt: str, session_id: str) -> Dict[str, Any]:
180201
}
181202

182203
async def process_deep_research_rag(prompt: str, session_id: str, verification_level: str) -> Dict[str, Any]:
183-
"""Process Deep Research RAG mode with comprehensive verification"""
204+
"""Process Deep Research RAG mode using Azure AI Agents"""
184205
try:
185-
agentic_result = await agentic_rag_service.process_question(
186-
question=prompt,
187-
rag_mode="deep-research-rag",
188-
session_id=session_id
189-
)
190-
191-
verification_docs = await retriever.invoke(prompt)
206+
from ..services.token_usage_tracker import ServiceType, OperationType
192207

193-
combined_citations = agentic_result.get("citations", [])
208+
tracking_id = token_tracker.start_tracking(
209+
session_id=session_id,
210+
service_type=ServiceType.DEEP_RESEARCH_RAG,
211+
operation_type=OperationType.ANSWER_GENERATION,
212+
endpoint="/deep-research-rag",
213+
rag_mode="deep-research-rag"
214+
)
194215

195-
for i, doc in enumerate(verification_docs[:2]): # Add top 2 verification docs
196-
combined_citations.append({
197-
'id': str(len(combined_citations) + 1),
198-
'title': doc.get('title', f'Verification Document {i + 1}'),
199-
'content': doc.get('content', '')[:300],
200-
'source': doc.get('source', ''),
201-
'score': doc.get('score', 0.0),
202-
'verification': True
203-
})
216+
agents_result = await azure_ai_agents_service.process_deep_research(
217+
question=prompt,
218+
session_id=session_id,
219+
tracking_id=tracking_id
220+
)
204221

205-
base_answer = agentic_result.get("answer", "")
206-
verification_note = f"\n\n*This response has been enhanced with {verification_level} verification using additional sources.*"
222+
base_answer = agents_result.get("answer", "")
223+
verification_note = f"\n\n*This response has been generated using Azure AI Agents deep research with {verification_level} verification.*"
207224

208225
return {
209226
"answer": base_answer + verification_note,
210-
"citations": combined_citations,
211-
"query_rewrites": agentic_result.get("query_rewrites", [prompt]),
212-
"token_usage": agentic_result.get("token_usage", {}),
213-
"processing_time_ms": agentic_result.get("processing_time_ms", 0),
214-
"retrieval_method": "deep_research_rag",
227+
"citations": agents_result.get("citations", []),
228+
"query_rewrites": agents_result.get("query_rewrites", [prompt]),
229+
"token_usage": agents_result.get("token_usage", {}),
230+
"processing_time_ms": 0, # Will be calculated by caller
231+
"retrieval_method": "azure_ai_agents_deep_research",
215232
"verification_level": verification_level,
216233
"success": True
217234
}
@@ -224,3 +241,65 @@ async def process_deep_research_rag(prompt: str, session_id: str, verification_l
224241
"token_usage": {"total_tokens": 0, "error": str(e)},
225242
"success": False
226243
}
244+
245+
@router.get("/chat/sessions/{session_id}/history")
246+
async def get_session_history(session_id: str, current_user: dict = Depends(get_current_user)):
247+
"""Get chat session history"""
248+
try:
249+
azure_service_manager = await get_azure_service_manager()
250+
history = await azure_service_manager.get_session_history(session_id)
251+
return {"session_id": session_id, "messages": history}
252+
except Exception as e:
253+
raise HTTPException(status_code=500, detail=str(e))
254+
255+
@router.delete("/chat/sessions/{session_id}")
256+
async def clear_session_history(session_id: str, current_user: dict = Depends(get_current_user)):
257+
"""Clear chat session history"""
258+
try:
259+
azure_service_manager = await get_azure_service_manager()
260+
empty_session = {
261+
"role": "system",
262+
"content": "Session cleared",
263+
"timestamp": datetime.utcnow().isoformat()
264+
}
265+
await azure_service_manager.save_session_history(f"{session_id}_cleared", empty_session)
266+
return {"session_id": session_id, "status": "cleared"}
267+
except Exception as e:
268+
raise HTTPException(status_code=500, detail=str(e))
269+
270+
@router.get("/chat/sessions")
271+
async def list_user_sessions(current_user: dict = Depends(get_current_user)):
272+
"""List all sessions for the current user (placeholder implementation)"""
273+
try:
274+
return {"sessions": [], "message": "Session listing not yet implemented"}
275+
except Exception as e:
276+
raise HTTPException(status_code=500, detail=str(e))
277+
278+
class FollowUpRequest(BaseModel):
279+
original_question: str
280+
answer: str
281+
session_id: Optional[str] = None
282+
283+
@router.post("/chat/follow-up-questions")
284+
async def generate_follow_up_questions(request: FollowUpRequest, current_user: dict = Depends(get_current_user)):
285+
"""Generate follow-up questions based on the original question and answer"""
286+
try:
287+
session_id = request.session_id or str(uuid.uuid4())
288+
289+
if not azure_ai_agents_service.agents_client:
290+
await azure_ai_agents_service.initialize()
291+
292+
result = await azure_ai_agents_service.generate_follow_up_questions(
293+
original_question=request.original_question,
294+
answer=request.answer,
295+
session_id=session_id
296+
)
297+
298+
return {
299+
"session_id": session_id,
300+
"follow_up_questions": result.get("follow_up_questions", []),
301+
"token_usage": result.get("token_usage", {}),
302+
"success": result.get("success", False)
303+
}
304+
except Exception as e:
305+
raise HTTPException(status_code=500, detail=str(e))

backend/app/api/ingest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ async def _fallback_processing(self, file_path: str, filename: str, status_callb
185185
content=text,
186186
source=filename,
187187
company="Unknown",
188-
year=2024
188+
year="2024"
189189
)
190190

191191
self.logger.info(f"Using proper chunking, created {len(proper_chunks)} chunks")
@@ -207,7 +207,7 @@ async def _fallback_processing(self, file_path: str, filename: str, status_callb
207207
"metadata": {
208208
"company": "Unknown",
209209
"document_type": "Document",
210-
"year": 2024,
210+
"year": "2024",
211211
"filename": filename,
212212
"total_chunks": len(chunks),
213213
"content_length": len(text),
@@ -227,7 +227,7 @@ async def _fallback_processing(self, file_path: str, filename: str, status_callb
227227
"metadata": {
228228
"company": "Unknown",
229229
"document_type": "Document",
230-
"year": 2024,
230+
"year": "2024",
231231
"filename": filename,
232232
"total_chunks": 0,
233233
"content_length": 0,
@@ -387,7 +387,7 @@ def status_callback(status_update):
387387
await asyncio.sleep(0.1)
388388
status_callback({
389389
"step": "RECEIVED",
390-
"message": f"📄 File received: {filename} ({file_extension.upper()}) - validating...",
390+
"message": f"📄 File received: {file.filename} ({file_extension.upper()}) - validating...",
391391
"progress": 3
392392
})
393393

0 commit comments

Comments
 (0)