Skip to content

Commit 16806fd

Browse files
committed
[DERCBOT-1609] Structure the LLM answer
1 parent 144c7c2 commit 16806fd

File tree

9 files changed

+199
-127
lines changed

9 files changed

+199
-127
lines changed

bot/engine/src/main/kotlin/admin/bot/rag/BotRAGConfiguration.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ data class BotRAGConfiguration(
3636
val llmSetting: LLMSetting? = null,
3737
val emSetting: EMSetting,
3838
val indexSessionId: String? = null,
39+
@Deprecated("Replaced by LLM answer status")
3940
val noAnswerSentence: String,
4041
val noAnswerStoryId: String? = null,
4142
val documentsRequired: Boolean = true,

bot/engine/src/main/kotlin/engine/config/RAGAnswerHandler.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import ai.tock.bot.engine.action.SendSentenceWithFootnotes
3131
import ai.tock.bot.engine.dialog.Dialog
3232
import ai.tock.bot.engine.user.PlayerType
3333
import ai.tock.genai.orchestratorclient.requests.*
34+
import ai.tock.genai.orchestratorclient.responses.LLMAnswer
3435
import ai.tock.genai.orchestratorclient.responses.ObservabilityInfo
3536
import ai.tock.genai.orchestratorclient.responses.RAGResponse
36-
import ai.tock.genai.orchestratorclient.responses.TextWithFootnotes
3737
import ai.tock.genai.orchestratorclient.retrofit.GenAIOrchestratorBusinessError
3838
import ai.tock.genai.orchestratorclient.retrofit.GenAIOrchestratorValidationError
3939
import ai.tock.genai.orchestratorclient.services.RAGService
@@ -47,6 +47,8 @@ private val technicalErrorMessage = property(
4747
name = "tock_gen_ai_orchestrator_technical_error",
4848
defaultValue = "Technical error :( sorry!")
4949

50+
// TODO MASS: Doit on enlever le champ NO_RAG_SENTENCE et la story associée de la config RAG (sudio Tock)?
51+
// TODO MASS: Il faudra changer tout les prompts
5052

5153
object RAGAnswerHandler : AbstractProactiveAnswerHandler {
5254

@@ -60,7 +62,7 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
6062
BotRepository.saveMetric(createMetric(MetricType.STORY_HANDLED))
6163

6264
// Call RAG Api - Gen AI Orchestrator
63-
val (answer, debug, noAnswerStory, observabilityInfo) = rag(this)
65+
val (answer, footnotes, debug, noAnswerStory, observabilityInfo) = rag(this)
6466

6567
// Add debug data if available and if debugging is enabled
6668
if (debug != null) {
@@ -75,14 +77,18 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
7577
val modifiedObservabilityInfo = observabilityInfo?.let { updateObservabilityInfo(this, it) }
7678

7779
send(
78-
SendSentenceWithFootnotes(
79-
botId, connectorId, userId, text = answer.text, footnotes = answer.footnotes.map {
80+
action = SendSentenceWithFootnotes(
81+
playerId = botId,
82+
applicationId = connectorId,
83+
recipientId = userId,
84+
text = answer.answer,
85+
footnotes = footnotes?.map {
8086
Footnote(
8187
it.identifier, it.title, it.url,
8288
if(action.metadata.sourceWithContent) it.content else null,
8389
it.score
8490
)
85-
}.toMutableList(),
91+
}?.toMutableList() ?: mutableListOf<Footnote>(),
8692
// modifiedObservabilityInfo includes the public langfuse URL if filled.
8793
metadata = ActionMetadata(isGenAiRagAnswer = true, observabilityInfo = modifiedObservabilityInfo)
8894
)
@@ -116,13 +122,13 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
116122
private fun ragStoryRedirection(botBus: BotBus, response: RAGResponse?): StoryDefinition? {
117123
return with(botBus) {
118124
botDefinition.ragConfiguration?.let { ragConfig ->
119-
if (response?.answer?.text.equals(ragConfig.noAnswerSentence, ignoreCase = true)) {
125+
if (response?.answer?.status.equals("not_found_in_context", ignoreCase = true)) {
120126
// Save no answer metric
121127
saveRagMetric(IndicatorValues.NO_ANSWER)
122128

123129
// Switch to no answer story if configured
124130
if (!ragConfig.noAnswerStoryId.isNullOrBlank()) {
125-
logger.info { "The RAG response is equal to the configured no-answer sentence, so switch to the no-answer story." }
131+
logger.info { "Switch to the no-answer RAG story." }
126132
getNoAnswerRAGStory(ragConfig)
127133
} else null
128134
} else {
@@ -221,7 +227,7 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
221227
)
222228

223229
// Handle RAG response
224-
return RAGResult(response?.answer, response?.debug, ragStoryRedirection(this, response), response?.observabilityInfo)
230+
return RAGResult(response?.answer, response?.footnotes, response?.debug, ragStoryRedirection(this, response), response?.observabilityInfo)
225231
} catch (exc: Exception) {
226232
logger.error { exc }
227233
// Save failure metric
@@ -232,7 +238,7 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
232238
RAGResult(noAnswerStory = getNoAnswerRAGStory(ragConfiguration))
233239
}
234240
else RAGResult(
235-
answer = TextWithFootnotes(text = technicalErrorMessage),
241+
answer = LLMAnswer(status="error", answer = technicalErrorMessage),
236242
debug = when(exc) {
237243
is GenAIOrchestratorBusinessError -> RAGError(exc.message, exc.error)
238244
is GenAIOrchestratorValidationError -> RAGError(exc.message, exc.detail)
@@ -282,7 +288,8 @@ object RAGAnswerHandler : AbstractProactiveAnswerHandler {
282288
* Aggregation of RAG answer, debug and the no answer Story.
283289
*/
284290
data class RAGResult(
285-
val answer: TextWithFootnotes? = null,
291+
val answer: LLMAnswer? = null,
292+
val footnotes: List<ai.tock.genai.orchestratorclient.responses.Footnote>? = null,
286293
val debug: Any? = null,
287294
val noAnswerStory: StoryDefinition? = null,
288295
val observabilityInfo: ObservabilityInfo? = null,

gen-ai/orchestrator-client/src/main/kotlin/ai/tock/genai/orchestratorclient/responses/RAGResponse.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
package ai.tock.genai.orchestratorclient.responses
1818

1919
data class RAGResponse(
20-
val answer: TextWithFootnotes,
20+
val answer: LLMAnswer,
21+
val footnotes: List<Footnote> = emptyList(),
2122
val debug: Any? = null,
2223
val observabilityInfo: ObservabilityInfo? = null,
2324
)

gen-ai/orchestrator-client/src/main/kotlin/ai/tock/genai/orchestratorclient/responses/models.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@
1717
package ai.tock.genai.orchestratorclient.responses
1818

1919

20-
data class TextWithFootnotes(
21-
val text: String,
22-
val footnotes: List<Footnote> = emptyList(),
20+
data class ChunkSentences(
21+
val chunk: Int,
22+
val sentences: List<String> = emptyList(),
23+
val reason: String? = null,
24+
)
25+
26+
data class LLMAnswer(
27+
val status: String,
28+
val answer: String,
29+
val topic: String? = null,
30+
val suggestedTopics: List<String>? = null,
31+
val context: List<ChunkSentences>? = null,
2332
)
2433

2534
data class Footnote(

gen-ai/orchestrator-server/src/main/python/server/src/gen_ai_orchestrator/models/rag/rag_models.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,17 @@ class Footnote(Source):
5252

5353
identifier: str = Field(description='Footnote identifier', examples=['1'])
5454

55-
56-
class TextWithFootnotes(BaseModel):
57-
"""Text with its footnotes. Used for RAG response"""
58-
59-
text: str = Field(
60-
description='Text with footnotes used to list outside sources',
61-
examples=['This is page content [1], and this is more content [2]'],
62-
)
63-
footnotes: set[Footnote] = Field(description='Set of footnotes')
64-
55+
class ChunkSentences(BaseModel):
56+
chunk: int
57+
sentences: List[str] = []
58+
reason: Optional[str] = None
59+
60+
class LLMAnswer(BaseModel):
61+
status: Optional[str] = None
62+
answer: Optional[str] = None
63+
topic: Optional[str] = None
64+
suggested_topics: Optional[List[str]] = None
65+
context: Optional[List[ChunkSentences]] = None
6566

6667
@unique
6768
class ChatMessageType(str, Enum):
@@ -154,4 +155,4 @@ class RAGDebugData(QADebugData):
154155
'Question: Hello, how to plan a trip to Morocco ?. Answer in French.'
155156
],
156157
)
157-
answer: str = Field(description='The RAG answer.')
158+
answer: LLMAnswer = Field(description='The RAG answer.')

gen-ai/orchestrator-server/src/main/python/server/src/gen_ai_orchestrator/routers/responses/responses.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525
ErrorInfo,
2626
)
2727
from gen_ai_orchestrator.models.llm.llm_provider import LLMProvider
28-
from gen_ai_orchestrator.models.rag.rag_models import Source, TextWithFootnotes
28+
from gen_ai_orchestrator.models.rag.rag_models import Source, LLMAnswer, Footnote
2929
from gen_ai_orchestrator.models.observability.observability_provider import ObservabilityProvider
30-
from gen_ai_orchestrator.models.rag.rag_models import TextWithFootnotes
3130
from gen_ai_orchestrator.models.vector_stores.vectore_store_provider import VectorStoreProvider
3231

3332

@@ -122,9 +121,10 @@ class ObservabilityInfo(BaseModel):
122121
class RAGResponse(BaseModel):
123122
"""The RAG response model"""
124123

125-
answer: TextWithFootnotes = Field(
126-
description='The RAG answer, with outside sources.'
124+
answer: Optional[LLMAnswer] = Field(
125+
description='The RAG answer'
127126
)
127+
footnotes: set[Footnote] = Field(description='Set of footnotes')
128128
debug: Optional[Any] = Field(
129129
description='Debug data',
130130
examples=[{'action': 'retrieve', 'result': 'OK', 'errors': []}],

gen-ai/orchestrator-server/src/main/python/server/src/gen_ai_orchestrator/services/langchain/factories/vector_stores/open_search_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def get_vector_store(self, async_mode: Optional[bool] = True) -> OpenSearchVecto
6161
# By default, is the same as host. If set to False, it will not verify hostname on certificate
6262
ssl_assert_hostname=self.setting.host if is_prod_environment else False,
6363
ssl_show_warn=is_prod_environment,
64-
index_name=self.index_name,
64+
index_name="ns-salledesmarchs-bot-sdm-session-f13384dd-084c-4414-bfbf-ad1f51e81490",
6565
embedding_function=self.embedding_function,
6666
timeout=application_settings.vector_store_timeout,
6767
)

0 commit comments

Comments
 (0)