LiKenun commited on
Commit
f7e11c1
·
1 Parent(s): 47ead95

Convert more service methods to async; drop `VectorDatabaseService` intermediary and use the repository classes directly

Browse files
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
- "vector_database_service = await container.vector_database_service()"
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, 8, 30, tzinfo=ZoneInfo(\"UTC\"))"
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 vector_database_service.store(vectorized_chunks)\n",
123
- " display_html(f\"Stored {web_vtt.get_metadata().get(\"filename\")}’s {len(vectorized_chunks)} vectorized chunks to the database.\")"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 # Skip __init__.py files
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 = Singleton(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
- vector_database_service=vector_database_service,
59
  vectorization_service=vectorization_service)
60
  context_retrieval_service = Singleton(ContextRetrievalService,
61
  settings=settings,
62
  vectorization_service=vectorization_service,
63
- vector_database_service=vector_database_service)
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
- vector_database_service: VectorDatabaseService
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.vector_database_service.content_exists(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
- await self.__vectorize_and_store_chunks_in_database(chunks)
38
- logger.debug("Stored {} vectorized chunk(s) in the database.", len(chunks))
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(chunks))
45
 
46
- async def __vectorize_and_store_chunks_in_database(self: Self, chunks: Sequence[Chunk]) -> None:
47
- vectorized_chunks = self.vectorization_service.vectorize(chunks)
48
- await self.vector_database_service.store(vectorized_chunks)
 
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
- vector_database_service: VectorDatabaseService
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.vector_database_service.find_by_vector(query)
 
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 OpenAI
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 = OpenAI(api_key=self.settings.OPENAI_API_KEY.get_secret_value())
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 OpenAI
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 = OpenAI(api_key=self.settings.OPENAI_API_KEY.get_secret_value())
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,