Skip to content

Commit

Permalink
[GenAIOrchestrator] Add QA chain (#1641)
Browse files Browse the repository at this point in the history
* Add QA chain

* ✅ Add QA chain tests

* 📝 Fix typo errors in documentation

* 🎨 Rename prompt_input to user_query
  • Loading branch information
killian-mahe authored Sep 3, 2024
1 parent 8a080df commit 316f456
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 111 deletions.
55 changes: 28 additions & 27 deletions gen-ai/orchestrator-server/src/main/python/server/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
from gen_ai_orchestrator.routers.app_monitors_router import (
application_check_router,
)
from gen_ai_orchestrator.routers.em_providers_router import em_providers_router
from gen_ai_orchestrator.routers.completion_router import completion_router
from gen_ai_orchestrator.routers.em_providers_router import em_providers_router
from gen_ai_orchestrator.routers.llm_providers_router import llm_providers_router
from gen_ai_orchestrator.routers.observability_providers_router import observability_providers_router
from gen_ai_orchestrator.routers.qa_router import qa_router
from gen_ai_orchestrator.routers.rag_router import rag_router

# configure logging
Expand All @@ -54,6 +55,7 @@
app.include_router(em_providers_router)
app.include_router(observability_providers_router)
app.include_router(rag_router)
app.include_router(qa_router)
app.include_router(completion_router)

logger.info('Generative AI Orchestrator - Startup')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,49 @@
"""Module for RAG Models"""

from enum import Enum, unique
from typing import Optional, List
from typing import List, Optional

from pydantic import AnyUrl, BaseModel, Field, HttpUrl

from gen_ai_orchestrator.models.vector_stores.vector_stores_types import DocumentSearchParams
from gen_ai_orchestrator.models.vector_stores.vector_stores_types import (
DocumentSearchParams,
)


class Footnote(BaseModel):
"""A footnote model, used to associate document sources with the RAG answer"""
class Source(BaseModel):
"""A source model, used to associate document sources with the QA response"""

identifier: str = Field(description='Footnote identifier', examples=['1'])
title: str = Field(description='Footnote title', examples=['Tock Documentation'])
title: str = Field(description='Source title', examples=['Tock Documentation'])
url: Optional[AnyUrl] = Field(
description='Footnote url', examples=['https://doc.tock.ai/tock/'], default=None
description='Source url', examples=['https://doc.tock.ai/tock/'], default=None
)
content: str = Field(
description='Source content', examples=['Tock: The Open Conversation Kit']
)
content: str = Field(description='Footnote content', examples=['Tock: The Open Conversation Kit'])

def __eq__(self, other):
"""
Footnotes are identified by their title and URL.
When the identifier or the content are not the same for identical footnotes,
Source are identified by their title and URL.
When the identifier or the content are not the same for identical sources,
this means that the sources are parts of the same document.
"""
return (
isinstance(other, Footnote)
and self.title == other.title
and self.url == other.url
and self.content == other.content
isinstance(other, Source)
and self.title == other.title
and self.url == other.url
and self.content == other.content
)

def __hash__(self):
return hash((self.title, self.url, self.content))


class Footnote(Source):
"""A footnote model, used to associate document sources with the RAG answer"""

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


class TextWithFootnotes(BaseModel):
"""Text with its footnotes. Used for RAG response"""

Expand Down Expand Up @@ -82,73 +91,75 @@ class RagDocumentMetadata(BaseModel):
index_session_id: str = Field(
description='The indexing session id.', examples=['123f-ed01-gt21-gg08']
)
id: str = Field(
description='The document id.', examples=['e014-g24-0f11-1g3e']
)
title: str = Field(
description='The document title.',
examples=['Tracking shot'])
id: str = Field(description='The document id.', examples=['e014-g24-0f11-1g3e'])
title: str = Field(description='The document title.', examples=['Tracking shot'])
url: Optional[HttpUrl] = Field(
description='The document url.',
examples=['https://en.wikipedia.org/wiki/Tracking_shot'],
default=None
)
chunk: str = Field(
description='The document chunk.',
examples=['1/3']
default=None,
)
chunk: str = Field(description='The document chunk.', examples=['1/3'])


class RagDocument(BaseModel):
"""The definition of RAG document"""

content: str = Field(
description='The document content.',
examples=['In cinematography, a tracking shot is any shot where the camera follows backward, '
'forward or moves alongside the subject being recorded.']
examples=[
'In cinematography, a tracking shot is any shot where the camera follows backward, '
'forward or moves alongside the subject being recorded.'
],
)
metadata: RagDocumentMetadata = Field(
description='The document metadata.',
)


class RagDebugData(BaseModel):
"""A RAG debug data"""
class QADebugData(BaseModel):
"""A QA debug data. This class is not currently used in the QA chain as Langfuse is now supported."""

user_question: Optional[str] = Field(
description='The user\'s initial question.',
examples=["I'm interested in going to Morocco"]
description="The user's initial question.",
examples=["I'm interested in going to Morocco"],
)
documents: List[RagDocument] = Field(
description='Documents retrieved from the vector store.'
)
document_index_name: str = Field(
description='Index name corresponding to a document collection in the vector database.',
)
document_search_params: DocumentSearchParams = Field(
description='The document search parameters. Ex: number of documents, metadata filter',
)
duration: float = Field(
description='The duration of RAG in seconds.', examples=['7.2']
)


class RagDebugData(QADebugData):
"""A RAG debug data"""

condense_question_prompt: Optional[str] = Field(
description='The prompt of the question rephrased with the history of the conversation.',
examples=["""Given the following conversation and a follow up question,
examples=[
"""Given the following conversation and a follow up question,
rephrase the follow up question to be a standalone question, in its original language.
Chat History:
Human: What travel offers are you proposing?
Assistant: We offer trips to all of Europe and North Africa.
Follow Up Input: I'm interested in going to Morocco
Standalone question:"""]
Standalone question:"""
],
)
condense_question: Optional[str] = Field(
description='The question rephrased with the history of the conversation.',
examples=['Hello, how to plan a trip to Morocco ?']
examples=['Hello, how to plan a trip to Morocco ?'],
)
question_answering_prompt: Optional[str] = Field(
description='The question answering prompt.',
examples=['Question: Hello, how to plan a trip to Morocco ?. Answer in French.']
)
documents: List[RagDocument] = Field(
description='Documents retrieved from the vector store.'
)
document_index_name: str = Field(
description='Index name corresponding to a document collection in the vector database.',
)
document_search_params: DocumentSearchParams = Field(
description='The document search parameters. Ex: number of documents, metadata filter',
)
answer: str = Field(
description='The RAG answer.'
)
duration: float = Field(
description='The duration of RAG in seconds.', examples=['7.2']
examples=[
'Question: Hello, how to plan a trip to Morocco ?. Answer in French.'
],
)
answer: str = Field(description='The RAG answer.')
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (C) 2023-2024 Credit Mutuel Arkea
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""QA Router Module"""

from fastapi import APIRouter

from gen_ai_orchestrator.routers.requests.requests import QAQuery
from gen_ai_orchestrator.routers.responses.responses import QAResponse
from gen_ai_orchestrator.services.qa.qa_service import qa

qa_router = APIRouter(prefix='/qa', tags=['Question Answering'])


@qa_router.post('')
async def ask_qa(query: QAQuery) -> QAResponse:
"""
## Ask a QA System
Ask question to a QA System, and return sources founded in a knowledge base (documents)
"""
return await qa(query)
Loading

0 comments on commit 316f456

Please sign in to comment.