Skip to content

Commit eb57624

Browse files
committed
Merge feature/topic-driven-endpoint-architecture into dev
Issue #113: Topic-Driven Endpoint Architecture Implementation Complete This merge brings the complete implementation of the topic-driven endpoint architecture from the feature branch to dev. Summary of Changes: - ✅ Phase 1: Foundation (Endpoint Registry, Topic Seed Data, Seeding Service, CLI) - ✅ Phase 2: Unified AI Engine (Response Serializer, Single-shot, Conversations) - ✅ Phase 3: Endpoint Migration (12 active endpoints migrated) - ✅ Phase 4: Admin Enhancements & Testing (Registry API, Integration Tests) Key Statistics: - 21 commits following conventional commit format - 12 endpoints migrated to UnifiedAIEngine - ~1,410 lines of boilerplate removed - +4,885 lines of new architecture added - 15 integration tests passing - 100% linting/formatting compliance Migration Impact: - All active single-shot endpoints now use unified architecture - Admin-configurable prompts via S3 storage - Consistent error handling and logging across endpoints - Zero breaking changes to API contracts - Backward compatible with existing frontends Files Changed: - New architecture components in src/core, src/application, src/api - Migrated routes: coaching_ai.py, operations_ai.py, onboarding.py, insights.py - Enhanced admin routes with registry and testing endpoints - Comprehensive integration test suite - Updated documentation For detailed information, see issue #113 and branch commits.
2 parents 65241e8 + 4cf2989 commit eb57624

19 files changed

Lines changed: 7078 additions & 529 deletions

coaching/src/api/dependencies/__init__.py

Whitespace-only changes.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Dependency injection for UnifiedAIEngine and related services.
2+
3+
This module provides FastAPI dependency factories for the AI engine
4+
and its components, enabling proper dependency injection in route handlers.
5+
"""
6+
7+
import boto3
8+
import structlog
9+
from coaching.src.api.handlers.generic_ai_handler import GenericAIHandler
10+
from coaching.src.application.ai_engine.response_serializer import ResponseSerializer
11+
from coaching.src.application.ai_engine.unified_ai_engine import UnifiedAIEngine
12+
from coaching.src.core.config_multitenant import settings
13+
from coaching.src.domain.ports.llm_provider_port import LLMProviderPort
14+
from coaching.src.infrastructure.llm.bedrock_provider import BedrockLLMProvider
15+
from coaching.src.repositories.topic_repository import TopicRepository
16+
from coaching.src.services.s3_prompt_storage import S3PromptStorage
17+
18+
from shared.services.aws_helpers import get_bedrock_client
19+
20+
logger = structlog.get_logger()
21+
22+
23+
# Singleton instances (cached for performance)
24+
_topic_repo: TopicRepository | None = None
25+
_s3_storage: S3PromptStorage | None = None
26+
_response_serializer: ResponseSerializer | None = None
27+
_llm_provider: LLMProviderPort | None = None
28+
_unified_engine: UnifiedAIEngine | None = None
29+
_generic_handler: GenericAIHandler | None = None
30+
31+
32+
async def get_topic_repository() -> TopicRepository:
33+
"""Get or create TopicRepository singleton.
34+
35+
Returns:
36+
TopicRepository instance
37+
"""
38+
global _topic_repo
39+
if _topic_repo is None:
40+
dynamodb_resource = boto3.resource("dynamodb", region_name=settings.aws_region)
41+
_topic_repo = TopicRepository(
42+
dynamodb_resource=dynamodb_resource,
43+
table_name=settings.topics_table_name,
44+
)
45+
logger.info("TopicRepository initialized", table=settings.topics_table_name)
46+
47+
return _topic_repo
48+
49+
50+
async def get_s3_prompt_storage() -> S3PromptStorage:
51+
"""Get or create S3PromptStorage singleton.
52+
53+
Returns:
54+
S3PromptStorage instance
55+
"""
56+
global _s3_storage
57+
if _s3_storage is None:
58+
s3_client = boto3.client("s3", region_name=settings.aws_region)
59+
_s3_storage = S3PromptStorage(
60+
bucket_name=settings.prompts_bucket_name,
61+
s3_client=s3_client,
62+
)
63+
logger.info("S3PromptStorage initialized", bucket=settings.prompts_bucket_name)
64+
65+
return _s3_storage
66+
67+
68+
async def get_response_serializer() -> ResponseSerializer:
69+
"""Get or create ResponseSerializer singleton.
70+
71+
Returns:
72+
ResponseSerializer instance
73+
"""
74+
global _response_serializer
75+
if _response_serializer is None:
76+
_response_serializer = ResponseSerializer()
77+
logger.info("ResponseSerializer initialized")
78+
79+
return _response_serializer
80+
81+
82+
async def get_llm_provider() -> LLMProviderPort:
83+
"""Get or create LLM Provider singleton.
84+
85+
Returns:
86+
LLM Provider instance (Bedrock)
87+
"""
88+
global _llm_provider
89+
if _llm_provider is None:
90+
bedrock_client = get_bedrock_client(settings.bedrock_region)
91+
_llm_provider = BedrockLLMProvider(
92+
bedrock_client=bedrock_client,
93+
region=settings.bedrock_region,
94+
)
95+
logger.info("LLM Provider initialized", provider="bedrock")
96+
97+
return _llm_provider
98+
99+
100+
async def get_unified_ai_engine() -> UnifiedAIEngine:
101+
"""Get or create UnifiedAIEngine singleton.
102+
103+
Returns:
104+
UnifiedAIEngine instance with all dependencies
105+
"""
106+
global _unified_engine
107+
if _unified_engine is None:
108+
topic_repo = await get_topic_repository()
109+
s3_storage = await get_s3_prompt_storage()
110+
llm_provider = await get_llm_provider()
111+
response_serializer = await get_response_serializer()
112+
113+
_unified_engine = UnifiedAIEngine(
114+
topic_repo=topic_repo,
115+
s3_storage=s3_storage,
116+
llm_provider=llm_provider,
117+
response_serializer=response_serializer,
118+
)
119+
logger.info("UnifiedAIEngine initialized")
120+
121+
return _unified_engine
122+
123+
124+
async def get_generic_handler() -> GenericAIHandler:
125+
"""Get or create GenericAIHandler singleton.
126+
127+
Returns:
128+
GenericAIHandler instance
129+
"""
130+
global _generic_handler
131+
if _generic_handler is None:
132+
ai_engine = await get_unified_ai_engine()
133+
_generic_handler = GenericAIHandler(ai_engine=ai_engine)
134+
logger.info("GenericAIHandler initialized")
135+
136+
return _generic_handler
137+
138+
139+
def reset_singletons() -> None:
140+
"""Reset all singleton instances (useful for testing).
141+
142+
This clears all cached instances, forcing recreation on next access.
143+
"""
144+
global _topic_repo, _s3_storage, _response_serializer, _llm_provider
145+
global _unified_engine, _generic_handler
146+
147+
_topic_repo = None
148+
_s3_storage = None
149+
_response_serializer = None
150+
_llm_provider = None
151+
_unified_engine = None
152+
_generic_handler = None
153+
154+
logger.info("All singleton dependencies reset")
155+
156+
157+
__all__ = [
158+
"get_generic_handler",
159+
"get_llm_provider",
160+
"get_response_serializer",
161+
"get_s3_prompt_storage",
162+
"get_topic_repository",
163+
"get_unified_ai_engine",
164+
"reset_singletons",
165+
]

coaching/src/api/handlers/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)