diff --git a/.gitignore b/.gitignore index a406423..1c28ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ credentials.json .zed .DS_STORE *.sqlite3 +*.db \ No newline at end of file diff --git a/app.py b/app.py index e4c988a..4b50bee 100644 --- a/app.py +++ b/app.py @@ -5,6 +5,7 @@ from fastapi.templating import Jinja2Templates from fastapi import Request from fastapi.staticfiles import StaticFiles +from db import DB from chain import RagChain from pprint import pprint @@ -16,7 +17,18 @@ app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/") def home(request: Request): - return templates.TemplateResponse("index.html", {"request": request}) + db = DB() + prompts = db.get_prompts() + + return templates.TemplateResponse("index.html", {"request": request, "prompts": prompts}) + + +@app.get("/prova") +def prova(request: Request): + cursor = DB() + prompts = cursor.get_prompts() + + return prompts @app.websocket("/ws") @@ -40,8 +52,10 @@ async def websocket_endpoint(websocket: WebSocket): top_k=int(config.get("top_k", 40)), top_p=float(config.get("top_p", 0.0)), temperature=float(config.get("temperature", 0.0)), - retriever_max_docs=int(config.get("retriever_max_docs", 40)), - reranker_max_results=int(config.get("reranker_max_results", 20)), + retriever_max_docs=int( + config.get("retriever_max_docs", 40)), + reranker_max_results=int( + config.get("reranker_max_results", 20)), ) async for chunk in rag_chain.stream(message): @@ -52,6 +66,7 @@ async def websocket_endpoint(websocket: WebSocket): "type": "report", "sources": rag_chain.getSources(), "reranked_sources": rag_chain.getRankedSources(), + "rephrased_question": rag_chain.getRephrasedQuestion(), } ) finally: diff --git a/chain.py b/chain.py index e99da77..6f641be 100644 --- a/chain.py +++ b/chain.py @@ -45,6 +45,7 @@ class RagChain: self._retriever_sources: list[dict] = [] self._reranked_sources: list[dict] = [] + self._rephrased_question: str = "" self._llm = ChatGoogleGenerativeAI( model=MODEL, @@ -80,7 +81,7 @@ class RagChain: | question_rewrite_prompt | self._llm | StrOutputParser() - | RunnableLambda(self._log_rewritten_question) + | RunnableLambda(self._log_rephrased_question) ) rag_chain = ( @@ -95,8 +96,9 @@ class RagChain: self._full_chain = question_rewrite_chain | rag_chain - def _log_rewritten_question(self, rewritten_question: str) -> str: - return rewritten_question + def _log_rephrased_question(self, rephrased_question: str) -> str: + self._rephrased_question = rephrased_question + return rephrased_question def _format_docs(self, question: str) -> str: retrieved_docs = self._base_retriever.invoke(question) @@ -150,5 +152,8 @@ class RagChain: def getRankedSources(self) -> list[dict]: return list(self._reranked_sources) + def getRephrasedQuestion(self) -> str: + return self._rephrased_question + def stream(self, message: str): return self._full_chain.astream(message) diff --git a/db.py b/db.py new file mode 100644 index 0000000..bc2e1cd --- /dev/null +++ b/db.py @@ -0,0 +1,15 @@ +from typing import List +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + +from models.orm import Prompt + + +class DB: + def __init__(self, db: str = "sqlite:///example.db"): + self.engine = create_engine( + db, connect_args={"check_same_thread": False}) + + def get_prompts(self) -> List[Prompt]: + with Session(self.engine) as session: + return session.query(Prompt).all() diff --git a/models/orm.py b/models/orm.py new file mode 100644 index 0000000..be223f4 --- /dev/null +++ b/models/orm.py @@ -0,0 +1,17 @@ +from sqlalchemy import create_engine, Column, String, Text, Integer +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + + +class Prompt(Base): + __tablename__ = "prompts" + + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) + text = Column(Text, nullable=False) + + +if __name__ == "__main__": + engine = create_engine("sqlite:///example.db") + Base.metadata.create_all(engine) diff --git a/models/validation.py b/models/validation.py new file mode 100644 index 0000000..91d1bf8 --- /dev/null +++ b/models/validation.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class Prompt(BaseModel): + name: str + text: str diff --git a/pyproject.toml b/pyproject.toml index 6dc12b0..73f58d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,8 @@ dependencies = [ "python-dotenv>=1.2.1", "fastapi>=0.129.0", "fastapi[standard]", + "pydantic>=2.12.5", + "sqlalchemy>=2.0.46", ] [dependency-groups] diff --git a/requirements.txt b/requirements.txt index 2cf3edc..2a32256 100644 --- a/requirements.txt +++ b/requirements.txt @@ -75,7 +75,6 @@ mdurl==0.1.2 multidict==6.7.1 mypy-extensions==1.1.0 nodeenv==1.10.0 -nuitka==4.0.1 numexpr==2.14.1 numpy==2.4.2 openai==2.21.0 diff --git a/static/css/chat.css b/static/css/chat.css index b8e9efa..8c2a06d 100644 --- a/static/css/chat.css +++ b/static/css/chat.css @@ -1,274 +1,272 @@ :root { - color-scheme: light; - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color-scheme: light; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; } * { - box-sizing: border-box; + box-sizing: border-box; } html, body { - height: 100%; + height: 100%; } body { - margin: 0; - padding: 0; - background: #f7f8fa; - color: #1f2937; + margin: 0; + padding: 0; + background: #f7f8fa; + color: #1f2937; } .app { - display: flex; - width: 100vw; - height: 100vh; - overflow: hidden; + display: flex; + width: 100vw; + height: 100vh; + overflow: hidden; } .chat { - flex: 1; - height: 100%; - margin: 0; - background: #ffffff; - border: 1px solid #e5e7eb; - border-radius: 0; - display: flex; - flex-direction: column; + flex: 1; + height: 100%; + margin: 0; + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 0; + display: flex; + flex-direction: column; } .chat__header { - padding: 16px 20px; - border-bottom: 1px solid #e5e7eb; - font-weight: 600; - background: #f9fafb; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; + padding: 16px 20px; + border-bottom: 1px solid #e5e7eb; + font-weight: 600; + background: #f9fafb; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; } .chat__settings { - padding: 6px 12px; - border-radius: 999px; - border: 1px solid #d1d5db; - background: #ffffff; - color: #1f2937; - font-weight: 600; - font-size: 13px; - cursor: pointer; + padding: 6px 12px; + border-radius: 999px; + border: 1px solid #d1d5db; + background: #ffffff; + color: #1f2937; + font-weight: 600; + font-size: 13px; + cursor: pointer; } .chat__settings:hover { - background: #f3f4f6; + background: #f3f4f6; } .chat__messages { - flex: 1; - padding: 16px 20px; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 10px; + flex: 1; + padding: 16px 20px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 10px; } .message { - max-width: 70%; - padding: 10px 12px; - border-radius: 12px; - line-height: 1.4; - font-size: 14px; - word-wrap: break-word; - white-space: pre-wrap; + max-width: 70%; + padding: 10px 12px; + border-radius: 12px; + line-height: 1.4; + font-size: 14px; + word-wrap: break-word; + white-space: pre-wrap; } .message--out { - align-self: flex-end; - background: #2563eb; - color: #ffffff; + align-self: flex-end; + background: #2563eb; + color: #ffffff; } .message--in { - align-self: flex-start; - background: #f3f4f6; - color: #111827; + align-self: flex-start; + background: #f3f4f6; + color: #111827; } /* Typing / loading indicator */ .message--loading { - display: flex; - align-items: center; - gap: 5px; - padding: 12px 16px; + display: flex; + align-items: center; + gap: 5px; + padding: 12px 16px; } .typing-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: #9ca3af; - animation: typing-bounce 1.2s infinite ease-in-out; + width: 8px; + height: 8px; + border-radius: 50%; + background: #9ca3af; + animation: typing-bounce 1.2s infinite ease-in-out; } .typing-dot:nth-child(1) { - animation-delay: 0s; + animation-delay: 0s; } .typing-dot:nth-child(2) { - animation-delay: 0.2s; + animation-delay: 0.2s; } .typing-dot:nth-child(3) { - animation-delay: 0.4s; + animation-delay: 0.4s; } @keyframes typing-bounce { + 0%, + 60%, + 100% { + transform: translateY(0); + background: #9ca3af; + } - 0%, - 60%, - 100% { - transform: translateY(0); - background: #9ca3af; - } - - 30% { - transform: translateY(-6px); - background: #6b7280; - } + 30% { + transform: translateY(-6px); + background: #6b7280; + } } .chat__footer { - padding: 16px 20px; - border-top: 1px solid #e5e7eb; - display: flex; - gap: 12px; - background: #f9fafb; + padding: 16px 20px; + border-top: 1px solid #e5e7eb; + display: flex; + gap: 12px; + background: #f9fafb; } .chat__input { - flex: 1; - padding: 10px 12px; - border-radius: 8px; - border: 1px solid #d1d5db; - background: #ffffff; - color: #111827; + flex: 1; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid #d1d5db; + background: #ffffff; + color: #111827; } .chat__input::placeholder { - color: #9ca3af; + color: #9ca3af; } .chat__button { - padding: 10px 16px; - border-radius: 8px; - border: none; - background: #2563eb; - color: #ffffff; - font-weight: 600; - cursor: pointer; + padding: 10px 16px; + border-radius: 8px; + border: none; + background: #2563eb; + color: #ffffff; + font-weight: 600; + cursor: pointer; } .chat__button:disabled { - background: #cbd5f5; - color: #ffffff; - cursor: not-allowed; - opacity: 0.75; + background: #cbd5f5; + color: #ffffff; + cursor: not-allowed; + opacity: 0.75; } .chat__button--secondary { - background: #e5e7eb; - color: #1f2937; + background: #e5e7eb; + color: #1f2937; } .chat__button--secondary:hover { - background: #d1d5db; + background: #d1d5db; } .chat__status { - padding: 8px 20px 0; - font-size: 12px; - color: #6b7280; + padding: 8px 20px 0; + font-size: 12px; + color: #6b7280; } .drawer { - width: 320px; - max-width: 100%; - height: 100%; - border-left: 1px solid #e5e7eb; - background: #ffffff; - padding: 20px; - display: flex; - flex-direction: column; - gap: 16px; - transform: translateX(100%); - transition: transform 0.25s ease; + width: 320px; + max-width: 100%; + height: 100%; + border-left: 1px solid #e5e7eb; + background: #ffffff; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; + transform: translateX(100%); + transition: transform 0.25s ease; } .drawer--open { - transform: translateX(0%); + transform: translateX(0%); } .drawer__header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; } .drawer__title { - margin: 0; - font-size: 18px; + margin: 0; + font-size: 18px; } .drawer__close { - border: 1px solid #d1d5db; - background: #ffffff; - border-radius: 8px; - padding: 4px 8px; - cursor: pointer; + border: 1px solid #d1d5db; + background: #ffffff; + border-radius: 8px; + padding: 4px 8px; + cursor: pointer; } .drawer__form { - display: flex; - flex-direction: column; - gap: 16px; + display: flex; + flex-direction: column; + gap: 16px; } .drawer__field { - display: grid; - grid-template-columns: 1fr auto; - grid-template-rows: auto auto; - gap: 6px 12px; - align-items: center; - font-size: 13px; - color: #1f2937; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto auto; + gap: 6px 12px; + align-items: center; + font-size: 13px; + color: #1f2937; } .drawer__label { - font-weight: 600; + font-weight: 600; } .drawer__range { - grid-column: 1 / -1; - width: 100%; + grid-column: 1 / -1; + width: 100%; } .drawer__value { - font-variant-numeric: tabular-nums; -} - -.drawer__hint { - margin: 0; - font-size: 12px; - color: #6b7280; + font-variant-numeric: tabular-nums; } @media (max-width: 900px) { - .drawer { - position: absolute; - right: 0; - top: 0; - height: 100vh; - box-shadow: -12px 0 24px rgba(15, 23, 42, 0.08); - } -} \ No newline at end of file + .drawer { + position: absolute; + right: 0; + top: 0; + height: 100vh; + box-shadow: -12px 0 24px rgba(15, 23, 42, 0.08); + } +} diff --git a/static/js/chat.js b/static/js/chat.js index 0e7f754..889e32e 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -8,14 +8,14 @@ const settingsToggle = document.getElementById("settings-toggle"); const settingsDrawer = document.getElementById("settings-drawer"); const settingsClose = document.getElementById("settings-close"); const rangeInputs = settingsDrawer - ? Array.from(settingsDrawer.querySelectorAll("input[type='range']")) - : []; + ? Array.from(settingsDrawer.querySelectorAll("input[type='range']")) + : []; const configInputs = { - topK: document.getElementById("config-top-k"), - topP: document.getElementById("config-top-p"), - temperature: document.getElementById("config-temperature"), - retrieverMaxDocs: document.getElementById("config-retriever-max-docs"), - rerankerMaxDocs: document.getElementById("config-reranker-max-docs"), + topK: document.getElementById("config-top-k"), + topP: document.getElementById("config-top-p"), + temperature: document.getElementById("config-temperature"), + retrieverMaxDocs: document.getElementById("config-retriever-max-docs"), + rerankerMaxDocs: document.getElementById("config-reranker-max-docs"), }; const scheme = window.location.protocol === "https:" ? "wss" : "ws"; @@ -26,178 +26,189 @@ let loadingBubble = null; let isAwaitingResponse = false; const updateSendButtonState = () => { - if (!sendButtonEl) return; - sendButtonEl.disabled = isAwaitingResponse; + if (!sendButtonEl) return; + sendButtonEl.disabled = isAwaitingResponse; }; const addMessage = (text, direction) => { - const bubble = document.createElement("div"); - bubble.className = `message message--${direction}`; - bubble.textContent = text; - messagesEl.appendChild(bubble); - messagesEl.scrollTop = messagesEl.scrollHeight; + const bubble = document.createElement("div"); + bubble.className = `message message--${direction}`; + bubble.textContent = text; + messagesEl.appendChild(bubble); + messagesEl.scrollTop = messagesEl.scrollHeight; }; const showLoadingBubble = () => { - if (loadingBubble) return; - loadingBubble = document.createElement("div"); - loadingBubble.className = "message message--in message--loading"; - loadingBubble.innerHTML = - ''; - messagesEl.appendChild(loadingBubble); - messagesEl.scrollTop = messagesEl.scrollHeight; + if (loadingBubble) return; + loadingBubble = document.createElement("div"); + loadingBubble.className = "message message--in message--loading"; + loadingBubble.innerHTML = + ''; + messagesEl.appendChild(loadingBubble); + messagesEl.scrollTop = messagesEl.scrollHeight; }; const removeLoadingBubble = () => { - if (!loadingBubble) return; - loadingBubble.remove(); - loadingBubble = null; + if (!loadingBubble) return; + loadingBubble.remove(); + loadingBubble = null; }; const clearMessages = () => { - if (!messagesEl) return; - messagesEl.innerHTML = ""; - streamingBubble = null; - loadingBubble = null; + if (!messagesEl) return; + messagesEl.innerHTML = ""; + streamingBubble = null; + loadingBubble = null; }; const connect = () => { - socket = new WebSocket(socketUrl); + socket = new WebSocket(socketUrl); - socket.addEventListener("open", () => { - statusEl.textContent = "Connected"; - }); + socket.addEventListener("open", () => { + statusEl.textContent = "Connected"; + }); - socket.addEventListener("message", (event) => { - let payload; - try { - payload = JSON.parse(event.data); - } catch (error) { - addMessage(event.data, "in"); - return; - } + socket.addEventListener("message", (event) => { + let payload; + try { + payload = JSON.parse(event.data); + } catch (error) { + addMessage(event.data, "in"); + return; + } - if (payload.type === "start") { - // Keep the loading bubble visible until the first chunk arrives. - // Just prepare the streaming bubble but don't append it yet. - streamingBubble = document.createElement("div"); - streamingBubble.className = "message message--in"; - streamingBubble.textContent = ""; - return; - } + if (payload.type === "start") { + // Keep the loading bubble visible until the first chunk arrives. + // Just prepare the streaming bubble but don't append it yet. + streamingBubble = document.createElement("div"); + streamingBubble.className = "message message--in"; + streamingBubble.textContent = ""; + return; + } - if (payload.type === "chunk") { - if (!streamingBubble) { - streamingBubble = document.createElement("div"); - streamingBubble.className = "message message--in"; - } - // First chunk: swap loading bubble for the streaming bubble. - if (!streamingBubble.isConnected) { - removeLoadingBubble(); - messagesEl.appendChild(streamingBubble); - } - streamingBubble.textContent += payload.data ?? ""; - messagesEl.scrollTop = messagesEl.scrollHeight; - return; - } - - if (payload.type === "report") { - console.log("%cSOURCES", 'border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;') - console.log(payload.sources); - console.log("%cRE-RANKED SOURCES", 'border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;') - console.log(payload.reranked_sources); - return; - } - - if (payload.type === "end") { - // Safety net: remove loading bubble if no chunks were ever received. - removeLoadingBubble(); - streamingBubble = null; - isAwaitingResponse = false; - updateSendButtonState(); - return; - } - }); - - socket.addEventListener("close", () => { - statusEl.textContent = "Disconnected"; + if (payload.type === "chunk") { + if (!streamingBubble) { + streamingBubble = document.createElement("div"); + streamingBubble.className = "message message--in"; + } + // First chunk: swap loading bubble for the streaming bubble. + if (!streamingBubble.isConnected) { removeLoadingBubble(); - isAwaitingResponse = false; - updateSendButtonState(); - }); + messagesEl.appendChild(streamingBubble); + } + streamingBubble.textContent += payload.data ?? ""; + messagesEl.scrollTop = messagesEl.scrollHeight; + return; + } - socket.addEventListener("error", () => { - statusEl.textContent = "Connection error"; - removeLoadingBubble(); - isAwaitingResponse = false; - updateSendButtonState(); - }); + if (payload.type === "report") { + console.log( + "%cREPHRASED QUESTION", + "border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;", + ); + console.log(payload.rephrased_question); + console.log( + "%cSOURCES", + "border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;", + ); + console.log(payload.sources); + console.log( + "%cRE-RANKED SOURCES", + "border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;", + ); + console.log(payload.reranked_sources); + return; + } + + if (payload.type === "end") { + // Safety net: remove loading bubble if no chunks were ever received. + removeLoadingBubble(); + streamingBubble = null; + isAwaitingResponse = false; + updateSendButtonState(); + return; + } + }); + + socket.addEventListener("close", () => { + statusEl.textContent = "Disconnected"; + removeLoadingBubble(); + isAwaitingResponse = false; + updateSendButtonState(); + }); + + socket.addEventListener("error", () => { + statusEl.textContent = "Connection error"; + removeLoadingBubble(); + isAwaitingResponse = false; + updateSendButtonState(); + }); }; const updateDrawerValue = (input) => { - const output = input.parentElement?.querySelector(".drawer__value"); - if (!output) return; - output.textContent = input.value; + const output = input.parentElement?.querySelector(".drawer__value"); + if (!output) return; + output.textContent = input.value; }; rangeInputs.forEach((input) => { - updateDrawerValue(input); - input.addEventListener("input", () => updateDrawerValue(input)); + updateDrawerValue(input); + input.addEventListener("input", () => updateDrawerValue(input)); }); const closeDrawer = () => { - settingsDrawer?.classList.remove("drawer--open"); - if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "true"); - if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "false"); + settingsDrawer?.classList.remove("drawer--open"); + if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "true"); + if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "false"); }; const openDrawer = () => { - settingsDrawer?.classList.add("drawer--open"); - if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "false"); - if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "true"); + settingsDrawer?.classList.add("drawer--open"); + if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "false"); + if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "true"); }; settingsToggle?.addEventListener("click", () => { - if (settingsDrawer?.classList.contains("drawer--open")) { - closeDrawer(); - } else { - openDrawer(); - } + if (settingsDrawer?.classList.contains("drawer--open")) { + closeDrawer(); + } else { + openDrawer(); + } }); settingsClose?.addEventListener("click", closeDrawer); clearEl?.addEventListener("click", () => { - clearMessages(); - inputEl?.focus(); - updateSendButtonState(); + clearMessages(); + inputEl?.focus(); + updateSendButtonState(); }); inputEl?.addEventListener("input", updateSendButtonState); formEl.addEventListener("submit", (event) => { - event.preventDefault(); - const text = inputEl.value.trim(); - if (!text) return; - if (!socket || socket.readyState !== WebSocket.OPEN) { - addMessage("Not connected.", "in"); - return; - } - if (isAwaitingResponse) return; - const config = { - top_k: Number(configInputs.topK?.value ?? 0), - top_p: Number(configInputs.topP?.value ?? 0), - temperature: Number(configInputs.temperature?.value ?? 0), - retriever_max_docs: Number(configInputs.retrieverMaxDocs?.value ?? 0), - reranker_max_results: Number(configInputs.rerankerMaxDocs?.value ?? 0), - }; - addMessage(text, "out"); - socket.send(JSON.stringify({ message: text, config })); - inputEl.value = ""; - inputEl.focus(); - isAwaitingResponse = true; - updateSendButtonState(); - showLoadingBubble(); + event.preventDefault(); + const text = inputEl.value.trim(); + if (!text) return; + if (!socket || socket.readyState !== WebSocket.OPEN) { + addMessage("Not connected.", "in"); + return; + } + if (isAwaitingResponse) return; + const config = { + top_k: Number(configInputs.topK?.value ?? 0), + top_p: Number(configInputs.topP?.value ?? 0), + temperature: Number(configInputs.temperature?.value ?? 0), + retriever_max_docs: Number(configInputs.retrieverMaxDocs?.value ?? 0), + reranker_max_results: Number(configInputs.rerankerMaxDocs?.value ?? 0), + }; + addMessage(text, "out"); + socket.send(JSON.stringify({ message: text, config })); + inputEl.value = ""; + inputEl.focus(); + isAwaitingResponse = true; + updateSendButtonState(); + showLoadingBubble(); }); connect(); diff --git a/templates/index.html b/templates/index.html index c6477f7..f195548 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,72 +1,143 @@ - + - - + AKERN Assistant - + - +
-
-
- AKERN Assistant - -
-
-
Connecting…
- -
+
+
+ AKERN Assistant + +
+
+
Connecting…
+ +
- +
- - - \ No newline at end of file + + diff --git a/uv.lock b/uv.lock index aca1ffb..ea3dc78 100644 --- a/uv.lock +++ b/uv.lock @@ -109,7 +109,9 @@ dependencies = [ { name = "langchain-google-genai" }, { name = "langchain-google-vertexai" }, { name = "langchain-openai" }, + { name = "pydantic" }, { name = "python-dotenv" }, + { name = "sqlalchemy" }, ] [package.dev-dependencies] @@ -129,7 +131,9 @@ requires-dist = [ { name = "langchain-google-genai", specifier = ">=4.2.0" }, { name = "langchain-google-vertexai", specifier = ">=3.2.0" }, { name = "langchain-openai", specifier = ">=1.1.9" }, + { name = "pydantic", specifier = ">=2.12.5" }, { name = "python-dotenv", specifier = ">=1.2.1" }, + { name = "sqlalchemy", specifier = ">=2.0.46" }, ] [package.metadata.requires-dev] @@ -920,6 +924,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, @@ -928,6 +933,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, @@ -936,6 +942,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },