Spaces:
Runtime error
Runtime error
Convert more service methods to async; drop `VectorDatabaseService` intermediary and use the repository classes directly
Browse files- notebooks/container.ipynb +0 -102
- notebooks/google_drive_web_vtt_vectorizer_and_storer.ipynb +23 -5
- src/ctp_slack_bot/containers.py +4 -8
- src/ctp_slack_bot/services/__init__.py +0 -1
- src/ctp_slack_bot/services/answer_retrieval_service.py +1 -1
- src/ctp_slack_bot/services/content_ingestion_service.py +11 -11
- src/ctp_slack_bot/services/context_retrieval_service.py +5 -4
- src/ctp_slack_bot/services/embeddings_model_service.py +4 -4
- src/ctp_slack_bot/services/language_model_service.py +4 -4
- src/ctp_slack_bot/services/question_dispatch_service.py +0 -1
- src/ctp_slack_bot/services/vector_database_service.py +0 -67
- src/ctp_slack_bot/services/vectorization_service.py +2 -2
notebooks/container.ipynb
DELETED
@@ -1,102 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"cells": [
|
3 |
-
{
|
4 |
-
"cell_type": "markdown",
|
5 |
-
"metadata": {},
|
6 |
-
"source": [
|
7 |
-
"# Loading Dependency Injection Container in Jupyter Notebook"
|
8 |
-
]
|
9 |
-
},
|
10 |
-
{
|
11 |
-
"cell_type": "code",
|
12 |
-
"execution_count": 4,
|
13 |
-
"metadata": {},
|
14 |
-
"outputs": [],
|
15 |
-
"source": [
|
16 |
-
"from ctp_slack_bot.containers import Container\n",
|
17 |
-
"from ctp_slack_bot.services import VectorDatabaseService\n",
|
18 |
-
"\n",
|
19 |
-
"container = Container()\n",
|
20 |
-
"container.wire(packages=['ctp_slack_bot'])"
|
21 |
-
]
|
22 |
-
},
|
23 |
-
{
|
24 |
-
"cell_type": "code",
|
25 |
-
"execution_count": 2,
|
26 |
-
"metadata": {},
|
27 |
-
"outputs": [
|
28 |
-
{
|
29 |
-
"name": "stderr",
|
30 |
-
"output_type": "stream",
|
31 |
-
"text": [
|
32 |
-
"\u001b[32m2025-04-19 16:43:46.927\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.core.config\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m14\u001b[0m - \u001b[34m\u001b[1mCreated Settings\u001b[0m\n"
|
33 |
-
]
|
34 |
-
},
|
35 |
-
{
|
36 |
-
"data": {
|
37 |
-
"text/plain": [
|
38 |
-
"Settings(LOG_LEVEL='INFO', LOG_FORMAT='json', SCHEDULER_TIMEZONE='America/New_York', SLACK_BOT_TOKEN=SecretStr('**********'), SLACK_APP_TOKEN=SecretStr('**********'), EMBEDDING_MODEL='text-embedding-3-small', VECTOR_DIMENSION=1536, CHUNK_SIZE=1000, CHUNK_OVERLAP=200, TOP_K_MATCHES=5, MONGODB_URI=SecretStr('**********'), MONGODB_NAME='ctp_slack_bot', SCORE_THRESHOLD=0.5, HF_API_TOKEN=SecretStr('**********'), OPENAI_API_KEY=SecretStr('**********'), CHAT_MODEL='gpt-3.5-turbo', MAX_TOKENS=150, TEMPERATURE=0.8, SYSTEM_PROMPT=\"You are a helpful teaching assistant for a data science class.\\nBased on the students question, you will be given context retreived from class transcripts and materials to answer their question.\\nYour responses should be:\\n\\n1. Accurate and based on the class content\\n2. Clear and educational\\n3. Concise but complete\\nIf you're unsure about something, acknowledge it and suggest asking the professor.\", GOOGLE_PROJECT_ID='voltaic-reducer-294821', GOOGLE_PRIVATE_KEY_ID=SecretStr('**********'), GOOGLE_PRIVATE_KEY=SecretStr('**********'), GOOGLE_CLIENT_ID='102943207835073856980', GOOGLE_CLIENT_EMAIL='[email protected]', GOOGLE_AUTH_URI='https://accounts.google.com/o/oauth2/auth', GOOGLE_TOKEN_URI='https://oauth2.googleapis.com/token', GOOGLE_AUTH_PROVIDER_CERT_URL='https://www.googleapis.com/oauth2/v1/certs', GOOGLE_CLIENT_CERT_URL='https://www.googleapis.com/robot/v1/metadata/x509/ctp-slack-bot-714%40voltaic-reducer-294821.iam.gserviceaccount.com', GOOGLE_UNIVERSE_DOMAIN='googleapis.com', FILE_MONITOR_ROOT_PATH='Transcripts/Friday Building AI Applications Session')"
|
39 |
-
]
|
40 |
-
},
|
41 |
-
"execution_count": 2,
|
42 |
-
"metadata": {},
|
43 |
-
"output_type": "execute_result"
|
44 |
-
}
|
45 |
-
],
|
46 |
-
"source": [
|
47 |
-
"container.settings()"
|
48 |
-
]
|
49 |
-
},
|
50 |
-
{
|
51 |
-
"cell_type": "code",
|
52 |
-
"execution_count": null,
|
53 |
-
"metadata": {},
|
54 |
-
"outputs": [
|
55 |
-
{
|
56 |
-
"name": "stderr",
|
57 |
-
"output_type": "stream",
|
58 |
-
"text": [
|
59 |
-
"\u001b[32m2025-04-19 16:45:25.997\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.core.config\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m14\u001b[0m - \u001b[34m\u001b[1mCreated Settings\u001b[0m\n"
|
60 |
-
]
|
61 |
-
},
|
62 |
-
{
|
63 |
-
"name": "stderr",
|
64 |
-
"output_type": "stream",
|
65 |
-
"text": [
|
66 |
-
"\u001b[32m2025-04-19 16:45:25.999\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36minit\u001b[0m:\u001b[36m175\u001b[0m - \u001b[1mInitializing MongoDB connection for database: ctp_slack_bot\u001b[0m\n",
|
67 |
-
"\u001b[32m2025-04-19 16:45:25.999\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m26\u001b[0m - \u001b[34m\u001b[1mCreated MongoDB\u001b[0m\n",
|
68 |
-
"\u001b[32m2025-04-19 16:45:25.999\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36mconnect\u001b[0m:\u001b[36m32\u001b[0m - \u001b[34m\u001b[1mConnecting to MongoDB using URI: mongodb+srv://ctp-slack-bot.xkipuvm.mongodb.net/?retryWrites=true&w=majority&appName=ctp-slack-bot\u001b[0m\n",
|
69 |
-
"\u001b[32m2025-04-19 16:45:26.000\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36mconnect\u001b[0m:\u001b[36m49\u001b[0m - \u001b[34m\u001b[1mMongoDB client initialized for database: ctp_slack_bot\u001b[0m\n",
|
70 |
-
"\u001b[32m2025-04-19 16:45:26.279\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36mping\u001b[0m:\u001b[36m85\u001b[0m - \u001b[34m\u001b[1mMongoDB connection is active!\u001b[0m\n",
|
71 |
-
"\u001b[32m2025-04-19 16:45:26.280\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mctp_slack_bot.db.mongo_db\u001b[0m:\u001b[36m_test_connection\u001b[0m:\u001b[36m186\u001b[0m - \u001b[1mMongoDB connection test successful!\u001b[0m\n",
|
72 |
-
"\u001b[32m2025-04-19 16:45:26.280\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mctp_slack_bot.services.vector_database_service\u001b[0m:\u001b[36m__init__\u001b[0m:\u001b[36m21\u001b[0m - \u001b[34m\u001b[1mCreated VectorDatabaseService\u001b[0m\n"
|
73 |
-
]
|
74 |
-
}
|
75 |
-
],
|
76 |
-
"source": [
|
77 |
-
"vector_database_service: VectorDatabaseService = container.vector_database_service()"
|
78 |
-
]
|
79 |
-
}
|
80 |
-
],
|
81 |
-
"metadata": {
|
82 |
-
"kernelspec": {
|
83 |
-
"display_name": ".venv",
|
84 |
-
"language": "python",
|
85 |
-
"name": "python3"
|
86 |
-
},
|
87 |
-
"language_info": {
|
88 |
-
"codemirror_mode": {
|
89 |
-
"name": "ipython",
|
90 |
-
"version": 3
|
91 |
-
},
|
92 |
-
"file_extension": ".py",
|
93 |
-
"mimetype": "text/x-python",
|
94 |
-
"name": "python",
|
95 |
-
"nbconvert_exporter": "python",
|
96 |
-
"pygments_lexer": "ipython3",
|
97 |
-
"version": "3.12.3"
|
98 |
-
}
|
99 |
-
},
|
100 |
-
"nbformat": 4,
|
101 |
-
"nbformat_minor": 2
|
102 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notebooks/google_drive_web_vtt_vectorizer_and_storer.ipynb
CHANGED
@@ -30,7 +30,7 @@
|
|
30 |
"mongo_db = await container.mongo_db()\n",
|
31 |
"google_drive_service = container.google_drive_service()\n",
|
32 |
"vectorization_service = container.vectorization_service()\n",
|
33 |
-
"
|
34 |
]
|
35 |
},
|
36 |
{
|
@@ -50,7 +50,7 @@
|
|
50 |
"source": [
|
51 |
"MIME_TYPE = \"text/vtt\" # This should probably not be changed.\n",
|
52 |
"\n",
|
53 |
-
"MODIFICATION_TIME_CUTOFF = datetime(2024,
|
54 |
]
|
55 |
},
|
56 |
{
|
@@ -117,10 +117,28 @@
|
|
117 |
"for web_vtt in web_vtts:\n",
|
118 |
" chunks = web_vtt.get_chunks()\n",
|
119 |
" display_html(f\"Chunked {web_vtt.get_metadata().get(\"filename\")} into {len(chunks)} chunks.\")\n",
|
120 |
-
" vectorized_chunks = vectorization_service.vectorize(chunks)\n",
|
121 |
" display_html(f\"Vectorized {web_vtt.get_metadata().get(\"filename\")}’s {len(vectorized_chunks)} chunks.\")\n",
|
122 |
-
" await
|
123 |
-
" display_html(f\"Stored {web_vtt.get_metadata().get(\"filename\")}’s {len(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
]
|
125 |
}
|
126 |
],
|
|
|
30 |
"mongo_db = await container.mongo_db()\n",
|
31 |
"google_drive_service = container.google_drive_service()\n",
|
32 |
"vectorization_service = container.vectorization_service()\n",
|
33 |
+
"vectorized_chunk_repository = await container.vectorized_chunk_repository()"
|
34 |
]
|
35 |
},
|
36 |
{
|
|
|
50 |
"source": [
|
51 |
"MIME_TYPE = \"text/vtt\" # This should probably not be changed.\n",
|
52 |
"\n",
|
53 |
+
"MODIFICATION_TIME_CUTOFF = datetime(2024, 7, 1, tzinfo=ZoneInfo(\"UTC\"))"
|
54 |
]
|
55 |
},
|
56 |
{
|
|
|
117 |
"for web_vtt in web_vtts:\n",
|
118 |
" chunks = web_vtt.get_chunks()\n",
|
119 |
" display_html(f\"Chunked {web_vtt.get_metadata().get(\"filename\")} into {len(chunks)} chunks.\")\n",
|
120 |
+
" vectorized_chunks = await vectorization_service.vectorize(chunks)\n",
|
121 |
" display_html(f\"Vectorized {web_vtt.get_metadata().get(\"filename\")}’s {len(vectorized_chunks)} chunks.\")\n",
|
122 |
+
" inserted_ids = await vectorized_chunk_repository.insert_many(vectorized_chunks)\n",
|
123 |
+
" display_html(f\"Stored {web_vtt.get_metadata().get(\"filename\")}’s {len(inserted_ids)} vectorized chunks to the database.\")"
|
124 |
+
]
|
125 |
+
},
|
126 |
+
{
|
127 |
+
"cell_type": "markdown",
|
128 |
+
"metadata": {},
|
129 |
+
"source": [
|
130 |
+
"## Close MongoDB Connection\n",
|
131 |
+
"\n",
|
132 |
+
"Don’t forget to clean up…"
|
133 |
+
]
|
134 |
+
},
|
135 |
+
{
|
136 |
+
"cell_type": "code",
|
137 |
+
"execution_count": null,
|
138 |
+
"metadata": {},
|
139 |
+
"outputs": [],
|
140 |
+
"source": [
|
141 |
+
"mongo_db.close()"
|
142 |
]
|
143 |
}
|
144 |
],
|
src/ctp_slack_bot/containers.py
CHANGED
@@ -19,14 +19,13 @@ from ctp_slack_bot.services.language_model_service import LanguageModelService
|
|
19 |
from ctp_slack_bot.services.question_dispatch_service import QuestionDispatchService
|
20 |
from ctp_slack_bot.services.schedule_service import ScheduleServiceResource
|
21 |
from ctp_slack_bot.services.slack_service import SlackServiceResource
|
22 |
-
from ctp_slack_bot.services.vector_database_service import VectorDatabaseService
|
23 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
24 |
|
25 |
|
26 |
def __load_plugins(plugin_dir) -> None:
|
27 |
for path in Path(plugin_dir).glob("*.py"):
|
28 |
if path.stem == "__init__":
|
29 |
-
continue
|
30 |
module_name = f"{plugin_dir.replace('/', '.')}.{path.stem}"
|
31 |
import_module(module_name)
|
32 |
|
@@ -35,7 +34,7 @@ __load_plugins("ctp_slack_bot/mime_type_handlers")
|
|
35 |
|
36 |
|
37 |
class Container(DeclarativeContainer): # TODO: audit for potential async-related bugs.
|
38 |
-
settings
|
39 |
event_brokerage_service = Singleton(EventBrokerageService)
|
40 |
schedule_service = Resource (ScheduleServiceResource,
|
41 |
settings=settings)
|
@@ -44,9 +43,6 @@ class Container(DeclarativeContainer): # TODO: audit for potential async-related
|
|
44 |
vectorized_chunk_repository = Resource (MongoVectorizedChunkRepositoryResource,
|
45 |
settings=settings,
|
46 |
mongo_db=mongo_db)
|
47 |
-
vector_database_service = Singleton(VectorDatabaseService,
|
48 |
-
settings=settings,
|
49 |
-
vectorized_chunk_repository=vectorized_chunk_repository)
|
50 |
embeddings_model_service = Singleton(EmbeddingsModelService,
|
51 |
settings=settings)
|
52 |
vectorization_service = Singleton(VectorizationService,
|
@@ -55,12 +51,12 @@ class Container(DeclarativeContainer): # TODO: audit for potential async-related
|
|
55 |
content_ingestion_service = Singleton(ContentIngestionService,
|
56 |
settings=settings,
|
57 |
event_brokerage_service=event_brokerage_service,
|
58 |
-
|
59 |
vectorization_service=vectorization_service)
|
60 |
context_retrieval_service = Singleton(ContextRetrievalService,
|
61 |
settings=settings,
|
62 |
vectorization_service=vectorization_service,
|
63 |
-
|
64 |
language_model_service = Singleton(LanguageModelService,
|
65 |
settings=settings)
|
66 |
answer_retrieval_service = Singleton(AnswerRetrievalService,
|
|
|
19 |
from ctp_slack_bot.services.question_dispatch_service import QuestionDispatchService
|
20 |
from ctp_slack_bot.services.schedule_service import ScheduleServiceResource
|
21 |
from ctp_slack_bot.services.slack_service import SlackServiceResource
|
|
|
22 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
23 |
|
24 |
|
25 |
def __load_plugins(plugin_dir) -> None:
|
26 |
for path in Path(plugin_dir).glob("*.py"):
|
27 |
if path.stem == "__init__":
|
28 |
+
continue # Skip __init__.py files
|
29 |
module_name = f"{plugin_dir.replace('/', '.')}.{path.stem}"
|
30 |
import_module(module_name)
|
31 |
|
|
|
34 |
|
35 |
|
36 |
class Container(DeclarativeContainer): # TODO: audit for potential async-related bugs.
|
37 |
+
settings = Singleton(Settings)
|
38 |
event_brokerage_service = Singleton(EventBrokerageService)
|
39 |
schedule_service = Resource (ScheduleServiceResource,
|
40 |
settings=settings)
|
|
|
43 |
vectorized_chunk_repository = Resource (MongoVectorizedChunkRepositoryResource,
|
44 |
settings=settings,
|
45 |
mongo_db=mongo_db)
|
|
|
|
|
|
|
46 |
embeddings_model_service = Singleton(EmbeddingsModelService,
|
47 |
settings=settings)
|
48 |
vectorization_service = Singleton(VectorizationService,
|
|
|
51 |
content_ingestion_service = Singleton(ContentIngestionService,
|
52 |
settings=settings,
|
53 |
event_brokerage_service=event_brokerage_service,
|
54 |
+
vectorized_chunk_repository=vectorized_chunk_repository,
|
55 |
vectorization_service=vectorization_service)
|
56 |
context_retrieval_service = Singleton(ContextRetrievalService,
|
57 |
settings=settings,
|
58 |
vectorization_service=vectorization_service,
|
59 |
+
vectorized_chunk_repository=vectorized_chunk_repository)
|
60 |
language_model_service = Singleton(LanguageModelService,
|
61 |
settings=settings)
|
62 |
answer_retrieval_service = Singleton(AnswerRetrievalService,
|
src/ctp_slack_bot/services/__init__.py
CHANGED
@@ -7,5 +7,4 @@ from ctp_slack_bot.services.google_drive_service import GoogleDriveService
|
|
7 |
from ctp_slack_bot.services.language_model_service import LanguageModelService
|
8 |
from ctp_slack_bot.services.question_dispatch_service import QuestionDispatchService
|
9 |
from ctp_slack_bot.services.slack_service import SlackService
|
10 |
-
from ctp_slack_bot.services.vector_database_service import VectorDatabaseService
|
11 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
|
|
7 |
from ctp_slack_bot.services.language_model_service import LanguageModelService
|
8 |
from ctp_slack_bot.services.question_dispatch_service import QuestionDispatchService
|
9 |
from ctp_slack_bot.services.slack_service import SlackService
|
|
|
10 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
src/ctp_slack_bot/services/answer_retrieval_service.py
CHANGED
@@ -28,7 +28,7 @@ class AnswerRetrievalService(BaseModel):
|
|
28 |
async def push(self: Self, question: SlackMessage, context: Collection[Chunk]) -> None:
|
29 |
channel_to_respond_to = question.channel
|
30 |
thread_to_respond_to = question.thread_ts if question.thread_ts else question.ts
|
31 |
-
answer = self.language_model_service.answer_question(question.text, context)
|
32 |
logger.debug("Pushing response to channel {} and thread {}: {}", channel_to_respond_to, thread_to_respond_to, answer)
|
33 |
slack_response = SlackResponse(text=answer, channel=channel_to_respond_to, thread_ts=thread_to_respond_to)
|
34 |
await self.event_brokerage_service.publish(EventType.OUTGOING_SLACK_RESPONSE, slack_response)
|
|
|
28 |
async def push(self: Self, question: SlackMessage, context: Collection[Chunk]) -> None:
|
29 |
channel_to_respond_to = question.channel
|
30 |
thread_to_respond_to = question.thread_ts if question.thread_ts else question.ts
|
31 |
+
answer = await self.language_model_service.answer_question(question.text, context)
|
32 |
logger.debug("Pushing response to channel {} and thread {}: {}", channel_to_respond_to, thread_to_respond_to, answer)
|
33 |
slack_response = SlackResponse(text=answer, channel=channel_to_respond_to, thread_ts=thread_to_respond_to)
|
34 |
await self.event_brokerage_service.publish(EventType.OUTGOING_SLACK_RESPONSE, slack_response)
|
src/ctp_slack_bot/services/content_ingestion_service.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1 |
from loguru import logger
|
2 |
from pydantic import BaseModel
|
3 |
-
from typing import Self, Sequence
|
4 |
|
5 |
from ctp_slack_bot.core import Settings
|
|
|
6 |
from ctp_slack_bot.enums import EventType
|
7 |
from ctp_slack_bot.models import Chunk, Content, SlackMessage
|
8 |
from ctp_slack_bot.services.event_brokerage_service import EventBrokerageService
|
9 |
-
from ctp_slack_bot.services.vector_database_service import VectorDatabaseService
|
10 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
11 |
|
12 |
class ContentIngestionService(BaseModel):
|
@@ -16,7 +16,7 @@ class ContentIngestionService(BaseModel):
|
|
16 |
|
17 |
settings: Settings
|
18 |
event_brokerage_service: EventBrokerageService
|
19 |
-
|
20 |
vectorization_service: VectorizationService
|
21 |
|
22 |
class Config:
|
@@ -30,19 +30,19 @@ class ContentIngestionService(BaseModel):
|
|
30 |
|
31 |
async def process_incoming_content(self: Self, content: Content) -> None:
|
32 |
logger.debug("Content ingestion service received content with metadata: {}", content.get_metadata())
|
33 |
-
if self.
|
34 |
logger.debug("Ignored content with identifier, {}, because it already exists in the database.", content.get_id())
|
35 |
return
|
36 |
chunks = content.get_chunks()
|
37 |
-
await self.__vectorize_and_store_chunks_in_database(chunks)
|
38 |
-
logger.debug("Stored {} vectorized chunk(s) in the database.", len(
|
39 |
|
40 |
async def process_incoming_slack_message(self: Self, slack_message: SlackMessage) -> None:
|
41 |
logger.debug("Content ingestion service received a Slack message: {}", slack_message.text)
|
42 |
chunks = slack_message.get_chunks()
|
43 |
-
await self.__vectorize_and_store_chunks_in_database(chunks)
|
44 |
-
logger.debug("Stored {} vectorized chunk(s) in the database.", len(
|
45 |
|
46 |
-
async def __vectorize_and_store_chunks_in_database(self: Self, chunks: Sequence[Chunk]) ->
|
47 |
-
vectorized_chunks = self.vectorization_service.vectorize(chunks)
|
48 |
-
await self.
|
|
|
1 |
from loguru import logger
|
2 |
from pydantic import BaseModel
|
3 |
+
from typing import Self, Sequence, Set
|
4 |
|
5 |
from ctp_slack_bot.core import Settings
|
6 |
+
from ctp_slack_bot.db.repositories import VectorizedChunkRepository
|
7 |
from ctp_slack_bot.enums import EventType
|
8 |
from ctp_slack_bot.models import Chunk, Content, SlackMessage
|
9 |
from ctp_slack_bot.services.event_brokerage_service import EventBrokerageService
|
|
|
10 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
11 |
|
12 |
class ContentIngestionService(BaseModel):
|
|
|
16 |
|
17 |
settings: Settings
|
18 |
event_brokerage_service: EventBrokerageService
|
19 |
+
vectorized_chunk_repository: VectorizedChunkRepository
|
20 |
vectorization_service: VectorizationService
|
21 |
|
22 |
class Config:
|
|
|
30 |
|
31 |
async def process_incoming_content(self: Self, content: Content) -> None:
|
32 |
logger.debug("Content ingestion service received content with metadata: {}", content.get_metadata())
|
33 |
+
if self.vectorized_chunk_repository.count_by_id(content.get_id()):
|
34 |
logger.debug("Ignored content with identifier, {}, because it already exists in the database.", content.get_id())
|
35 |
return
|
36 |
chunks = content.get_chunks()
|
37 |
+
inserted_ids = await self.__vectorize_and_store_chunks_in_database(chunks)
|
38 |
+
logger.debug("Stored {} vectorized chunk(s) in the database.", len(inserted_ids))
|
39 |
|
40 |
async def process_incoming_slack_message(self: Self, slack_message: SlackMessage) -> None:
|
41 |
logger.debug("Content ingestion service received a Slack message: {}", slack_message.text)
|
42 |
chunks = slack_message.get_chunks()
|
43 |
+
inserted_ids = await self.__vectorize_and_store_chunks_in_database(chunks)
|
44 |
+
logger.debug("Stored {} vectorized chunk(s) in the database.", len(inserted_ids))
|
45 |
|
46 |
+
async def __vectorize_and_store_chunks_in_database(self: Self, chunks: Sequence[Chunk]) -> Set[str]:
|
47 |
+
vectorized_chunks = await self.vectorization_service.vectorize(chunks)
|
48 |
+
return await self.vectorized_chunk_repository.insert_many(vectorized_chunks)
|
src/ctp_slack_bot/services/context_retrieval_service.py
CHANGED
@@ -3,8 +3,8 @@ from pydantic import BaseModel
|
|
3 |
from typing import Self, Sequence
|
4 |
|
5 |
from ctp_slack_bot.core.config import Settings
|
|
|
6 |
from ctp_slack_bot.models import Chunk, SlackMessage, VectorQuery, VectorizedChunk
|
7 |
-
from ctp_slack_bot.services.vector_database_service import VectorDatabaseService
|
8 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
9 |
|
10 |
class ContextRetrievalService(BaseModel):
|
@@ -14,7 +14,7 @@ class ContextRetrievalService(BaseModel):
|
|
14 |
|
15 |
settings: Settings
|
16 |
vectorization_service: VectorizationService
|
17 |
-
|
18 |
|
19 |
class Config:
|
20 |
frozen=True
|
@@ -37,7 +37,7 @@ class ContextRetrievalService(BaseModel):
|
|
37 |
message_chunks = message.get_chunks() # Guaranteed to have exactly 1 chunk
|
38 |
|
39 |
try:
|
40 |
-
vectorized_message_chunks = self.vectorization_service.vectorize(message_chunks)
|
41 |
except Exception as e:
|
42 |
logger.error("An error occurred while vectorizing the question, “{}”: {}", message.text, e)
|
43 |
|
@@ -49,7 +49,8 @@ class ContextRetrievalService(BaseModel):
|
|
49 |
)
|
50 |
|
51 |
try:
|
52 |
-
results = await self.
|
|
|
53 |
return results
|
54 |
except Exception as e:
|
55 |
logger.error("An error occurred while searching the vector database for context: {}", e)
|
|
|
3 |
from typing import Self, Sequence
|
4 |
|
5 |
from ctp_slack_bot.core.config import Settings
|
6 |
+
from ctp_slack_bot.db.repositories import VectorizedChunkRepository
|
7 |
from ctp_slack_bot.models import Chunk, SlackMessage, VectorQuery, VectorizedChunk
|
|
|
8 |
from ctp_slack_bot.services.vectorization_service import VectorizationService
|
9 |
|
10 |
class ContextRetrievalService(BaseModel):
|
|
|
14 |
|
15 |
settings: Settings
|
16 |
vectorization_service: VectorizationService
|
17 |
+
vectorized_chunk_repository: VectorizedChunkRepository
|
18 |
|
19 |
class Config:
|
20 |
frozen=True
|
|
|
37 |
message_chunks = message.get_chunks() # Guaranteed to have exactly 1 chunk
|
38 |
|
39 |
try:
|
40 |
+
vectorized_message_chunks = await self.vectorization_service.vectorize(message_chunks)
|
41 |
except Exception as e:
|
42 |
logger.error("An error occurred while vectorizing the question, “{}”: {}", message.text, e)
|
43 |
|
|
|
49 |
)
|
50 |
|
51 |
try:
|
52 |
+
results = await self.vectorized_chunk_repository.find_by_vector(query)
|
53 |
+
logger.debug("Found {} chunks in the database by similarity search.", len(results))
|
54 |
return results
|
55 |
except Exception as e:
|
56 |
logger.error("An error occurred while searching the vector database for context: {}", e)
|
src/ctp_slack_bot/services/embeddings_model_service.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
from loguru import logger
|
2 |
-
from openai import
|
3 |
from pydantic import BaseModel, PrivateAttr
|
4 |
from typing import Any, Dict, Sequence, Self
|
5 |
|
@@ -18,10 +18,10 @@ class EmbeddingsModelService(BaseModel):
|
|
18 |
|
19 |
def __init__(self: Self, **data: Dict[str, Any]) -> None:
|
20 |
super().__init__(**data)
|
21 |
-
self._open_ai_client =
|
22 |
logger.debug("Created {}", self.__class__.__name__)
|
23 |
|
24 |
-
def get_embeddings(self: Self, texts: Sequence[str]) -> Sequence[Sequence[float]]:
|
25 |
"""Get embeddings for a collection of texts using OpenAI’s API.
|
26 |
|
27 |
Args:
|
@@ -34,7 +34,7 @@ class EmbeddingsModelService(BaseModel):
|
|
34 |
ValueError: If the embedding dimensions don't match expected size
|
35 |
"""
|
36 |
logger.debug("Creating embeddings for {} text string(s)…", len(texts))
|
37 |
-
response = self._open_ai_client.embeddings.create(
|
38 |
model=self.settings.EMBEDDING_MODEL,
|
39 |
input=texts,
|
40 |
encoding_format="float" # Ensure we get raw float values.
|
|
|
1 |
from loguru import logger
|
2 |
+
from openai import AsyncOpenAI
|
3 |
from pydantic import BaseModel, PrivateAttr
|
4 |
from typing import Any, Dict, Sequence, Self
|
5 |
|
|
|
18 |
|
19 |
def __init__(self: Self, **data: Dict[str, Any]) -> None:
|
20 |
super().__init__(**data)
|
21 |
+
self._open_ai_client = AsyncOpenAI(api_key=self.settings.OPENAI_API_KEY.get_secret_value())
|
22 |
logger.debug("Created {}", self.__class__.__name__)
|
23 |
|
24 |
+
async def get_embeddings(self: Self, texts: Sequence[str]) -> Sequence[Sequence[float]]:
|
25 |
"""Get embeddings for a collection of texts using OpenAI’s API.
|
26 |
|
27 |
Args:
|
|
|
34 |
ValueError: If the embedding dimensions don't match expected size
|
35 |
"""
|
36 |
logger.debug("Creating embeddings for {} text string(s)…", len(texts))
|
37 |
+
response = await self._open_ai_client.embeddings.create(
|
38 |
model=self.settings.EMBEDDING_MODEL,
|
39 |
input=texts,
|
40 |
encoding_format="float" # Ensure we get raw float values.
|
src/ctp_slack_bot/services/language_model_service.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
from loguru import logger
|
2 |
-
from openai import
|
3 |
from openai.types.chat import ChatCompletion
|
4 |
from pydantic import BaseModel, PrivateAttr
|
5 |
from typing import Collection, Self
|
@@ -20,10 +20,10 @@ class LanguageModelService(BaseModel):
|
|
20 |
|
21 |
def __init__(self: Self, **data) -> None:
|
22 |
super().__init__(**data)
|
23 |
-
self._open_ai_client =
|
24 |
logger.debug("Created {}", self.__class__.__name__)
|
25 |
|
26 |
-
def answer_question(self, question: str, context: Collection[Chunk]) -> str:
|
27 |
"""Generate a response using OpenAI’s API with retrieved context.
|
28 |
|
29 |
Args:
|
@@ -45,7 +45,7 @@ class LanguageModelService(BaseModel):
|
|
45 |
|
46 |
Please answer the Student Question based on the Context from class materials and transcripts. If the context doesn’t contain relevant information, acknowledge that and suggest asking the professor. Otherwise, carry on."""}
|
47 |
]
|
48 |
-
response: ChatCompletion = self._open_ai_client.chat.completions.create(
|
49 |
model=self.settings.CHAT_MODEL,
|
50 |
messages=messages,
|
51 |
max_tokens=self.settings.MAX_TOKENS,
|
|
|
1 |
from loguru import logger
|
2 |
+
from openai import AsyncOpenAI
|
3 |
from openai.types.chat import ChatCompletion
|
4 |
from pydantic import BaseModel, PrivateAttr
|
5 |
from typing import Collection, Self
|
|
|
20 |
|
21 |
def __init__(self: Self, **data) -> None:
|
22 |
super().__init__(**data)
|
23 |
+
self._open_ai_client = AsyncOpenAI(api_key=self.settings.OPENAI_API_KEY.get_secret_value())
|
24 |
logger.debug("Created {}", self.__class__.__name__)
|
25 |
|
26 |
+
async def answer_question(self, question: str, context: Collection[Chunk]) -> str:
|
27 |
"""Generate a response using OpenAI’s API with retrieved context.
|
28 |
|
29 |
Args:
|
|
|
45 |
|
46 |
Please answer the Student Question based on the Context from class materials and transcripts. If the context doesn’t contain relevant information, acknowledge that and suggest asking the professor. Otherwise, carry on."""}
|
47 |
]
|
48 |
+
response: ChatCompletion = await self._open_ai_client.chat.completions.create(
|
49 |
model=self.settings.CHAT_MODEL,
|
50 |
messages=messages,
|
51 |
max_tokens=self.settings.MAX_TOKENS,
|
src/ctp_slack_bot/services/question_dispatch_service.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
# from asyncio import create_task
|
2 |
from loguru import logger
|
3 |
from pydantic import BaseModel
|
4 |
from typing import Self
|
|
|
|
|
1 |
from loguru import logger
|
2 |
from pydantic import BaseModel
|
3 |
from typing import Self
|
src/ctp_slack_bot/services/vector_database_service.py
DELETED
@@ -1,67 +0,0 @@
|
|
1 |
-
from loguru import logger
|
2 |
-
from pydantic import BaseModel
|
3 |
-
from typing import Iterable, Optional, Self, Sequence
|
4 |
-
|
5 |
-
from ctp_slack_bot.core import Settings
|
6 |
-
from ctp_slack_bot.db.repositories import VectorizedChunkRepository
|
7 |
-
from ctp_slack_bot.models import Chunk, VectorizedChunk, VectorQuery
|
8 |
-
|
9 |
-
class VectorDatabaseService(BaseModel): # TODO: this should not rely specifically on MongoDB.
|
10 |
-
"""
|
11 |
-
Service for storing and retrieving vector embeddings from MongoDB.
|
12 |
-
"""
|
13 |
-
|
14 |
-
settings: Settings
|
15 |
-
vectorized_chunk_repository: VectorizedChunkRepository
|
16 |
-
|
17 |
-
class Config:
|
18 |
-
frozen=True
|
19 |
-
|
20 |
-
def __init__(self: Self, **data) -> None:
|
21 |
-
super().__init__(**data)
|
22 |
-
logger.debug("Created {}", self.__class__.__name__)
|
23 |
-
|
24 |
-
async def content_exists(self: Self, parent_id: str, chunk_id: Optional[str] = None)-> bool:
|
25 |
-
"""
|
26 |
-
Check if the content identified by the parent and optionally the chunk identifiers exist in the database.
|
27 |
-
|
28 |
-
Args:
|
29 |
-
parent_id: the identifier of the source content
|
30 |
-
chunk_id: the identifier of the chunk within the source content
|
31 |
-
"""
|
32 |
-
matching_chunk_count = await self.vectorized_chunk_repository.count_by_id(parent_id, chunk_id)
|
33 |
-
return 0 < matching_chunk_count
|
34 |
-
|
35 |
-
async def find_by_vector(self: Self, query: VectorQuery) -> Sequence[Chunk]:
|
36 |
-
"""
|
37 |
-
Query the vector database for similar chunks.
|
38 |
-
|
39 |
-
Args:
|
40 |
-
query: the query criteria
|
41 |
-
|
42 |
-
Returns:
|
43 |
-
Sequence[Chunk]: an ordered collection of similar chunks
|
44 |
-
"""
|
45 |
-
try:
|
46 |
-
result = await self.vectorized_chunk_repository.find_by_vector(query)
|
47 |
-
logger.debug("Found {} chunks in the database by similarity search.", len(result))
|
48 |
-
return result
|
49 |
-
except Exception as e:
|
50 |
-
logger.error("Error finding chunks by vector: {}", str(e))
|
51 |
-
raise
|
52 |
-
|
53 |
-
async def store(self: Self, chunks: Iterable[VectorizedChunk]) -> None:
|
54 |
-
"""
|
55 |
-
Stores vectorized chunks and their embedding vectors in the database.
|
56 |
-
|
57 |
-
Args:
|
58 |
-
chunks: a collection of vectorized chunks to store
|
59 |
-
|
60 |
-
Returns: None
|
61 |
-
"""
|
62 |
-
try:
|
63 |
-
inserted_ids = await self.vectorized_chunk_repository.insert_many(chunks)
|
64 |
-
logger.debug("Stored {} vectorized chunks in the database.", len(inserted_ids))
|
65 |
-
except Exception as e:
|
66 |
-
logger.error("Error storing vectorized chunks: {}", str(e))
|
67 |
-
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/ctp_slack_bot/services/vectorization_service.py
CHANGED
@@ -21,8 +21,8 @@ class VectorizationService(BaseModel):
|
|
21 |
super().__init__(**data)
|
22 |
logger.debug("Created {}", self.__class__.__name__)
|
23 |
|
24 |
-
def vectorize(self: Self, chunks: Sequence[Chunk]) -> Sequence[VectorizedChunk]:
|
25 |
-
embeddings = self.embeddings_model_service.get_embeddings([chunk.text for chunk in chunks])
|
26 |
return tuple(VectorizedChunk(
|
27 |
text=chunk.text,
|
28 |
parent_id=chunk.parent_id,
|
|
|
21 |
super().__init__(**data)
|
22 |
logger.debug("Created {}", self.__class__.__name__)
|
23 |
|
24 |
+
async def vectorize(self: Self, chunks: Sequence[Chunk]) -> Sequence[VectorizedChunk]:
|
25 |
+
embeddings = await self.embeddings_model_service.get_embeddings([chunk.text for chunk in chunks])
|
26 |
return tuple(VectorizedChunk(
|
27 |
text=chunk.text,
|
28 |
parent_id=chunk.parent_id,
|