Rauhan commited on
Commit
5ec1ba2
·
1 Parent(s): e457c6f
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN apt-get update && apt-get install -y
8
+
9
+ RUN pip install uv
10
+
11
+ RUN uv sync
12
+
13
+ EXPOSE 10000
14
+
15
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "10000"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rauhan Ahmed Siddiqui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
api/__init__.py ADDED
File without changes
api/models.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from enum import Enum
3
+
4
+ class LikedOrFlagged(str, Enum):
5
+ liked = "liked"
6
+ flagged = "flagged"
7
+
8
+ class WorkflowQuery(BaseModel):
9
+ query: str
10
+
11
+ class FlagOutput(BaseModel):
12
+ query: str
13
+ response: str
14
+ flag: LikedOrFlagged
15
+ feedback: str | None = None
api/services.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from api.models import FlagOutput, WorkflowQuery
2
+ from src.workflows.workflow import workflow
3
+ from sqlalchemy import create_engine, text
4
+ import os
5
+
6
+ class FastAPIService:
7
+ def __init__(self):
8
+ self.engine = create_engine(
9
+ os.environ.get("POSTGRE_CONNECTION_STRING")
10
+ )
11
+
12
+ def answerQuery(self, workflowQueryModel: WorkflowQuery) -> str:
13
+ return workflow.run(workflowQueryModel.query)
14
+
15
+ def flagResponse(self, likedOrFlaggedModel: FlagOutput) -> str:
16
+ with self.engine.connect() as conn:
17
+ conn.execute(
18
+ text("""
19
+ INSERT INTO feedback (query, response, flag, feedback)
20
+ VALUES (:query, :response, :flag, :feedback)
21
+ """),
22
+ {
23
+ "query": likedOrFlaggedModel.query,
24
+ "response": likedOrFlaggedModel.response,
25
+ "flag": likedOrFlaggedModel.flag,
26
+ "feedback": likedOrFlaggedModel.feedback
27
+ }
28
+ )
29
+ conn.commit()
30
+ return "Data inserted successfully"
config.ini ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [SQLAGENT]
2
+ modelName = zai-glm-4.6
3
+ maxTokens = 512
4
+ temperature = 0.5
5
+
6
+ [RAGAGENT]
7
+ denseEmbeddings = BAAI/bge-large-en-v1.5
8
+ sparseEmbeddings = Qdrant/bm25
9
+ modelName = llama-3.3-70b
10
+ maxTokens = 1024
11
+ temperature = 0.75
12
+
13
+ [REASONINGAGENT]
14
+ modelName = qwen-3-32b
15
+ temperature = 0.6
16
+
17
+ [SYNTHESIZERAGENT]
18
+ modelName = gpt-oss-120b
19
+ temperature = 0.6
20
+ maxTokens = 1250
demo/architecture.png ADDED
main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from api.models import FlagOutput, WorkflowQuery
2
+ from fastapi.exceptions import HTTPException
3
+ from fastapi.responses import JSONResponse
4
+ from api.services import FastAPIService
5
+ from fastapi import FastAPI
6
+
7
+ service = FastAPIService()
8
+ app = FastAPI(title = "Agentic Data Pipeline Endpoints")
9
+
10
+ @app.post("/answerQuery")
11
+ async def answerQuery(queryModel: WorkflowQuery):
12
+ try:
13
+ response = service.answerQuery(workflowQueryModel = queryModel)
14
+ return JSONResponse(status_code = 200, content = {"response": response})
15
+ except Exception as e:
16
+ raise HTTPException(status_code = 500, detail = str(e))
17
+
18
+ @app.post("/flag")
19
+ async def flagOutput(flagModel: FlagOutput):
20
+ try:
21
+ response = service.flagResponse(likedOrFlaggedModel = flagModel)
22
+ return JSONResponse(status_code = 200, content = {"response": response})
23
+ except Exception as e:
24
+ raise HTTPException(status_code = 500, detail = str(e))
notebooks/IndividualAgents.ipynb ADDED
@@ -0,0 +1,686 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {
7
+ "id": "5S7i2sK0aXZf"
8
+ },
9
+ "outputs": [],
10
+ "source": [
11
+ "!pip install -qU langchain langchain-community langchain-cerebras sqlalchemy langchain-core langchain-qdrant fastembed langchain-huggingface langgraph langgraph"
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "markdown",
16
+ "metadata": {
17
+ "id": "0-H0yyHyioEV"
18
+ },
19
+ "source": [
20
+ "## SQL Agent"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": null,
26
+ "metadata": {
27
+ "id": "NuqpdoNTitWT"
28
+ },
29
+ "outputs": [],
30
+ "source": [
31
+ "from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit\n",
32
+ "from langchain_community.utilities.sql_database import SQLDatabase\n",
33
+ "from langchain_community.tools.sql_database.tool import (\n",
34
+ " InfoSQLDatabaseTool,\n",
35
+ " ListSQLDatabaseTool,\n",
36
+ " QuerySQLCheckerTool,\n",
37
+ " QuerySQLDatabaseTool,\n",
38
+ ")\n",
39
+ "from langchain_cerebras import ChatCerebras\n",
40
+ "from langchain.agents import create_agent\n",
41
+ "from sqlalchemy.pool import StaticPool\n",
42
+ "from sqlalchemy import create_engine\n",
43
+ "from langchain_classic import hub\n",
44
+ "import os\n",
45
+ "\n",
46
+ "\n",
47
+ "promptTemplate = hub.pull(\"langchain-ai/sql-agent-system-prompt\")\n",
48
+ "systemMessage = promptTemplate.format(dialect=\"PostgreSQL\", top_k=5)\n",
49
+ "\n",
50
+ "class PostgreSQLAgent:\n",
51
+ " def __init__(self, connectionString: str) -> None:\n",
52
+ " self.engine = create_engine(connectionString, poolclass = StaticPool)\n",
53
+ " db = SQLDatabase(self.engine)\n",
54
+ " llm = ChatCerebras(\n",
55
+ " model = \"zai-glm-4.6\"\n",
56
+ " )\n",
57
+ " self.toolkit = SQLDatabaseToolkit(db = db, llm = llm)\n",
58
+ " self.agent = create_agent(llm, self.toolkit.get_tools(), system_prompt=systemMessage)\n",
59
+ "\n",
60
+ " def query(self, query) -> str:\n",
61
+ " response = self.agent.invoke(\n",
62
+ " {\"messages\": [(\"user\", query)]}\n",
63
+ " )\n",
64
+ " return response[\"messages\"][-1].content"
65
+ ]
66
+ },
67
+ {
68
+ "cell_type": "code",
69
+ "execution_count": null,
70
+ "metadata": {
71
+ "id": "paQ6BDQBcJa4"
72
+ },
73
+ "outputs": [],
74
+ "source": [
75
+ "pg = PostgreSQLAgent(os.environ.get(\"POSTGRE_CONNECTION_STRING\"))"
76
+ ]
77
+ },
78
+ {
79
+ "cell_type": "code",
80
+ "execution_count": 4,
81
+ "metadata": {
82
+ "colab": {
83
+ "base_uri": "https://localhost:8080/",
84
+ "height": 86
85
+ },
86
+ "id": "bBrhF6UHiS-J",
87
+ "outputId": "5897ab64-4127-48c0-9bac-67e668fff082"
88
+ },
89
+ "outputs": [
90
+ {
91
+ "data": {
92
+ "application/vnd.google.colaboratory.intrinsic+json": {
93
+ "type": "string"
94
+ },
95
+ "text/plain": [
96
+ "'\\nBased on the data from the database, the **AI Specialist** role offers the highest average salary at approximately **$120,571** per year.\\n\\nHere are the top 5 highest-paying job roles by average salary:\\n\\n1. **AI Specialist**: $120,571\\n2. **Machine Learning Engineer**: $118,828\\n3. **Head of AI**: $118,543\\n4. **AI Research Scientist**: $117,898\\n5. **AI Architect**: $117,437\\n\\nAll of the top-paying roles are in the artificial intelligence field, which reflects the high demand and specialized skills required for AI-related positions in the current job market.'"
97
+ ]
98
+ },
99
+ "execution_count": 4,
100
+ "metadata": {},
101
+ "output_type": "execute_result"
102
+ }
103
+ ],
104
+ "source": [
105
+ "pg.query(\"Which job role offers the highest average salary?\")"
106
+ ]
107
+ },
108
+ {
109
+ "cell_type": "markdown",
110
+ "metadata": {
111
+ "id": "ncujGlUalxJe"
112
+ },
113
+ "source": [
114
+ "## RAG Agent"
115
+ ]
116
+ },
117
+ {
118
+ "cell_type": "code",
119
+ "execution_count": 5,
120
+ "metadata": {
121
+ "id": "N4FPEtxPwz4K"
122
+ },
123
+ "outputs": [],
124
+ "source": [
125
+ "from langchain_qdrant import FastEmbedSparse, RetrievalMode\n",
126
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
127
+ "from langchain_qdrant import QdrantVectorStore\n",
128
+ "\n",
129
+ "modelName = \"BAAI/bge-large-en-v1.5\"\n",
130
+ "modelKwargs = {'device': 'cpu'}\n",
131
+ "encodeKwargs = {'normalize_embeddings': True}\n",
132
+ "embeddings = HuggingFaceEmbeddings(\n",
133
+ " model_name=modelName,\n",
134
+ " model_kwargs=modelKwargs,\n",
135
+ " encode_kwargs=encodeKwargs\n",
136
+ ")\n",
137
+ "\n",
138
+ "sparseEmbeddings = FastEmbedSparse(model_name=\"Qdrant/bm25\")"
139
+ ]
140
+ },
141
+ {
142
+ "cell_type": "code",
143
+ "execution_count": 6,
144
+ "metadata": {
145
+ "id": "UvuUKB3f2PUa"
146
+ },
147
+ "outputs": [],
148
+ "source": [
149
+ "ragTemplate = \"\"\"\n",
150
+ "You are an expert, fact-checking assistant. Your sole purpose is to answer the user's question.\n",
151
+ "\n",
152
+ "CONSTRAINTS:\n",
153
+ "1. **USE ONLY THE PROVIDED CONTEXT**. Do not use any prior knowledge or external information.\n",
154
+ "2. If the context does not contain the answer, you **MUST** respond with: \"I do not have enough information to answer the question based on the provided documents.\" DO NOT make up an answer.\n",
155
+ "3. Your answer must be **complete**, **accurate**, and directly address the user's question.\n",
156
+ "4. For every statement you make, cite the source document. For this exercise, assume the entire context can be from multiple sources, and please state **(Source: <content source from metadata of retrieved documents>)** at the end of the sentence or paragraph, but not the page number.\n",
157
+ "\n",
158
+ "CONTEXT:\n",
159
+ "{context}\n",
160
+ "\n",
161
+ "---\n",
162
+ "\n",
163
+ "QUESTION: {query}\n",
164
+ "\n",
165
+ "---\n",
166
+ "\n",
167
+ "FINAL ANSWER:\n",
168
+ "\"\"\""
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": null,
174
+ "metadata": {
175
+ "id": "jKnA_vkR0jgk"
176
+ },
177
+ "outputs": [],
178
+ "source": [
179
+ "from qdrant_client.http.models import Distance, VectorParams\n",
180
+ "from langchain_core.output_parsers import StrOutputParser\n",
181
+ "from langchain_core.runnables import RunnablePassthrough\n",
182
+ "from langchain_core.prompts import ChatPromptTemplate\n",
183
+ "from langchain_qdrant import QdrantVectorStore\n",
184
+ "from qdrant_client import QdrantClient\n",
185
+ "\n",
186
+ "class RAGAgent:\n",
187
+ " def __init__(self) -> None:\n",
188
+ " client = QdrantClient(\n",
189
+ " url=os.environ.get(\"QDRANT_URL\"),\n",
190
+ " api_key=os.environ.get(\"QDRANT_API_KEY\"),\n",
191
+ " )\n",
192
+ " vectorStore = QdrantVectorStore(\n",
193
+ " client=client,\n",
194
+ " collection_name=\"sampleCollection\",\n",
195
+ " embedding=embeddings,\n",
196
+ " vector_name=\"semantic-search\",\n",
197
+ " sparse_vector_name=\"syntactic-search\",\n",
198
+ " retrieval_mode=RetrievalMode.SPARSE,\n",
199
+ " sparse_embedding=sparseEmbeddings\n",
200
+ " )\n",
201
+ " promptTemplate = ChatPromptTemplate.from_template(ragTemplate)\n",
202
+ " retriever = vectorStore.as_retriever(search_kwargs = {\"k\": 5})\n",
203
+ " llm = ChatCerebras(\n",
204
+ " model = \"llama-3.3-70b\"\n",
205
+ " )\n",
206
+ " chain = {\"query\": RunnablePassthrough(), \"context\": RunnablePassthrough() | retriever} | promptTemplate | llm | StrOutputParser()\n",
207
+ " self.chain = chain\n",
208
+ "\n",
209
+ " def query(self, query) -> str:\n",
210
+ " output = self.chain.invoke(query)\n",
211
+ " return output"
212
+ ]
213
+ },
214
+ {
215
+ "cell_type": "code",
216
+ "execution_count": 8,
217
+ "metadata": {
218
+ "id": "9L69H_N_3c5k"
219
+ },
220
+ "outputs": [],
221
+ "source": [
222
+ "r = RAGAgent()"
223
+ ]
224
+ },
225
+ {
226
+ "cell_type": "code",
227
+ "execution_count": 9,
228
+ "metadata": {
229
+ "id": "rPb4TZAg3eKt"
230
+ },
231
+ "outputs": [],
232
+ "source": [
233
+ "response = r.query(\"What is OOPM?\")"
234
+ ]
235
+ },
236
+ {
237
+ "cell_type": "code",
238
+ "execution_count": 10,
239
+ "metadata": {
240
+ "colab": {
241
+ "base_uri": "https://localhost:8080/"
242
+ },
243
+ "id": "j5uvqtgb3hPW",
244
+ "outputId": "d1bbf872-9eb1-40f4-e405-309750c6fa3c"
245
+ },
246
+ "outputs": [
247
+ {
248
+ "name": "stdout",
249
+ "output_type": "stream",
250
+ "text": [
251
+ "I do not have enough information to answer the question based on the provided documents. (Source: None)\n"
252
+ ]
253
+ }
254
+ ],
255
+ "source": [
256
+ "print(response)"
257
+ ]
258
+ },
259
+ {
260
+ "cell_type": "markdown",
261
+ "metadata": {
262
+ "id": "SLwUbRA_4epj"
263
+ },
264
+ "source": [
265
+ "## Internet Search"
266
+ ]
267
+ },
268
+ {
269
+ "cell_type": "code",
270
+ "execution_count": null,
271
+ "metadata": {
272
+ "id": "5-hqVXaMV-TS"
273
+ },
274
+ "outputs": [],
275
+ "source": [
276
+ "from langchain_community.utilities import GoogleSerperAPIWrapper\n",
277
+ "\n",
278
+ "class InternetSearchAgent:\n",
279
+ " def __init__(self) -> None:\n",
280
+ " self.search = GoogleSerperAPIWrapper(serper_api_key=os.environ.get(\"SERPER_API_KEY\"))\n",
281
+ "\n",
282
+ " def query(self, query) -> str:\n",
283
+ " output = self.search.run(query)\n",
284
+ " return output"
285
+ ]
286
+ },
287
+ {
288
+ "cell_type": "markdown",
289
+ "metadata": {
290
+ "id": "c9ll1QRXbEUF"
291
+ },
292
+ "source": [
293
+ "## Reasoning Agent"
294
+ ]
295
+ },
296
+ {
297
+ "cell_type": "code",
298
+ "execution_count": 12,
299
+ "metadata": {
300
+ "id": "8dLn7yvdbGfy"
301
+ },
302
+ "outputs": [],
303
+ "source": [
304
+ "reasoningTemplate = \"\"\"\n",
305
+ "You are the General Knowledge and Reasoning Agent. Your function is to provide clear, concise, and expert explanations of widely established academic concepts, definitions, and common-sense knowledge. You must rely exclusively on your core training data and internal knowledge.\n",
306
+ "\n",
307
+ "MANDATE:\n",
308
+ "\n",
309
+ "Answer factual, common knowledge, definition, or theoretical questions.\n",
310
+ "\n",
311
+ "CRITICAL REASONING STEP (CoT): For all problem-solving, analytical, or multi-part questions, you MUST perform the following internal steps:\n",
312
+ "a. Analyze: Carefully interpret the overall user problem and its objectives.\n",
313
+ "b. Breakdown: Decompose the problem into sequential, logical sub-parts.\n",
314
+ "c. Solve: Execute the necessary reasoning steps for each sub-part rigorously.\n",
315
+ "d. Synthesize: Combine the results into a single, cohesive, final answer.\n",
316
+ "\n",
317
+ "Provide structured, high-quality explanations, examples, and summaries.\n",
318
+ "\n",
319
+ "CONSTRAINTS (CRITICAL ANTI-HALLUCINATION RULES):\n",
320
+ "\n",
321
+ "DO NOT answer any question that requires accessing external, private, or real-time data. This includes but is not limited to: recent news, local weather, file content (RAG), specific company metrics (SQL), or any data more recent than your last training cutoff.\n",
322
+ "\n",
323
+ "IMMEDIATELY and EXCLUSIVELY respond with the exact phrase: \"Knowledge gap detected. This query requires external data access (RAG, SQL, or Web).\" if the answer is not already in your internal knowledge base.\n",
324
+ "\n",
325
+ "Ensure your response is clear, professional, and directly addresses the query.\n",
326
+ "\n",
327
+ "QUESTION: {query}\n",
328
+ "\"\"\""
329
+ ]
330
+ },
331
+ {
332
+ "cell_type": "code",
333
+ "execution_count": null,
334
+ "metadata": {
335
+ "id": "vYhJVALTbGWX"
336
+ },
337
+ "outputs": [],
338
+ "source": [
339
+ "class ReasoningAgent:\n",
340
+ " def __init__(self) -> None:\n",
341
+ " promptTemplate = ChatPromptTemplate.from_template(reasoningTemplate)\n",
342
+ " llm = ChatCerebras(\n",
343
+ " model = \"qwen-3-32b\"\n",
344
+ " )\n",
345
+ " chain = {\"query\": RunnablePassthrough()} | promptTemplate | llm | StrOutputParser()\n",
346
+ " self.chain = chain\n",
347
+ "\n",
348
+ " def query(self, query) -> str:\n",
349
+ " output = self.chain.invoke(query)\n",
350
+ " return output"
351
+ ]
352
+ },
353
+ {
354
+ "cell_type": "code",
355
+ "execution_count": 14,
356
+ "metadata": {
357
+ "id": "ToygvML3cHBl"
358
+ },
359
+ "outputs": [],
360
+ "source": [
361
+ "r = ReasoningAgent()"
362
+ ]
363
+ },
364
+ {
365
+ "cell_type": "code",
366
+ "execution_count": 15,
367
+ "metadata": {
368
+ "id": "smNTW70tcJDK"
369
+ },
370
+ "outputs": [],
371
+ "source": [
372
+ "output = r.query(\"What is 34957 * 4035?\")"
373
+ ]
374
+ },
375
+ {
376
+ "cell_type": "code",
377
+ "execution_count": 16,
378
+ "metadata": {
379
+ "colab": {
380
+ "base_uri": "https://localhost:8080/",
381
+ "height": 120
382
+ },
383
+ "id": "ChIOqnlMcpiI",
384
+ "outputId": "130a9cc6-6cfa-4015-d80a-64b4e2dd0dc7"
385
+ },
386
+ "outputs": [
387
+ {
388
+ "data": {
389
+ "application/vnd.google.colaboratory.intrinsic+json": {
390
+ "type": "string"
391
+ },
392
+ "text/plain": [
393
+ "\"<think>\\nOkay, let's see. The user is asking for the product of 34,957 and 4,035. Hmm, I need to calculate this multiplication. First, I remember that multiplying large numbers can be broken down into smaller parts using the standard multiplication method.\\n\\nLet me start by breaking down 4,035 into 4,000 + 35. That might make it easier. So, 34,957 multiplied by 4,000 and then 34,957 multiplied by 35, and then add those two results together.\\n\\nWait, multiplying by 4,000 is the same as multiplying by 4 and then by 1,000. Let me do 34,957 * 4 first. 34,957 times 4: 30,000 * 4 is 120,000. 4,000 * 4 is 16,000. 900 * 4 is 3,600. 50 * 4 is 200. 7 * 4 is 28. Adding those together: 120,000 + 16,000 = 136,000. 136,000 + 3,600 = 139,600. Plus 200 is 139,800. Plus 28 is 139,828. Then I need to multiply that by 1,000 (since 4,000 is 4 * 1,000), so that's 139,828,000.\\n\\nNow, 34,957 * 35. Let's break that down. 35 is 30 + 5. So first, 34,957 * 30. 34,957 * 10 is 349,570, so times 3 is 1,048,710. Then 34,957 * 5 is 174,785. Adding those two together: 1,048,710 + 174,785 = 1,223,495.\\n\\nNow, add the two results: 139,828,000 + 1,223,495 = 141,051,495. Let me double-check these steps to make sure I didn't make a mistake in any of the multiplications or additions.\\n\\nWait, let me verify 34,957 * 4 again. 34,957 * 4: 30,000 * 4 = 120,000. 4,000 * 4 = 16,000. 900 * 4 = 3,600. 50 * 4 = 200. 7 * 4 = 28. Adding all these steps: 120,000 + 16,000 is 136,000. Then 136,000 + 3,600 is 139,600. Then 139,600 + 200 = 139,800. Then 139,800 + 28 = 139,828. So 139,828 * 1,000 is indeed 139,828,000.\\n\\nFor 34,957 * 30: 34,957 * 10 = 349,570. Times 3 is 1,048,710. Then 34,957 * 5: 30,000 *5 = 150,000. 4,000 *5=20,000. 900 *5=4,500. 50 *5=250. 7*5=35. Adding those: 150,000 +20,000=170,000. +4,500=174,500. +250=174,750. +35=174,785. Then 1,048,710 + 174,785 = 1,223,495. Adding that to 139,828,000 gives 141,051,495. Let me check with a different method just to be sure. Maybe using another multiplication strategy, like expanding both numbers.\\n\\nAlternatively, 4,035 is 4.035 × 10³. So 34,957 × 4.035 × 10³. Let's compute 34,957 × 4.035 first. 34,957 × 4 = 139,828. 34,957 × 0.035 = ?\\n\\n0.035 is 0.03 + 0.005. So 34,957 × 0.03 = 1,048.71. 34,957 × 0.005 = 174.785. Adding them gives 1,048.71 + 174.785 = 1,223.495. Then adding to 139,828 gives 139,828 + 1,223.495 = 141,051.495. Then multiply by 1,000 (the 10³ factor) to get 141,051,495. That matches the earlier result. So I think the calculation is correct. Therefore, the product of 34,957 and 4,035 is 141,051,495.\\n</think>\\n\\nThe product of 34,957 multiplied by 4,035 is **141,051,495**. \\n\\n### Step-by-Step Explanation:\\n1. **Breakdown**: \\n Use the distributive property: \\n $ 34,957 \\\\times 4,035 = 34,957 \\\\times (4,000 + 35) $.\\n\\n2. **Compute $ 34,957 \\\\times 4,000 $**: \\n - $ 34,957 \\\\times 4 = 139,828 $. \\n - Append three zeros: $ 139,828 \\\\times 1,000 = 139,828,000 $.\\n\\n3. **Compute $ 34,957 \\\\times 35 $**: \\n - $ 34,957 \\\\times 30 = 1,048,710 $. \\n - $ 34,957 \\\\times 5 = 174,785 $. \\n - Add: $ 1,048,710 + 174,785 = 1,223,495 $.\\n\\n4. **Add partial results**: \\n $ 139,828,000 + 1,223,495 = 141,051,495 $.\\n\\nThis method ensures accuracy by decomposing the problem into manageable parts.\""
394
+ ]
395
+ },
396
+ "execution_count": 16,
397
+ "metadata": {},
398
+ "output_type": "execute_result"
399
+ }
400
+ ],
401
+ "source": [
402
+ "output"
403
+ ]
404
+ },
405
+ {
406
+ "cell_type": "markdown",
407
+ "metadata": {
408
+ "id": "kldzxsdcgEkl"
409
+ },
410
+ "source": [
411
+ "## Synthesizer Agent"
412
+ ]
413
+ },
414
+ {
415
+ "cell_type": "code",
416
+ "execution_count": 17,
417
+ "metadata": {
418
+ "id": "BHGaRr21ge2e"
419
+ },
420
+ "outputs": [],
421
+ "source": [
422
+ "synthesizerTemplate = \"\"\"\n",
423
+ "You are the Lead Synthesis Editor, a high-level intelligence responsible for compiling the final, definitive response to the user. Your core objective is to deliver a single, highly coherent, and contextually accurate answer without revealing the underlying system complexity.\n",
424
+ "\n",
425
+ "MANDATE & LOGICAL FRAMEWORK:\n",
426
+ "\n",
427
+ "Seamless Integration: Generate a continuous, narrative response. The user must never know that multiple sub-agents (RAG, SQL, Web) were used. Do not include labels like \"RAG says\" or \"SQL confirms.\"\n",
428
+ "\n",
429
+ "Contextual Hierarchy & Wisdom: When information is available from multiple sources, weigh the evidence and prioritize the most authoritative source for the specific query type:\n",
430
+ "\n",
431
+ "Internal Data (RAG, SQL): Use this for proprietary definitions, policy details, employee counts, or specific organizational data. If RAG/SQL directly contradicts General Knowledge (Reasoning), the internal data wins.\n",
432
+ "\n",
433
+ "Real-Time Data (Web): Use this for external, up-to-date facts (e.g., market trends, recent events, current stock prices). If Web contradicts a theoretical definition from Reasoning, the Web data wins only if the theory has changed recently.\n",
434
+ "\n",
435
+ "General Knowledge (Reasoning): Use this for foundational definitions, historical context, or conceptual explanations (e.g., what is OOPS, how does an algorithm work).\n",
436
+ "\n",
437
+ "Contradiction Guardrail: If an agent's output is absurd, irrelevant, or obviously contradictory to the question's focus (e.g., the Web Agent gives a cooking recipe for an SQL query), treat that output as null and completely exclude it from the final answer. Always favor logic and direct relevance.\n",
438
+ "\n",
439
+ "Citations: Every claim must be supported by a citation corresponding to the data source. Use parentheses at the end of the sentence or clause: (Source: RAG), (Source: SQL), (Source: Web), or (Source: Model Knowledge).\n",
440
+ "\n",
441
+ "SOURCE DATA INPUTS:\n",
442
+ "\n",
443
+ "Original Question: {query}\n",
444
+ "\n",
445
+ "RAG Context (Internal/Policy Data):\n",
446
+ "{ragOutput}\n",
447
+ "\n",
448
+ "SQL Results (Structured Metrics/Counts):\n",
449
+ "{sqlOutput}\n",
450
+ "\n",
451
+ "Web Search Snippets (Current/External Data):\n",
452
+ "{webOutput}\n",
453
+ "\n",
454
+ "Reasoning Agent Output (Conceptual/Theoretical):\n",
455
+ "{reasoningOutput}\n",
456
+ "\n",
457
+ "FINAL DEFINITIVE RESPONSE:\n",
458
+ "(Begin your response here, synthesizing information from the sources above into a single, cohesive narrative. Apply the logic and citation rules strictly.)\n",
459
+ "\"\"\""
460
+ ]
461
+ },
462
+ {
463
+ "cell_type": "code",
464
+ "execution_count": null,
465
+ "metadata": {
466
+ "id": "HIyspZIwgHQE"
467
+ },
468
+ "outputs": [],
469
+ "source": [
470
+ "class SynthesizerAgent:\n",
471
+ " def __init__(self) -> None:\n",
472
+ " promptTemplate = ChatPromptTemplate.from_template(synthesizerTemplate)\n",
473
+ " llm = ChatCerebras(\n",
474
+ " model = \"gpt-oss-120b\"\n",
475
+ " )\n",
476
+ " chain = RunnablePassthrough() | promptTemplate | llm | StrOutputParser()\n",
477
+ " self.chain = chain\n",
478
+ "\n",
479
+ " def query(self, query) -> str:\n",
480
+ " output = self.chain.invoke(query)\n",
481
+ " return output"
482
+ ]
483
+ },
484
+ {
485
+ "cell_type": "code",
486
+ "execution_count": 19,
487
+ "metadata": {
488
+ "id": "VzXoLQDtgHMF"
489
+ },
490
+ "outputs": [],
491
+ "source": [
492
+ "s = SynthesizerAgent()"
493
+ ]
494
+ },
495
+ {
496
+ "cell_type": "markdown",
497
+ "metadata": {
498
+ "id": "_CzP63eSYC5T"
499
+ },
500
+ "source": [
501
+ "## Workflow"
502
+ ]
503
+ },
504
+ {
505
+ "cell_type": "code",
506
+ "execution_count": 23,
507
+ "metadata": {
508
+ "id": "xxpfQQ4jXz_p"
509
+ },
510
+ "outputs": [],
511
+ "source": [
512
+ "from langgraph.graph import START, END, StateGraph\n",
513
+ "from typing import TypedDict\n",
514
+ "from IPython.display import Image, display\n",
515
+ "from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles"
516
+ ]
517
+ },
518
+ {
519
+ "cell_type": "code",
520
+ "execution_count": 24,
521
+ "metadata": {
522
+ "id": "bAGHVkiIYMzA"
523
+ },
524
+ "outputs": [],
525
+ "source": [
526
+ "class AgentState(TypedDict):\n",
527
+ " internetResults: str\n",
528
+ " reasoningResults: str\n",
529
+ " sqlResults: str\n",
530
+ " ragResults: str\n",
531
+ " query: str\n",
532
+ " finalAnswer: str"
533
+ ]
534
+ },
535
+ {
536
+ "cell_type": "code",
537
+ "execution_count": null,
538
+ "metadata": {
539
+ "id": "UI6u9O1MYl4l"
540
+ },
541
+ "outputs": [],
542
+ "source": [
543
+ "class Workflow:\n",
544
+ " def __init__(self) -> None:\n",
545
+ " self.internetSearchAgentObj = InternetSearchAgent()\n",
546
+ " self.reasoningAgentObj = ReasoningAgent()\n",
547
+ " self.ragAgentObj = RAGAgent()\n",
548
+ " self.sqlAgentObj = PostgreSQLAgent(os.environ.get(\"POSTGRE_CONNECTION_STRING\"))\n",
549
+ " self.synthesizerAgentObj = SynthesizerAgent()\n",
550
+ "\n",
551
+ " def _internetSearchAgent(self, state: AgentState) -> dict:\n",
552
+ " return {\"internetResults\": self.internetSearchAgentObj.query(query=state.get(\"query\"))}\n",
553
+ "\n",
554
+ " def _reasoningAgent(self, state: AgentState) -> dict:\n",
555
+ " return {\"reasoningResults\": self.reasoningAgentObj.query(query=state.get(\"query\"))}\n",
556
+ "\n",
557
+ " def _ragAgent(self, state: AgentState) -> dict:\n",
558
+ " return {\"ragResults\": self.ragAgentObj.query(query=state.get(\"query\"))}\n",
559
+ "\n",
560
+ " def _sqlAgent(self, state: AgentState) -> dict:\n",
561
+ " return {\"sqlResults\": self.sqlAgentObj.query(query=state.get(\"query\"))}\n",
562
+ "\n",
563
+ " def _synthesizerAgent(self, state: AgentState) -> dict:\n",
564
+ " return {\"finalAnswer\": self.synthesizerAgentObj.query({\"query\": state[\"query\"], \"reasoningOutput\": state[\"reasoningResults\"], \"webOutput\": state[\"internetResults\"], \"ragOutput\": state[\"ragResults\"], \"sqlOutput\": state[\"sqlResults\"]})}\n",
565
+ "\n",
566
+ " def createWorkflow(self) -> None:\n",
567
+ " graph = StateGraph(AgentState)\n",
568
+ " graph.add_node(\"internetSearchAgent\", self._internetSearchAgent, )\n",
569
+ " graph.add_node(\"reasoningAgent\", self._reasoningAgent,)\n",
570
+ " graph.add_node(\"ragAgent\", self._ragAgent)\n",
571
+ " graph.add_node(\"sqlAgent\", self._sqlAgent)\n",
572
+ " graph.add_node(\"synthesizerAgent\", self._synthesizerAgent, defer = True)\n",
573
+ " graph.add_edge(START, \"internetSearchAgent\")\n",
574
+ " graph.add_edge(START, \"reasoningAgent\")\n",
575
+ " graph.add_edge(START, \"ragAgent\")\n",
576
+ " graph.add_edge(START, \"sqlAgent\")\n",
577
+ " graph.add_edge(\"internetSearchAgent\", \"synthesizerAgent\")\n",
578
+ " graph.add_edge(\"reasoningAgent\", \"synthesizerAgent\")\n",
579
+ " graph.add_edge(\"ragAgent\", \"synthesizerAgent\")\n",
580
+ " graph.add_edge(\"sqlAgent\", \"synthesizerAgent\")\n",
581
+ " graph.add_edge(\"synthesizerAgent\", END)\n",
582
+ " self.graph = graph.compile()\n",
583
+ "\n",
584
+ " display(Image(self.graph.get_graph().draw_mermaid_png()))\n",
585
+ "\n",
586
+ " def run(self, query: str) -> str:\n",
587
+ " return self.graph.invoke({\"query\": query})[\"finalAnswer\"]"
588
+ ]
589
+ },
590
+ {
591
+ "cell_type": "code",
592
+ "execution_count": 26,
593
+ "metadata": {
594
+ "colab": {
595
+ "base_uri": "https://localhost:8080/",
596
+ "height": 392
597
+ },
598
+ "id": "1Mqc1StMliL4",
599
+ "outputId": "3fa023e9-731e-4765-8bb0-7b8fb3bb5ace"
600
+ },
601
+ "outputs": [
602
+ {
603
+ "data": {
604
+ "image/png": "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",
605
+ "text/plain": [
606
+ "<IPython.core.display.Image object>"
607
+ ]
608
+ },
609
+ "metadata": {},
610
+ "output_type": "display_data"
611
+ }
612
+ ],
613
+ "source": [
614
+ "wo = Workflow()\n",
615
+ "wo.createWorkflow()"
616
+ ]
617
+ },
618
+ {
619
+ "cell_type": "code",
620
+ "execution_count": null,
621
+ "metadata": {
622
+ "id": "SlTvKBARlnqp"
623
+ },
624
+ "outputs": [],
625
+ "source": [
626
+ "response = wo.run(\"According to the provided documents, what is the role of a data structure in software design and how does it relate to the physical form of data?\")"
627
+ ]
628
+ },
629
+ {
630
+ "cell_type": "code",
631
+ "execution_count": null,
632
+ "metadata": {
633
+ "id": "GR6Fy_SUnczt"
634
+ },
635
+ "outputs": [],
636
+ "source": [
637
+ "print(response)"
638
+ ]
639
+ },
640
+ {
641
+ "cell_type": "code",
642
+ "execution_count": null,
643
+ "metadata": {
644
+ "id": "huAMIlamoCJ6"
645
+ },
646
+ "outputs": [],
647
+ "source": [
648
+ "response = wo.run(\"Explain what a Database Management System (DBMS) is, and then find the highest recorded salary for a 'Principal Data Scientist'.\")"
649
+ ]
650
+ },
651
+ {
652
+ "cell_type": "code",
653
+ "execution_count": null,
654
+ "metadata": {
655
+ "id": "D0GYw_VDrSUx"
656
+ },
657
+ "outputs": [],
658
+ "source": [
659
+ "print(response)"
660
+ ]
661
+ },
662
+ {
663
+ "cell_type": "code",
664
+ "execution_count": null,
665
+ "metadata": {
666
+ "id": "uN7WRQ0krTmD"
667
+ },
668
+ "outputs": [],
669
+ "source": []
670
+ }
671
+ ],
672
+ "metadata": {
673
+ "colab": {
674
+ "provenance": []
675
+ },
676
+ "kernelspec": {
677
+ "display_name": "Python 3",
678
+ "name": "python3"
679
+ },
680
+ "language_info": {
681
+ "name": "python"
682
+ }
683
+ },
684
+ "nbformat": 4,
685
+ "nbformat_minor": 0
686
+ }
notebooks/SQLPoplulator.ipynb ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 20,
6
+ "metadata": {
7
+ "colab": {
8
+ "base_uri": "https://localhost:8080/"
9
+ },
10
+ "id": "lv2nI8oDZBwx",
11
+ "outputId": "a0783fd9-bab8-4f2d-a35c-2ec12ea39da4"
12
+ },
13
+ "outputs": [
14
+ {
15
+ "name": "stdout",
16
+ "output_type": "stream",
17
+ "text": [
18
+ "Requirement already satisfied: sqlalchemy in /usr/local/lib/python3.12/dist-packages (2.0.44)\n",
19
+ "Requirement already satisfied: psycopg2 in /usr/local/lib/python3.12/dist-packages (2.9.11)\n",
20
+ "Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)\n",
21
+ "Requirement already satisfied: greenlet>=1 in /usr/local/lib/python3.12/dist-packages (from sqlalchemy) (3.2.4)\n",
22
+ "Requirement already satisfied: typing-extensions>=4.6.0 in /usr/local/lib/python3.12/dist-packages (from sqlalchemy) (4.15.0)\n",
23
+ "Requirement already satisfied: numpy>=1.26.0 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.0.2)\n",
24
+ "Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)\n",
25
+ "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)\n",
26
+ "Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)\n",
27
+ "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n"
28
+ ]
29
+ }
30
+ ],
31
+ "source": [
32
+ "!pip install sqlalchemy psycopg2 pandas"
33
+ ]
34
+ },
35
+ {
36
+ "cell_type": "code",
37
+ "execution_count": 21,
38
+ "metadata": {
39
+ "colab": {
40
+ "base_uri": "https://localhost:8080/",
41
+ "height": 360
42
+ },
43
+ "id": "4jJ55ColUV5d",
44
+ "outputId": "df044c10-364c-4c77-ea53-f3d257f213db"
45
+ },
46
+ "outputs": [
47
+ {
48
+ "data": {
49
+ "application/vnd.google.colaboratory.intrinsic+json": {
50
+ "summary": "{\n \"name\": \"data\",\n \"rows\": 15000,\n \"fields\": [\n {\n \"column\": \"job_id\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 15000,\n \"samples\": [\n \"AI11500\",\n \"AI06476\",\n \"AI13168\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"job_title\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 20,\n \"samples\": [\n \"AI Research Scientist\",\n \"Data Scientist\",\n \"Head of AI\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"salary_usd\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 60260,\n \"min\": 32519,\n \"max\": 399095,\n \"num_unique_values\": 14315,\n \"samples\": [\n 121638,\n 125960,\n 47012\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"salary_currency\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"USD\",\n \"EUR\",\n \"GBP\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"experience_level\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"EN\",\n \"EX\",\n \"SE\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"employment_type\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"FL\",\n \"FT\",\n \"CT\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"company_location\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 20,\n \"samples\": [\n \"China\",\n \"Denmark\",\n \"Australia\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"company_size\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"M\",\n \"L\",\n \"S\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"employee_residence\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 20,\n \"samples\": [\n \"China\",\n \"Japan\",\n \"Switzerland\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"remote_ratio\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 40,\n \"min\": 0,\n \"max\": 100,\n \"num_unique_values\": 3,\n \"samples\": [\n 50,\n 100,\n 0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"required_skills\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 13663,\n \"samples\": [\n \"Spark, Python, Hadoop\",\n \"PyTorch, Kubernetes, Computer Vision, Docker\",\n \"Git, Hadoop, Java, PyTorch\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"education_required\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"Master\",\n \"PhD\",\n \"Bachelor\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"years_experience\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5,\n \"min\": 0,\n \"max\": 19,\n \"num_unique_values\": 20,\n \"samples\": [\n 9,\n 13,\n 19\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"industry\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 15,\n \"samples\": [\n \"Energy\",\n \"Real Estate\",\n \"Automotive\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"posting_date\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 486,\n \"samples\": [\n \"2024-12-01\",\n \"2024-08-15\",\n \"2024-04-16\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"application_deadline\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 543,\n \"samples\": [\n \"2025-07-06\",\n \"2025-07-01\",\n \"2024-03-18\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"job_description_length\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 576,\n \"min\": 500,\n \"max\": 2499,\n \"num_unique_values\": 2000,\n \"samples\": [\n 1520,\n 571,\n 1113\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"benefits_score\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 1.4508696496026827,\n \"min\": 5.0,\n \"max\": 10.0,\n \"num_unique_values\": 51,\n \"samples\": [\n 8.4,\n 9.5,\n 8.3\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"company_name\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 16,\n \"samples\": [\n \"Smart Analytics\",\n \"TechCorp Inc\",\n \"Neural Networks Co\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
51
+ "type": "dataframe",
52
+ "variable_name": "data"
53
+ },
54
+ "text/html": [
55
+ "\n",
56
+ " <div id=\"df-65321346-6d61-4cd5-b8f0-14ad6f32170a\" class=\"colab-df-container\">\n",
57
+ " <div>\n",
58
+ "<style scoped>\n",
59
+ " .dataframe tbody tr th:only-of-type {\n",
60
+ " vertical-align: middle;\n",
61
+ " }\n",
62
+ "\n",
63
+ " .dataframe tbody tr th {\n",
64
+ " vertical-align: top;\n",
65
+ " }\n",
66
+ "\n",
67
+ " .dataframe thead th {\n",
68
+ " text-align: right;\n",
69
+ " }\n",
70
+ "</style>\n",
71
+ "<table border=\"1\" class=\"dataframe\">\n",
72
+ " <thead>\n",
73
+ " <tr style=\"text-align: right;\">\n",
74
+ " <th></th>\n",
75
+ " <th>job_id</th>\n",
76
+ " <th>job_title</th>\n",
77
+ " <th>salary_usd</th>\n",
78
+ " <th>salary_currency</th>\n",
79
+ " <th>experience_level</th>\n",
80
+ " <th>employment_type</th>\n",
81
+ " <th>company_location</th>\n",
82
+ " <th>company_size</th>\n",
83
+ " <th>employee_residence</th>\n",
84
+ " <th>remote_ratio</th>\n",
85
+ " <th>required_skills</th>\n",
86
+ " <th>education_required</th>\n",
87
+ " <th>years_experience</th>\n",
88
+ " <th>industry</th>\n",
89
+ " <th>posting_date</th>\n",
90
+ " <th>application_deadline</th>\n",
91
+ " <th>job_description_length</th>\n",
92
+ " <th>benefits_score</th>\n",
93
+ " <th>company_name</th>\n",
94
+ " </tr>\n",
95
+ " </thead>\n",
96
+ " <tbody>\n",
97
+ " <tr>\n",
98
+ " <th>0</th>\n",
99
+ " <td>AI00001</td>\n",
100
+ " <td>AI Research Scientist</td>\n",
101
+ " <td>90376</td>\n",
102
+ " <td>USD</td>\n",
103
+ " <td>SE</td>\n",
104
+ " <td>CT</td>\n",
105
+ " <td>China</td>\n",
106
+ " <td>M</td>\n",
107
+ " <td>China</td>\n",
108
+ " <td>50</td>\n",
109
+ " <td>Tableau, PyTorch, Kubernetes, Linux, NLP</td>\n",
110
+ " <td>Bachelor</td>\n",
111
+ " <td>9</td>\n",
112
+ " <td>Automotive</td>\n",
113
+ " <td>2024-10-18</td>\n",
114
+ " <td>2024-11-07</td>\n",
115
+ " <td>1076</td>\n",
116
+ " <td>5.9</td>\n",
117
+ " <td>Smart Analytics</td>\n",
118
+ " </tr>\n",
119
+ " <tr>\n",
120
+ " <th>1</th>\n",
121
+ " <td>AI00002</td>\n",
122
+ " <td>AI Software Engineer</td>\n",
123
+ " <td>61895</td>\n",
124
+ " <td>USD</td>\n",
125
+ " <td>EN</td>\n",
126
+ " <td>CT</td>\n",
127
+ " <td>Canada</td>\n",
128
+ " <td>M</td>\n",
129
+ " <td>Ireland</td>\n",
130
+ " <td>100</td>\n",
131
+ " <td>Deep Learning, AWS, Mathematics, Python, Docker</td>\n",
132
+ " <td>Master</td>\n",
133
+ " <td>1</td>\n",
134
+ " <td>Media</td>\n",
135
+ " <td>2024-11-20</td>\n",
136
+ " <td>2025-01-11</td>\n",
137
+ " <td>1268</td>\n",
138
+ " <td>5.2</td>\n",
139
+ " <td>TechCorp Inc</td>\n",
140
+ " </tr>\n",
141
+ " <tr>\n",
142
+ " <th>2</th>\n",
143
+ " <td>AI00003</td>\n",
144
+ " <td>AI Specialist</td>\n",
145
+ " <td>152626</td>\n",
146
+ " <td>USD</td>\n",
147
+ " <td>MI</td>\n",
148
+ " <td>FL</td>\n",
149
+ " <td>Switzerland</td>\n",
150
+ " <td>L</td>\n",
151
+ " <td>South Korea</td>\n",
152
+ " <td>0</td>\n",
153
+ " <td>Kubernetes, Deep Learning, Java, Hadoop, NLP</td>\n",
154
+ " <td>Associate</td>\n",
155
+ " <td>2</td>\n",
156
+ " <td>Education</td>\n",
157
+ " <td>2025-03-18</td>\n",
158
+ " <td>2025-04-07</td>\n",
159
+ " <td>1974</td>\n",
160
+ " <td>9.4</td>\n",
161
+ " <td>Autonomous Tech</td>\n",
162
+ " </tr>\n",
163
+ " <tr>\n",
164
+ " <th>3</th>\n",
165
+ " <td>AI00004</td>\n",
166
+ " <td>NLP Engineer</td>\n",
167
+ " <td>80215</td>\n",
168
+ " <td>USD</td>\n",
169
+ " <td>SE</td>\n",
170
+ " <td>FL</td>\n",
171
+ " <td>India</td>\n",
172
+ " <td>M</td>\n",
173
+ " <td>India</td>\n",
174
+ " <td>50</td>\n",
175
+ " <td>Scala, SQL, Linux, Python</td>\n",
176
+ " <td>PhD</td>\n",
177
+ " <td>7</td>\n",
178
+ " <td>Consulting</td>\n",
179
+ " <td>2024-12-23</td>\n",
180
+ " <td>2025-02-24</td>\n",
181
+ " <td>1345</td>\n",
182
+ " <td>8.6</td>\n",
183
+ " <td>Future Systems</td>\n",
184
+ " </tr>\n",
185
+ " <tr>\n",
186
+ " <th>4</th>\n",
187
+ " <td>AI00005</td>\n",
188
+ " <td>AI Consultant</td>\n",
189
+ " <td>54624</td>\n",
190
+ " <td>EUR</td>\n",
191
+ " <td>EN</td>\n",
192
+ " <td>PT</td>\n",
193
+ " <td>France</td>\n",
194
+ " <td>S</td>\n",
195
+ " <td>Singapore</td>\n",
196
+ " <td>100</td>\n",
197
+ " <td>MLOps, Java, Tableau, Python</td>\n",
198
+ " <td>Master</td>\n",
199
+ " <td>0</td>\n",
200
+ " <td>Media</td>\n",
201
+ " <td>2025-04-15</td>\n",
202
+ " <td>2025-06-23</td>\n",
203
+ " <td>1989</td>\n",
204
+ " <td>6.6</td>\n",
205
+ " <td>Advanced Robotics</td>\n",
206
+ " </tr>\n",
207
+ " </tbody>\n",
208
+ "</table>\n",
209
+ "</div>\n",
210
+ " <div class=\"colab-df-buttons\">\n",
211
+ "\n",
212
+ " <div class=\"colab-df-container\">\n",
213
+ " <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-65321346-6d61-4cd5-b8f0-14ad6f32170a')\"\n",
214
+ " title=\"Convert this dataframe to an interactive table.\"\n",
215
+ " style=\"display:none;\">\n",
216
+ "\n",
217
+ " <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
218
+ " <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
219
+ " </svg>\n",
220
+ " </button>\n",
221
+ "\n",
222
+ " <style>\n",
223
+ " .colab-df-container {\n",
224
+ " display:flex;\n",
225
+ " gap: 12px;\n",
226
+ " }\n",
227
+ "\n",
228
+ " .colab-df-convert {\n",
229
+ " background-color: #E8F0FE;\n",
230
+ " border: none;\n",
231
+ " border-radius: 50%;\n",
232
+ " cursor: pointer;\n",
233
+ " display: none;\n",
234
+ " fill: #1967D2;\n",
235
+ " height: 32px;\n",
236
+ " padding: 0 0 0 0;\n",
237
+ " width: 32px;\n",
238
+ " }\n",
239
+ "\n",
240
+ " .colab-df-convert:hover {\n",
241
+ " background-color: #E2EBFA;\n",
242
+ " box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
243
+ " fill: #174EA6;\n",
244
+ " }\n",
245
+ "\n",
246
+ " .colab-df-buttons div {\n",
247
+ " margin-bottom: 4px;\n",
248
+ " }\n",
249
+ "\n",
250
+ " [theme=dark] .colab-df-convert {\n",
251
+ " background-color: #3B4455;\n",
252
+ " fill: #D2E3FC;\n",
253
+ " }\n",
254
+ "\n",
255
+ " [theme=dark] .colab-df-convert:hover {\n",
256
+ " background-color: #434B5C;\n",
257
+ " box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
258
+ " filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
259
+ " fill: #FFFFFF;\n",
260
+ " }\n",
261
+ " </style>\n",
262
+ "\n",
263
+ " <script>\n",
264
+ " const buttonEl =\n",
265
+ " document.querySelector('#df-65321346-6d61-4cd5-b8f0-14ad6f32170a button.colab-df-convert');\n",
266
+ " buttonEl.style.display =\n",
267
+ " google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
268
+ "\n",
269
+ " async function convertToInteractive(key) {\n",
270
+ " const element = document.querySelector('#df-65321346-6d61-4cd5-b8f0-14ad6f32170a');\n",
271
+ " const dataTable =\n",
272
+ " await google.colab.kernel.invokeFunction('convertToInteractive',\n",
273
+ " [key], {});\n",
274
+ " if (!dataTable) return;\n",
275
+ "\n",
276
+ " const docLinkHtml = 'Like what you see? Visit the ' +\n",
277
+ " '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
278
+ " + ' to learn more about interactive tables.';\n",
279
+ " element.innerHTML = '';\n",
280
+ " dataTable['output_type'] = 'display_data';\n",
281
+ " await google.colab.output.renderOutput(dataTable, element);\n",
282
+ " const docLink = document.createElement('div');\n",
283
+ " docLink.innerHTML = docLinkHtml;\n",
284
+ " element.appendChild(docLink);\n",
285
+ " }\n",
286
+ " </script>\n",
287
+ " </div>\n",
288
+ "\n",
289
+ "\n",
290
+ " <div id=\"df-00c3ed58-78da-47d0-a670-c989824d3602\">\n",
291
+ " <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-00c3ed58-78da-47d0-a670-c989824d3602')\"\n",
292
+ " title=\"Suggest charts\"\n",
293
+ " style=\"display:none;\">\n",
294
+ "\n",
295
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
296
+ " width=\"24px\">\n",
297
+ " <g>\n",
298
+ " <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
299
+ " </g>\n",
300
+ "</svg>\n",
301
+ " </button>\n",
302
+ "\n",
303
+ "<style>\n",
304
+ " .colab-df-quickchart {\n",
305
+ " --bg-color: #E8F0FE;\n",
306
+ " --fill-color: #1967D2;\n",
307
+ " --hover-bg-color: #E2EBFA;\n",
308
+ " --hover-fill-color: #174EA6;\n",
309
+ " --disabled-fill-color: #AAA;\n",
310
+ " --disabled-bg-color: #DDD;\n",
311
+ " }\n",
312
+ "\n",
313
+ " [theme=dark] .colab-df-quickchart {\n",
314
+ " --bg-color: #3B4455;\n",
315
+ " --fill-color: #D2E3FC;\n",
316
+ " --hover-bg-color: #434B5C;\n",
317
+ " --hover-fill-color: #FFFFFF;\n",
318
+ " --disabled-bg-color: #3B4455;\n",
319
+ " --disabled-fill-color: #666;\n",
320
+ " }\n",
321
+ "\n",
322
+ " .colab-df-quickchart {\n",
323
+ " background-color: var(--bg-color);\n",
324
+ " border: none;\n",
325
+ " border-radius: 50%;\n",
326
+ " cursor: pointer;\n",
327
+ " display: none;\n",
328
+ " fill: var(--fill-color);\n",
329
+ " height: 32px;\n",
330
+ " padding: 0;\n",
331
+ " width: 32px;\n",
332
+ " }\n",
333
+ "\n",
334
+ " .colab-df-quickchart:hover {\n",
335
+ " background-color: var(--hover-bg-color);\n",
336
+ " box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
337
+ " fill: var(--button-hover-fill-color);\n",
338
+ " }\n",
339
+ "\n",
340
+ " .colab-df-quickchart-complete:disabled,\n",
341
+ " .colab-df-quickchart-complete:disabled:hover {\n",
342
+ " background-color: var(--disabled-bg-color);\n",
343
+ " fill: var(--disabled-fill-color);\n",
344
+ " box-shadow: none;\n",
345
+ " }\n",
346
+ "\n",
347
+ " .colab-df-spinner {\n",
348
+ " border: 2px solid var(--fill-color);\n",
349
+ " border-color: transparent;\n",
350
+ " border-bottom-color: var(--fill-color);\n",
351
+ " animation:\n",
352
+ " spin 1s steps(1) infinite;\n",
353
+ " }\n",
354
+ "\n",
355
+ " @keyframes spin {\n",
356
+ " 0% {\n",
357
+ " border-color: transparent;\n",
358
+ " border-bottom-color: var(--fill-color);\n",
359
+ " border-left-color: var(--fill-color);\n",
360
+ " }\n",
361
+ " 20% {\n",
362
+ " border-color: transparent;\n",
363
+ " border-left-color: var(--fill-color);\n",
364
+ " border-top-color: var(--fill-color);\n",
365
+ " }\n",
366
+ " 30% {\n",
367
+ " border-color: transparent;\n",
368
+ " border-left-color: var(--fill-color);\n",
369
+ " border-top-color: var(--fill-color);\n",
370
+ " border-right-color: var(--fill-color);\n",
371
+ " }\n",
372
+ " 40% {\n",
373
+ " border-color: transparent;\n",
374
+ " border-right-color: var(--fill-color);\n",
375
+ " border-top-color: var(--fill-color);\n",
376
+ " }\n",
377
+ " 60% {\n",
378
+ " border-color: transparent;\n",
379
+ " border-right-color: var(--fill-color);\n",
380
+ " }\n",
381
+ " 80% {\n",
382
+ " border-color: transparent;\n",
383
+ " border-right-color: var(--fill-color);\n",
384
+ " border-bottom-color: var(--fill-color);\n",
385
+ " }\n",
386
+ " 90% {\n",
387
+ " border-color: transparent;\n",
388
+ " border-bottom-color: var(--fill-color);\n",
389
+ " }\n",
390
+ " }\n",
391
+ "</style>\n",
392
+ "\n",
393
+ " <script>\n",
394
+ " async function quickchart(key) {\n",
395
+ " const quickchartButtonEl =\n",
396
+ " document.querySelector('#' + key + ' button');\n",
397
+ " quickchartButtonEl.disabled = true; // To prevent multiple clicks.\n",
398
+ " quickchartButtonEl.classList.add('colab-df-spinner');\n",
399
+ " try {\n",
400
+ " const charts = await google.colab.kernel.invokeFunction(\n",
401
+ " 'suggestCharts', [key], {});\n",
402
+ " } catch (error) {\n",
403
+ " console.error('Error during call to suggestCharts:', error);\n",
404
+ " }\n",
405
+ " quickchartButtonEl.classList.remove('colab-df-spinner');\n",
406
+ " quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
407
+ " }\n",
408
+ " (() => {\n",
409
+ " let quickchartButtonEl =\n",
410
+ " document.querySelector('#df-00c3ed58-78da-47d0-a670-c989824d3602 button');\n",
411
+ " quickchartButtonEl.style.display =\n",
412
+ " google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
413
+ " })();\n",
414
+ " </script>\n",
415
+ " </div>\n",
416
+ "\n",
417
+ " </div>\n",
418
+ " </div>\n"
419
+ ],
420
+ "text/plain": [
421
+ " job_id job_title salary_usd salary_currency \\\n",
422
+ "0 AI00001 AI Research Scientist 90376 USD \n",
423
+ "1 AI00002 AI Software Engineer 61895 USD \n",
424
+ "2 AI00003 AI Specialist 152626 USD \n",
425
+ "3 AI00004 NLP Engineer 80215 USD \n",
426
+ "4 AI00005 AI Consultant 54624 EUR \n",
427
+ "\n",
428
+ " experience_level employment_type company_location company_size \\\n",
429
+ "0 SE CT China M \n",
430
+ "1 EN CT Canada M \n",
431
+ "2 MI FL Switzerland L \n",
432
+ "3 SE FL India M \n",
433
+ "4 EN PT France S \n",
434
+ "\n",
435
+ " employee_residence remote_ratio \\\n",
436
+ "0 China 50 \n",
437
+ "1 Ireland 100 \n",
438
+ "2 South Korea 0 \n",
439
+ "3 India 50 \n",
440
+ "4 Singapore 100 \n",
441
+ "\n",
442
+ " required_skills education_required \\\n",
443
+ "0 Tableau, PyTorch, Kubernetes, Linux, NLP Bachelor \n",
444
+ "1 Deep Learning, AWS, Mathematics, Python, Docker Master \n",
445
+ "2 Kubernetes, Deep Learning, Java, Hadoop, NLP Associate \n",
446
+ "3 Scala, SQL, Linux, Python PhD \n",
447
+ "4 MLOps, Java, Tableau, Python Master \n",
448
+ "\n",
449
+ " years_experience industry posting_date application_deadline \\\n",
450
+ "0 9 Automotive 2024-10-18 2024-11-07 \n",
451
+ "1 1 Media 2024-11-20 2025-01-11 \n",
452
+ "2 2 Education 2025-03-18 2025-04-07 \n",
453
+ "3 7 Consulting 2024-12-23 2025-02-24 \n",
454
+ "4 0 Media 2025-04-15 2025-06-23 \n",
455
+ "\n",
456
+ " job_description_length benefits_score company_name \n",
457
+ "0 1076 5.9 Smart Analytics \n",
458
+ "1 1268 5.2 TechCorp Inc \n",
459
+ "2 1974 9.4 Autonomous Tech \n",
460
+ "3 1345 8.6 Future Systems \n",
461
+ "4 1989 6.6 Advanced Robotics "
462
+ ]
463
+ },
464
+ "execution_count": 21,
465
+ "metadata": {},
466
+ "output_type": "execute_result"
467
+ }
468
+ ],
469
+ "source": [
470
+ "import pandas as pd\n",
471
+ "data = pd.read_csv(\"/content/ai_job_dataset.csv\")\n",
472
+ "data.head()"
473
+ ]
474
+ },
475
+ {
476
+ "cell_type": "code",
477
+ "execution_count": null,
478
+ "metadata": {
479
+ "id": "EGq5wykmYr37"
480
+ },
481
+ "outputs": [],
482
+ "source": [
483
+ "from sqlalchemy import create_engine\n",
484
+ "import os\n",
485
+ "engine = create_engine(\n",
486
+ " os.environ.get(\"POSTGRE_CONNECTION_STRING\")\n",
487
+ ")"
488
+ ]
489
+ },
490
+ {
491
+ "cell_type": "code",
492
+ "execution_count": 23,
493
+ "metadata": {
494
+ "colab": {
495
+ "base_uri": "https://localhost:8080/"
496
+ },
497
+ "id": "_UpDDKFbYv8q",
498
+ "outputId": "e3f3f43e-1f77-4a24-b1a8-7db41ae75d76"
499
+ },
500
+ "outputs": [
501
+ {
502
+ "data": {
503
+ "text/plain": [
504
+ "1000"
505
+ ]
506
+ },
507
+ "execution_count": 23,
508
+ "metadata": {},
509
+ "output_type": "execute_result"
510
+ }
511
+ ],
512
+ "source": [
513
+ "data.to_sql(\"sampleTable\", con = engine)"
514
+ ]
515
+ }
516
+ ],
517
+ "metadata": {
518
+ "colab": {
519
+ "provenance": []
520
+ },
521
+ "kernelspec": {
522
+ "display_name": "Python 3",
523
+ "name": "python3"
524
+ },
525
+ "language_info": {
526
+ "name": "python"
527
+ }
528
+ },
529
+ "nbformat": 4,
530
+ "nbformat_minor": 0
531
+ }
notebooks/VectorDBPopulator.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
prompts.yaml ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ragTemplate: |
2
+ You are an expert, fact-checking assistant. Your sole purpose is to answer the user's question.
3
+
4
+ CONSTRAINTS:
5
+ 1. **USE ONLY THE PROVIDED CONTEXT**. Do not use any prior knowledge or external information.
6
+ 2. If the context does not contain the answer, you **MUST** respond with: "I do not have enough information to answer the question based on the provided documents." DO NOT make up an answer.
7
+ 3. Your answer must be **complete**, **accurate**, and directly address the user's question.
8
+ 4. For every statement you make, cite the source document. For this exercise, assume the entire context can be from multiple sources, and please state **(Source: <content source from metadata of retrieved documents>)** at the end of the sentence or paragraph, but not the page number.
9
+
10
+ CONTEXT:
11
+ {context}
12
+
13
+ ---
14
+
15
+ QUESTION: {query}
16
+
17
+
18
+ reasoningTemplate: |
19
+ You are the General Knowledge and Reasoning Agent. Your function is to provide clear, concise, and expert explanations of widely established academic concepts, definitions, and common-sense knowledge. You must rely exclusively on your core training data and internal knowledge.
20
+
21
+ MANDATE:
22
+
23
+ Answer factual, common knowledge, definition, or theoretical questions.
24
+
25
+ CRITICAL REASONING STEP (CoT): For all problem-solving, analytical, or multi-part questions, you MUST perform the following internal steps:
26
+ a. Analyze: Carefully interpret the overall user problem and its objectives.
27
+ b. Breakdown: Decompose the problem into sequential, logical sub-parts.
28
+ c. Solve: Execute the necessary reasoning steps for each sub-part rigorously.
29
+ d. Synthesize: Combine the results into a single, cohesive, final answer.
30
+
31
+ Provide structured, high-quality explanations, examples, and summaries.
32
+
33
+ CONSTRAINTS (CRITICAL ANTI-HALLUCINATION RULES):
34
+
35
+ DO NOT answer any question that requires accessing external, private, or real-time data. This includes but is not limited to: recent news, local weather, file content (RAG), specific company metrics (SQL), or any data more recent than your last training cutoff.
36
+
37
+ IMMEDIATELY and EXCLUSIVELY respond with the exact phrase: "Knowledge gap detected. This query requires external data access (RAG, SQL, or Web)." if the answer is not already in your internal knowledge base.
38
+
39
+ Ensure your response is clear, professional, and directly addresses the query.
40
+
41
+ QUESTION: {query}
42
+
43
+
44
+ synthesizerTemplate: |
45
+ You are the Lead Synthesis Editor, a high-level intelligence responsible for compiling the final, definitive response to the user. Your core objective is to deliver a single, highly coherent, and contextually accurate answer without revealing the underlying system complexity.
46
+
47
+ MANDATE & LOGICAL FRAMEWORK:
48
+
49
+ Seamless Integration: Generate a continuous, narrative response. The user must never know that multiple sub-agents (RAG, SQL, Web) were used. Do not include labels like "RAG says" or "SQL confirms."
50
+
51
+ Contextual Hierarchy & Wisdom: When information is available from multiple sources, weigh the evidence and prioritize the most authoritative source for the specific query type:
52
+
53
+ Internal Data (RAG, SQL): Use this for proprietary definitions, policy details, employee counts, or specific organizational data. If RAG/SQL directly contradicts General Knowledge (Reasoning), the internal data wins.
54
+
55
+ Real-Time Data (Web): Use this for external, up-to-date facts (e.g., market trends, recent events, current stock prices). If Web contradicts a theoretical definition from Reasoning, the Web data wins only if the theory has changed recently.
56
+
57
+ General Knowledge (Reasoning): Use this for foundational definitions, historical context, or conceptual explanations (e.g., what is OOPS, how does an algorithm work).
58
+
59
+ Contradiction Guardrail: If an agent's output is absurd, irrelevant, or obviously contradictory to the question's focus (e.g., the Web Agent gives a cooking recipe for an SQL query), treat that output as null and completely exclude it from the final answer. Always favor logic and direct relevance.
60
+
61
+ Citations: Every claim must be supported by a citation corresponding to the data source. Use parentheses at the end of the sentence or clause: (Source: RAG), (Source: SQL), (Source: Web), or (Source: Model Knowledge).
62
+
63
+ SOURCE DATA INPUTS:
64
+
65
+ Original Question: {query}
66
+
67
+ RAG Context (Internal/Policy Data):
68
+ {ragOutput}
69
+
70
+ SQL Results (Structured Metrics/Counts):
71
+ {sqlOutput}
72
+
73
+ Web Search Snippets (Current/External Data):
74
+ {webOutput}
75
+
76
+ Reasoning Agent Output (Conceptual/Theoretical):
77
+ {reasoningOutput}
78
+
79
+ FINAL DEFINITIVE RESPONSE:
80
+ (Begin your response here, synthesizing information from the sources above into a single, cohesive narrative. Apply the logic and citation rules strictly.)
pyproject.toml ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "agenticdatapipeline"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "aiohappyeyeballs==2.6.1",
9
+ "aiohttp==3.13.2",
10
+ "aiosignal==1.4.0",
11
+ "annotated-doc==0.0.4",
12
+ "annotated-types==0.7.0",
13
+ "anyio==4.11.0",
14
+ "attrs==25.4.0",
15
+ "certifi==2025.11.12",
16
+ "charset-normalizer==3.4.4",
17
+ "click==8.3.1",
18
+ "coloredlogs==15.0.1",
19
+ "dataclasses-json==0.6.7",
20
+ "distro==1.9.0",
21
+ "fastapi==0.121.2",
22
+ "fastembed==0.7.3",
23
+ "filelock==3.20.0",
24
+ "flatbuffers==25.9.23",
25
+ "frozenlist==1.8.0",
26
+ "fsspec==2025.10.0",
27
+ "greenlet==3.2.4",
28
+ "grpcio==1.76.0",
29
+ "h11==0.16.0",
30
+ "h2==4.3.0",
31
+ "hf-xet==1.2.0",
32
+ "hpack==4.1.0",
33
+ "httpcore==1.0.9",
34
+ "httpx==0.28.1",
35
+ "httpx-sse==0.4.3",
36
+ "huggingface-hub==0.36.0",
37
+ "humanfriendly==10.0",
38
+ "hyperframe==6.1.0",
39
+ "idna==3.11",
40
+ "jinja2==3.1.6",
41
+ "jiter==0.12.0",
42
+ "joblib==1.5.2",
43
+ "jsonpatch==1.33",
44
+ "jsonpointer==3.0.0",
45
+ "langchain==1.0.7",
46
+ "langchain-cerebras==0.7.0",
47
+ "langchain-classic==1.0.0",
48
+ "langchain-community==0.4.1",
49
+ "langchain-core==1.0.5",
50
+ "langchain-huggingface==1.0.1",
51
+ "langchain-openai==1.0.3",
52
+ "langchain-qdrant==1.1.0",
53
+ "langchain-text-splitters==1.0.0",
54
+ "langgraph==1.0.3",
55
+ "langgraph-checkpoint==3.0.1",
56
+ "langgraph-prebuilt==1.0.4",
57
+ "langgraph-sdk==0.2.9",
58
+ "langsmith==0.4.43",
59
+ "loguru==0.7.3",
60
+ "markupsafe==3.0.3",
61
+ "marshmallow==3.26.1",
62
+ "mmh3==5.2.0",
63
+ "mpmath==1.3.0",
64
+ "multidict==6.7.0",
65
+ "mypy-extensions==1.1.0",
66
+ "networkx==3.5",
67
+ "numpy==2.3.5",
68
+ "onnxruntime==1.23.2",
69
+ "openai==2.8.1",
70
+ "orjson==3.11.4",
71
+ "ormsgpack==1.12.0",
72
+ "packaging==25.0",
73
+ "pillow==11.3.0",
74
+ "portalocker==3.2.0",
75
+ "propcache==0.4.1",
76
+ "protobuf==6.33.1",
77
+ "py-rust-stemmers==0.1.5",
78
+ "pydantic==2.12.4",
79
+ "pydantic-core==2.41.5",
80
+ "pydantic-settings==2.12.0",
81
+ "python-dotenv==1.2.1",
82
+ "pyyaml==6.0.3",
83
+ "qdrant-client==1.16.0",
84
+ "regex==2025.11.3",
85
+ "requests==2.32.5",
86
+ "requests-toolbelt==1.0.0",
87
+ "safetensors==0.6.2",
88
+ "scikit-learn==1.7.2",
89
+ "scipy==1.16.3",
90
+ "sentence-transformers==5.1.2",
91
+ "sniffio==1.3.1",
92
+ "sqlalchemy==2.0.44",
93
+ "starlette==0.49.3",
94
+ "sympy==1.14.0",
95
+ "tenacity==9.1.2",
96
+ "threadpoolctl==3.6.0",
97
+ "tiktoken==0.12.0",
98
+ "tokenizers==0.22.1",
99
+ "tqdm==4.67.1",
100
+ "transformers==4.57.1",
101
+ "typing-extensions==4.15.0",
102
+ "typing-inspect==0.9.0",
103
+ "typing-inspection==0.4.2",
104
+ "urllib3==2.5.0",
105
+ "uvicorn==0.38.0",
106
+ "xxhash==3.6.0",
107
+ "yarl==1.22.0",
108
+ "zstandard==0.25.0",
109
+ ]
src/__init__.py ADDED
File without changes
src/components/__init__.py ADDED
File without changes
src/components/internetSearchAgent.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.utilities import GoogleSerperAPIWrapper
2
+ from utils.exceptions import CustomException
3
+ from utils.logger import logger
4
+ import os
5
+
6
+ class InternetSearchAgent:
7
+ def __init__(self) -> None:
8
+ logger.info("INITIALIZING INTERNET SEARCH AGENT")
9
+ self.search = GoogleSerperAPIWrapper(serper_api_key=os.environ.get("SERPER_API_KEY"))
10
+
11
+ def query(self, query) -> str:
12
+ try:
13
+ output = self.search.run(query)
14
+ return output
15
+ except Exception as e:
16
+ exception = CustomException(e)
17
+ logger.error(exception)
18
+ raise exception
src/components/ragAgent.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_qdrant import FastEmbedSparse, RetrievalMode
2
+ from langchain_core.output_parsers import StrOutputParser
3
+ from langchain_core.runnables import RunnablePassthrough
4
+ from langchain_huggingface import HuggingFaceEmbeddings
5
+ from langchain_core.prompts import ChatPromptTemplate
6
+ from utils.initMethods import getConfig, readYaml
7
+ from langchain_qdrant import QdrantVectorStore
8
+ from utils.exceptions import CustomException
9
+ from langchain_cerebras import ChatCerebras
10
+ from qdrant_client import QdrantClient
11
+ from utils.logger import logger
12
+ import os
13
+
14
+ config = getConfig(os.path.join(os.getcwd(), "config.ini"))
15
+
16
+ modelName = config.get("RAGAGENT", "denseEmbeddings")
17
+ modelKwargs = {'device': 'cpu'}
18
+ encodeKwargs = {'normalize_embeddings': True}
19
+ embeddings = HuggingFaceEmbeddings(
20
+ model_name=modelName,
21
+ model_kwargs=modelKwargs,
22
+ encode_kwargs=encodeKwargs
23
+ )
24
+
25
+ sparseEmbeddings = FastEmbedSparse(model_name=config.get("RAGAGENT", "sparseEmbeddings"))
26
+
27
+ class RAGAgent:
28
+ def __init__(self) -> None:
29
+ try:
30
+ logger.info("INITIALIZING RAG AGENT")
31
+ client = QdrantClient(
32
+ url=os.environ.get("QDRANT_URL"),
33
+ api_key=os.environ.get("QDRANT_API_KEY"),
34
+ )
35
+ vectorStore = QdrantVectorStore(
36
+ client=client,
37
+ collection_name="sampleCollection",
38
+ embedding=embeddings,
39
+ vector_name="semantic-search",
40
+ sparse_vector_name="syntactic-search",
41
+ retrieval_mode=RetrievalMode.SPARSE,
42
+ sparse_embedding=sparseEmbeddings
43
+ )
44
+ promptTemplate = ChatPromptTemplate.from_template(readYaml(os.path.join(os.getcwd(), "prompts.yaml").get("ragTemplate")))
45
+ retriever = vectorStore.as_retriever(search_kwargs = {"k": 5})
46
+ llm = ChatCerebras(
47
+ model = config.get("RAGAGENT", "modelName"),
48
+ temperature = config.getint("RAGAGENT", "temperature"),
49
+ max_tokens = config.getint("RAGAGENT", "maxTokens")
50
+ )
51
+ chain = {"query": RunnablePassthrough(), "context": RunnablePassthrough() | retriever} | promptTemplate | llm | StrOutputParser()
52
+ self.chain = chain
53
+ except Exception as e:
54
+ exception = CustomException(e)
55
+ logger.error(exception)
56
+ raise exception
57
+
58
+ def query(self, query) -> str:
59
+ try:
60
+ output = self.chain.invoke(query)
61
+ return output
62
+ except Exception as e:
63
+ exception = CustomException(e)
64
+ logger.error(exception)
65
+ raise exception
src/components/reasoningAgent.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.output_parsers import StrOutputParser
2
+ from langchain_core.runnables import RunnablePassthrough
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+ from utils.initMethods import readYaml, getConfig
5
+ from utils.exceptions import CustomException
6
+ from langchain_cerebras import ChatCerebras
7
+ from utils.logger import logger
8
+ import os
9
+
10
+ config = getConfig(os.path.join(os.getcwd(), "config.ini"))
11
+ prompts = readYaml(os.path.join(os.getcwd(), "prompts.yaml"))
12
+
13
+ class ReasoningAgent:
14
+ def __init__(self) -> None:
15
+ try:
16
+ logger.info("INITIALIZING REASONING AGENT")
17
+ promptTemplate = ChatPromptTemplate.from_template(prompts.get("reasoningTemplate"))
18
+ llm = ChatCerebras(
19
+ model = config.get("REASONINGAGENT", "modelName"),
20
+ temperature = config.getint("REASONINGAGENT", "temperature")
21
+ )
22
+ chain = {"query": RunnablePassthrough()} | promptTemplate | llm | StrOutputParser()
23
+ self.chain = chain
24
+ except Exception as e:
25
+ exception = CustomException(e)
26
+ logger.error(exception)
27
+ raise exception
28
+
29
+ def query(self, query) -> str:
30
+ try:
31
+ output = self.chain.invoke(query)
32
+ return output
33
+ except Exception as e:
34
+ exception = CustomException(e)
35
+ logger.error(exception)
36
+ raise exception
src/components/sqlAgent.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
2
+ from langchain_community.utilities.sql_database import SQLDatabase
3
+ from utils.exceptions import CustomException
4
+ from langchain_cerebras import ChatCerebras
5
+ from langchain.agents import create_agent
6
+ from utils.initMethods import getConfig
7
+ from sqlalchemy.pool import StaticPool
8
+ from sqlalchemy import create_engine
9
+ from langchain_classic import hub
10
+ from utils.logger import logger
11
+ import os
12
+
13
+ promptTemplate = hub.pull("langchain-ai/sql-agent-system-prompt")
14
+ systemMessage = promptTemplate.format(dialect="PostgreSQL", top_k=5)
15
+
16
+ class PostgreSQLAgent:
17
+ def __init__(self) -> None:
18
+ try:
19
+ logger.info("INITIALIZING SQL AGENT")
20
+ self.config = getConfig(os.path.join(os.getcwd(), "config.ini"))
21
+ self.engine = create_engine(os.environ.get("POSTGRE_CONNECTION_STRING"), poolclass = StaticPool)
22
+ db = SQLDatabase(self.engine)
23
+ llm = ChatCerebras(
24
+ model = self.config.get("SQLAGENT", "modelName"),
25
+ temperature = self.config.getint("SQLAGENT", "temperature"),
26
+ max_tokens = self.config.getint("SQLAGENT", "maxTokens")
27
+ )
28
+ self.toolkit = SQLDatabaseToolkit(db = db, llm = llm)
29
+ self.agent = create_agent(llm, self.toolkit.get_tools(), system_prompt=systemMessage)
30
+ except Exception as e:
31
+ exception = CustomException(e)
32
+ logger.error(exception)
33
+ raise exception
34
+
35
+ def query(self, query) -> str:
36
+ try:
37
+ response = self.agent.invoke(
38
+ {"messages": [("user", query)]}
39
+ )
40
+ return response["messages"][-1].content
41
+ except Exception as e:
42
+ exception = CustomException(e)
43
+ logger.error(exception)
44
+ raise exception
src/components/synthesizerAgent.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.output_parsers import StrOutputParser
2
+ from langchain_core.runnables import RunnablePassthrough
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+ from utils.initMethods import readYaml, getConfig
5
+ from utils.exceptions import CustomException
6
+ from langchain_cerebras import ChatCerebras
7
+ from utils.logger import logger
8
+ import os
9
+
10
+ config = getConfig(os.path.join(os.getcwd(), "config.ini"))
11
+ prompts = readYaml(os.path.join(os.getcwd(), "prompts.yaml"))
12
+
13
+ class SynthesizerAgent:
14
+ def __init__(self) -> None:
15
+ try:
16
+ logger.info("INITIALIZING SYNTHESIZER AGENT")
17
+ promptTemplate = ChatPromptTemplate.from_template(prompts.get("synthesizerTemplate"))
18
+ llm = ChatCerebras(
19
+ model = config.get("SYNTHESIZERAGENT", "modelName"),
20
+ temperature = config.getint("SYNTHESIZERAGENT", "temperature"),
21
+ max_tokens = config.getint("SYNTHESIZERAGENT", "maxTokens")
22
+ )
23
+ chain = RunnablePassthrough() | promptTemplate | llm | StrOutputParser()
24
+ self.chain = chain
25
+ except Exception as e:
26
+ exception = CustomException(e)
27
+ logger.error(exception)
28
+ raise exception
29
+
30
+ def query(self, query) -> str:
31
+ try:
32
+ output = self.chain.invoke(query)
33
+ return output
34
+ except Exception as e:
35
+ exception = CustomException(e)
36
+ logger.error(exception)
37
+ raise exception
src/workflows/__init__.py ADDED
File without changes
src/workflows/workflow.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.components.internetSearchAgent import InternetSearchAgent
2
+ from src.components.synthesizerAgent import SynthesizerAgent
3
+ from src.components.reasoningAgent import ReasoningAgent
4
+ from src.components.sqlAgent import PostgreSQLAgent
5
+ from langgraph.graph import START, END, StateGraph
6
+ from utils.exceptions import CustomException
7
+ from src.components.ragAgent import RAGAgent
8
+ from utils.logger import logger
9
+ from typing import TypedDict
10
+
11
+ class AgentState(TypedDict):
12
+ internetResults: str
13
+ reasoningResults: str
14
+ sqlResults: str
15
+ ragResults: str
16
+ query: str
17
+ finalAnswer: str
18
+
19
+ class Workflow:
20
+ def __init__(self) -> None:
21
+ self.internetSearchAgentObj = InternetSearchAgent()
22
+ self.reasoningAgentObj = ReasoningAgent()
23
+ self.ragAgentObj = RAGAgent()
24
+ self.sqlAgentObj = PostgreSQLAgent()
25
+ self.synthesizerAgentObj = SynthesizerAgent()
26
+
27
+ def _internetSearchAgent(self, state: AgentState) -> dict:
28
+ return {"internetResults": self.internetSearchAgentObj.query(query=state.get("query"))}
29
+
30
+ def _reasoningAgent(self, state: AgentState) -> dict:
31
+ return {"reasoningResults": self.reasoningAgentObj.query(query=state.get("query"))}
32
+
33
+ def _ragAgent(self, state: AgentState) -> dict:
34
+ return {"ragResults": self.ragAgentObj.query(query=state.get("query"))}
35
+
36
+ def _sqlAgent(self, state: AgentState) -> dict:
37
+ return {"sqlResults": self.sqlAgentObj.query(query=state.get("query"))}
38
+
39
+ def _synthesizerAgent(self, state: AgentState) -> dict:
40
+ return {"finalAnswer": self.synthesizerAgentObj.query({"query": state["query"], "reasoningOutput": state["reasoningResults"], "webOutput": state["internetResults"], "ragOutput": state["ragResults"], "sqlOutput": state["sqlResults"]})}
41
+
42
+ def createWorkflow(self) -> None:
43
+ try:
44
+ logger.info("INITIALIZING LANGGRAPH WORKFLOW")
45
+ graph = StateGraph(AgentState)
46
+ graph.add_node("internetSearchAgent", self._internetSearchAgent, )
47
+ graph.add_node("reasoningAgent", self._reasoningAgent,)
48
+ graph.add_node("ragAgent", self._ragAgent)
49
+ graph.add_node("sqlAgent", self._sqlAgent)
50
+ graph.add_node("synthesizerAgent", self._synthesizerAgent, defer = True)
51
+ graph.add_edge(START, "internetSearchAgent")
52
+ graph.add_edge(START, "reasoningAgent")
53
+ graph.add_edge(START, "ragAgent")
54
+ graph.add_edge(START, "sqlAgent")
55
+ graph.add_edge("internetSearchAgent", "synthesizerAgent")
56
+ graph.add_edge("reasoningAgent", "synthesizerAgent")
57
+ graph.add_edge("ragAgent", "synthesizerAgent")
58
+ graph.add_edge("sqlAgent", "synthesizerAgent")
59
+ graph.add_edge("synthesizerAgent", END)
60
+ self.graph = graph.compile()
61
+ return
62
+ except Exception as e:
63
+ exception = CustomException(e)
64
+ logger.error(exception)
65
+ raise exception
66
+
67
+ def run(self, query: str) -> str:
68
+ return self.graph.invoke({"query": query})["finalAnswer"]
69
+
70
+ workflow = Workflow()
71
+ workflow.createWorkflow()
utils/__init__.py ADDED
File without changes
utils/exceptions.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+
3
+ class CustomException(Exception):
4
+ def __init__(self, exception: Exception) -> None:
5
+ tb = traceback.extract_tb(tb = exception.__traceback__)[-1]
6
+ customErrorMessage = "Error encountered in line no [{lineNumber}], filename : [{fileName}], saying [{errorMessage}]"
7
+ customErrorMessage = customErrorMessage.format(
8
+ lineNumber = tb.lineno,
9
+ fileName = tb.filename,
10
+ errorMessage = str(exception)
11
+ )
12
+ super().__init__(customErrorMessage)
utils/initMethods.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import configparser
2
+ import yaml
3
+
4
+ def readYaml(filePath: str) -> dict:
5
+ with open(filePath, "r") as f:
6
+ content = yaml.safe_load(f)
7
+ return content
8
+
9
+ def getConfig(path: str) -> dict:
10
+ config = configparser.ConfigParser()
11
+ config.read(path)
12
+ return config
utils/logger.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from loguru import logger
2
+ import sys
3
+ import os
4
+
5
+ os.makedirs("logs", exist_ok=True)
6
+
7
+ logger.remove()
8
+ logger.add(
9
+ sys.stdout,
10
+ colorize=True,
11
+ format="<green>{time}</green> | <level>{level}</level> | <cyan>{message}</cyan>",
12
+ level="INFO",
13
+ )
14
+ logger.add(
15
+ "logs/runLogs.log",
16
+ level="DEBUG",
17
+ rotation="1 MB",
18
+ )
uv.lock ADDED
The diff for this file is too large to render. See raw diff