44from aio_pika .abc import AbstractIncomingMessage
55
66from ai_server .chain .question_generation_chain import QuestionGenerator
7+ from ai_server .core .client import CoreClient
78from ai_server .messaging .idempotency import LruIdempotencyStore
89from ai_server .messaging .publisher import CallbackPublisher
910from ai_server .model .envelope import Envelope
1213 GenerateQuestionsRequest ,
1314 QuestionPoolCallbackPayload ,
1415)
16+ from ai_server .rag .embedder import EmbeddingProvider
1517
1618log = structlog .get_logger (__name__ )
1719
@@ -25,14 +27,20 @@ def __init__(
2527 idempotency : LruIdempotencyStore ,
2628 callback_routing_key : str ,
2729 initial_pool_size : int = 1 ,
30+ core_client : CoreClient | None = None ,
31+ embedder : EmbeddingProvider | None = None ,
32+ rag_top_k : int = 5 ,
2833 ) -> None :
2934 self ._generator = generator
3035 self ._publisher = publisher
3136 self ._idempotency = idempotency
3237 self ._callback_routing_key = callback_routing_key
33- # Core 의 QuestionsCallbackService.applyPool 은 questions[0] 만 INSERT 하고 나머지는 폐기.
34- # 토큰 낭비를 줄이기 위해 envelope.max_questions 대신 풀 크기를 강제. 후속 작업에서 풀 저장 도입 시 늘리기 쉬움 .
38+ # Core compatibility keeps callback.kind=POOL, but this is the initial
39+ # question result. maxQuestions remains the full session limit .
3540 self ._initial_pool_size = max (1 , initial_pool_size )
41+ self ._core = core_client
42+ self ._embedder = embedder
43+ self ._rag_top_k = rag_top_k
3644
3745 async def handle (self , message : AbstractIncomingMessage ) -> None :
3846 async with message .process (requeue = False ):
@@ -57,7 +65,10 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
5765 return
5866
5967 req = envelope .payload
60- effective_pool_size = self ._initial_pool_size # envelope.max_questions 무시
68+ effective_pool_size = max (
69+ 1 ,
70+ req .initial_question_count ,
71+ )
6172 log .info (
6273 "questions.generate.start" ,
6374 message_id = envelope .message_id ,
@@ -68,7 +79,7 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
6879 trace_id = envelope .trace_id ,
6980 )
7081
71- context_text = _build_context (req . documents )
82+ context_text = await self . _build_context (req )
7283 pool = await self ._generator .generate (
7384 job_category = req .job_category ,
7485 mode = req .mode ,
@@ -98,6 +109,34 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
98109 trace_id = envelope .trace_id ,
99110 )
100111
112+ async def _build_context (self , req : GenerateQuestionsRequest ) -> str :
113+ base_context = _build_context (req .documents )
114+ if not self ._core or not self ._embedder :
115+ return base_context
116+
117+ document_ids = [d .document_id for d in req .documents ]
118+ if not document_ids :
119+ return base_context
120+
121+ query = _build_initial_rag_query (req )
122+ try :
123+ query_vec = (await self ._embedder .embed ([query ]))[0 ]
124+ hits = await self ._core .search_embeddings (
125+ query_embedding = query_vec ,
126+ document_ids = document_ids ,
127+ top_k = self ._rag_top_k ,
128+ )
129+ except Exception as exc :
130+ log .warn ("questions.rag.failed" , error = str (exc ), session_id = req .session_id )
131+ return base_context
132+
133+ if not hits :
134+ return base_context
135+ rag_context = "\n ---\n " .join (
136+ f"[doc#{ h .document_id } chunk#{ h .chunk_index } ] { h .chunk_text } " for h in hits
137+ )
138+ return f"{ base_context } \n \n ## Retrieved document chunks\n { rag_context } "
139+
101140
102141def _build_context (documents : list [DocumentContext ]) -> str :
103142 parts : list [str ] = []
@@ -112,3 +151,20 @@ def _build_context(documents: list[DocumentContext]) -> str:
112151 block .append (d .markdown )
113152 parts .append ("\n " .join (block ))
114153 return "\n \n " .join (parts ) if parts else "(no documents)"
154+
155+
156+ def _build_initial_rag_query (req : GenerateQuestionsRequest ) -> str :
157+ parts = [
158+ f"mode: { req .mode } " ,
159+ f"job category: { req .job_category } " ,
160+ ]
161+ for d in req .documents :
162+ doc_parts = [f"document #{ d .document_id } { d .source_type } " ]
163+ if d .summary :
164+ doc_parts .append (d .summary )
165+ if d .tech_stack :
166+ doc_parts .append ("tech stack: " + ", " .join (d .tech_stack ))
167+ if d .markdown :
168+ doc_parts .append (d .markdown [:1000 ])
169+ parts .append ("\n " .join (doc_parts ))
170+ return "\n \n " .join (parts )
0 commit comments