mayankchugh-learning commited on
Commit ·
393bec4
1
Parent(s): 482fce4
Enhance API and Streamlit UI with new timeout handling and telemetry adjustments
Browse files- Added configurable HTTP read timeout in Streamlit app, allowing users to set a maximum wait time for requests.
- Updated API to disable telemetry for Chroma to prevent errors with posthog capture.
- Introduced legacy query endpoint for backward compatibility with older clients.
- Updated dependencies in pyproject.toml and requirements.txt to include posthog with version constraints.
- Enhanced vector store initialization to disable telemetry and ensure proper client settings.
- Improved user feedback in the Streamlit UI regarding request timeouts and API connection status.
- api/main.py +6 -0
- api/routes/query.py +9 -0
- pyproject.toml +2 -0
- rag/vector_store.py +6 -2
- requirements.txt +1 -0
- streamlit_app.py +116 -19
- uv.lock +22 -9
api/main.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
|
| 3 |
from api.config import get_settings
|
|
@@ -11,6 +16,7 @@ app.include_router(audit.router)
|
|
| 11 |
app.include_router(ingest.router)
|
| 12 |
app.include_router(jobs.router)
|
| 13 |
app.include_router(query.router)
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
@app.on_event("startup")
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
# Before any route imports that touch Chroma: disable product telemetry (avoids posthog capture() errors in logs).
|
| 4 |
+
os.environ.setdefault("ANONYMIZED_TELEMETRY", "FALSE")
|
| 5 |
+
|
| 6 |
from fastapi import FastAPI
|
| 7 |
|
| 8 |
from api.config import get_settings
|
|
|
|
| 16 |
app.include_router(ingest.router)
|
| 17 |
app.include_router(jobs.router)
|
| 18 |
app.include_router(query.router)
|
| 19 |
+
app.include_router(query.legacy_query_router)
|
| 20 |
|
| 21 |
|
| 22 |
@app.on_event("startup")
|
api/routes/query.py
CHANGED
|
@@ -116,3 +116,12 @@ async def summarise_endpoint(payload: SummariseRequest) -> QueryResponse:
|
|
| 116 |
except Exception as exc:
|
| 117 |
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
| 118 |
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
except Exception as exc:
|
| 117 |
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
| 118 |
return response
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
legacy_query_router = APIRouter(tags=["query"])
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@legacy_query_router.post("/query", response_model=QueryResponse)
|
| 125 |
+
async def query_post_compat(payload: QueryRequest) -> QueryResponse:
|
| 126 |
+
"""Same behavior as POST /query/ask; kept for older clients and docs that used POST /query."""
|
| 127 |
+
return await ask_endpoint(payload)
|
pyproject.toml
CHANGED
|
@@ -14,6 +14,8 @@ dependencies = [
|
|
| 14 |
"langchain-anthropic==0.1.15",
|
| 15 |
"langchain-ollama==0.1.3",
|
| 16 |
"chromadb==0.5.0",
|
|
|
|
|
|
|
| 17 |
"openai==1.30.1",
|
| 18 |
"anthropic==0.28.1",
|
| 19 |
"pydantic-settings==2.3.4",
|
|
|
|
| 14 |
"langchain-anthropic==0.1.15",
|
| 15 |
"langchain-ollama==0.1.3",
|
| 16 |
"chromadb==0.5.0",
|
| 17 |
+
# Chroma 0.5 calls posthog.capture(distinct_id, event, props); posthog 6+ removed that API (breaks telemetry + spams stderr).
|
| 18 |
+
"posthog>=3.7.0,<4",
|
| 19 |
"openai==1.30.1",
|
| 20 |
"anthropic==0.28.1",
|
| 21 |
"pydantic-settings==2.3.4",
|
rag/vector_store.py
CHANGED
|
@@ -2,10 +2,13 @@ from pathlib import Path
|
|
| 2 |
from uuid import uuid4
|
| 3 |
|
| 4 |
import chromadb
|
|
|
|
| 5 |
from langchain_chroma import Chroma
|
| 6 |
from langchain_core.documents import Document
|
| 7 |
from langchain_core.embeddings import Embeddings
|
| 8 |
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def get_vector_store(
|
| 11 |
persist_directory: str,
|
|
@@ -17,6 +20,7 @@ def get_vector_store(
|
|
| 17 |
collection_name=collection_name,
|
| 18 |
embedding_function=embedding_function,
|
| 19 |
persist_directory=persist_directory,
|
|
|
|
| 20 |
)
|
| 21 |
|
| 22 |
|
|
@@ -28,12 +32,12 @@ def add_documents(vector_store: Chroma, chunks: list[Document]) -> list[str]:
|
|
| 28 |
|
| 29 |
def list_collection_names(persist_directory: str) -> list[str]:
|
| 30 |
Path(persist_directory).mkdir(parents=True, exist_ok=True)
|
| 31 |
-
client = chromadb.PersistentClient(path=persist_directory)
|
| 32 |
return sorted(c.name for c in client.list_collections())
|
| 33 |
|
| 34 |
|
| 35 |
def delete_collection(persist_directory: str, collection_name: str) -> None:
|
| 36 |
Path(persist_directory).mkdir(parents=True, exist_ok=True)
|
| 37 |
-
client = chromadb.PersistentClient(path=persist_directory)
|
| 38 |
client.delete_collection(name=collection_name)
|
| 39 |
|
|
|
|
| 2 |
from uuid import uuid4
|
| 3 |
|
| 4 |
import chromadb
|
| 5 |
+
from chromadb.config import Settings
|
| 6 |
from langchain_chroma import Chroma
|
| 7 |
from langchain_core.documents import Document
|
| 8 |
from langchain_core.embeddings import Embeddings
|
| 9 |
|
| 10 |
+
_CHROMA_CLIENT_SETTINGS = Settings(anonymized_telemetry=False)
|
| 11 |
+
|
| 12 |
|
| 13 |
def get_vector_store(
|
| 14 |
persist_directory: str,
|
|
|
|
| 20 |
collection_name=collection_name,
|
| 21 |
embedding_function=embedding_function,
|
| 22 |
persist_directory=persist_directory,
|
| 23 |
+
client_settings=_CHROMA_CLIENT_SETTINGS,
|
| 24 |
)
|
| 25 |
|
| 26 |
|
|
|
|
| 32 |
|
| 33 |
def list_collection_names(persist_directory: str) -> list[str]:
|
| 34 |
Path(persist_directory).mkdir(parents=True, exist_ok=True)
|
| 35 |
+
client = chromadb.PersistentClient(path=persist_directory, settings=_CHROMA_CLIENT_SETTINGS)
|
| 36 |
return sorted(c.name for c in client.list_collections())
|
| 37 |
|
| 38 |
|
| 39 |
def delete_collection(persist_directory: str, collection_name: str) -> None:
|
| 40 |
Path(persist_directory).mkdir(parents=True, exist_ok=True)
|
| 41 |
+
client = chromadb.PersistentClient(path=persist_directory, settings=_CHROMA_CLIENT_SETTINGS)
|
| 42 |
client.delete_collection(name=collection_name)
|
| 43 |
|
requirements.txt
CHANGED
|
@@ -9,6 +9,7 @@ langchain-text-splitters==0.2.0
|
|
| 9 |
langchain-anthropic==0.1.15
|
| 10 |
langchain-ollama==0.1.3
|
| 11 |
chromadb==0.5.0
|
|
|
|
| 12 |
openai==1.30.1
|
| 13 |
anthropic==0.28.1
|
| 14 |
pymupdf==1.24.3
|
|
|
|
| 9 |
langchain-anthropic==0.1.15
|
| 10 |
langchain-ollama==0.1.3
|
| 11 |
chromadb==0.5.0
|
| 12 |
+
posthog>=3.7.0,<4
|
| 13 |
openai==1.30.1
|
| 14 |
anthropic==0.28.1
|
| 15 |
pymupdf==1.24.3
|
streamlit_app.py
CHANGED
|
@@ -10,15 +10,44 @@ import httpx
|
|
| 10 |
import streamlit as st
|
| 11 |
|
| 12 |
DEFAULT_API_BASE = os.environ.get("DOC_AUDI_API_BASE", "http://127.0.0.1:8000")
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def _api_base() -> str:
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
def _client() -> httpx.Client:
|
| 21 |
-
return httpx.Client(base_url=_api_base(), timeout=
|
| 22 |
|
| 23 |
|
| 24 |
def _fmt_api_error(exc: httpx.HTTPStatusError) -> str:
|
|
@@ -42,6 +71,30 @@ def _fmt_api_error(exc: httpx.HTTPStatusError) -> str:
|
|
| 42 |
return f"HTTP {exc.response.status_code}"
|
| 43 |
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
def _health_check() -> tuple[bool, str]:
|
| 46 |
try:
|
| 47 |
with _client() as c:
|
|
@@ -51,6 +104,8 @@ def _health_check() -> tuple[bool, str]:
|
|
| 51 |
return True, str(data)
|
| 52 |
except httpx.ConnectError as e:
|
| 53 |
return False, f"Cannot connect to {_api_base()}: {e}"
|
|
|
|
|
|
|
| 54 |
except httpx.HTTPStatusError as e:
|
| 55 |
return False, _fmt_api_error(e)
|
| 56 |
except Exception as e:
|
|
@@ -59,15 +114,25 @@ def _health_check() -> tuple[bool, str]:
|
|
| 59 |
|
| 60 |
def main() -> None:
|
| 61 |
st.set_page_config(page_title="doc-audi-ai", layout="wide")
|
| 62 |
-
st.title("doc-audi-ai")
|
| 63 |
-
st.caption("Ingest, query, and audit via the FastAPI backend.")
|
| 64 |
-
|
| 65 |
if "api_base" not in st.session_state:
|
| 66 |
st.session_state.api_base = DEFAULT_API_BASE
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
with st.sidebar:
|
| 69 |
st.subheader("Backend")
|
| 70 |
-
st.text_input(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
if st.button("Test connection"):
|
| 72 |
ok, msg = _health_check()
|
| 73 |
if ok:
|
|
@@ -254,18 +319,36 @@ def main() -> None:
|
|
| 254 |
st.warning("Enter a question.")
|
| 255 |
else:
|
| 256 |
try:
|
| 257 |
-
with
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
if ans.get("answer"):
|
| 263 |
st.markdown("### Answer")
|
| 264 |
st.markdown(ans["answer"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
src = ans.get("sources") or []
|
| 266 |
if src:
|
| 267 |
with st.expander(f"Sources ({len(src)})"):
|
| 268 |
st.json(src)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
except httpx.HTTPStatusError as e:
|
| 270 |
st.error(_fmt_api_error(e))
|
| 271 |
except httpx.ConnectError as e:
|
|
@@ -282,22 +365,32 @@ def main() -> None:
|
|
| 282 |
body: dict[str, Any] = {"collection_name": s_col}
|
| 283 |
if focus.strip():
|
| 284 |
body["focus"] = focus.strip()
|
| 285 |
-
with
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
| 290 |
if ans.get("answer"):
|
| 291 |
st.markdown("### Summary")
|
| 292 |
st.markdown(ans["answer"])
|
|
|
|
|
|
|
| 293 |
src = ans.get("sources") or []
|
| 294 |
if src:
|
| 295 |
with st.expander(f"Sources ({len(src)})"):
|
| 296 |
st.json(src)
|
|
|
|
|
|
|
| 297 |
except httpx.HTTPStatusError as e:
|
| 298 |
st.error(_fmt_api_error(e))
|
| 299 |
except httpx.ConnectError as e:
|
| 300 |
st.error(f"Connection failed: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
except Exception as e:
|
| 302 |
st.exception(e)
|
| 303 |
|
|
@@ -311,7 +404,11 @@ def main() -> None:
|
|
| 311 |
if st.button("List audit events", key="btn_audit_list"):
|
| 312 |
try:
|
| 313 |
with _client() as c:
|
| 314 |
-
r =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
r.raise_for_status()
|
| 316 |
payload = r.json()
|
| 317 |
events = payload.get("events", [])
|
|
@@ -340,7 +437,7 @@ def main() -> None:
|
|
| 340 |
if st.button("Load detail", key="btn_audit_detail") and ev_id:
|
| 341 |
try:
|
| 342 |
with _client() as c:
|
| 343 |
-
r =
|
| 344 |
r.raise_for_status()
|
| 345 |
st.json(r.json())
|
| 346 |
except httpx.HTTPStatusError as e:
|
|
|
|
| 10 |
import streamlit as st
|
| 11 |
|
| 12 |
DEFAULT_API_BASE = os.environ.get("DOC_AUDI_API_BASE", "http://127.0.0.1:8000")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _http_read_timeout_seconds() -> float:
|
| 16 |
+
raw = os.environ.get("DOC_AUDI_HTTP_READ_TIMEOUT", "600")
|
| 17 |
+
try:
|
| 18 |
+
read_s = float(raw)
|
| 19 |
+
except ValueError:
|
| 20 |
+
read_s = 600.0
|
| 21 |
+
return max(60.0, min(read_s, 3600.0))
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _http_timeout() -> httpx.Timeout:
|
| 25 |
+
"""LLM + embeddings can exceed a few minutes on CPU or cold Ollama; Streamlit uses this, not Uvicorn."""
|
| 26 |
+
read_s = _http_read_timeout_seconds()
|
| 27 |
+
return httpx.Timeout(connect=20.0, read=read_s, write=120.0, pool=30.0)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _fmt_timeout_hint() -> str:
|
| 31 |
+
cap = int(_http_read_timeout_seconds())
|
| 32 |
+
return (
|
| 33 |
+
f"The UI stops waiting after **{cap}s** per request (set **DOC_AUDI_HTTP_READ_TIMEOUT** to raise it, max 3600). "
|
| 34 |
+
"Ensure `ollama serve` is running; cold models or CPU inference can exceed a few minutes."
|
| 35 |
+
)
|
| 36 |
|
| 37 |
|
| 38 |
def _api_base() -> str:
|
| 39 |
+
"""Resolve API base URL. Whitespace-only sidebar input must not win over default (breaks httpx)."""
|
| 40 |
+
raw = st.session_state.get("api_base")
|
| 41 |
+
if raw is None:
|
| 42 |
+
return DEFAULT_API_BASE.rstrip("/")
|
| 43 |
+
s = str(raw).strip()
|
| 44 |
+
if not s:
|
| 45 |
+
return DEFAULT_API_BASE.rstrip("/")
|
| 46 |
+
return s.rstrip("/")
|
| 47 |
|
| 48 |
|
| 49 |
def _client() -> httpx.Client:
|
| 50 |
+
return httpx.Client(base_url=_api_base(), timeout=_http_timeout())
|
| 51 |
|
| 52 |
|
| 53 |
def _fmt_api_error(exc: httpx.HTTPStatusError) -> str:
|
|
|
|
| 71 |
return f"HTTP {exc.response.status_code}"
|
| 72 |
|
| 73 |
|
| 74 |
+
def _post_query_ask(client: httpx.Client, *, question: str, collection_name: str) -> httpx.Response:
|
| 75 |
+
"""Milestone 8 uses POST /query/ask; older servers only expose POST /query."""
|
| 76 |
+
body = {"question": question.strip(), "collection_name": collection_name}
|
| 77 |
+
r = client.post("/query/ask", json=body)
|
| 78 |
+
if r.status_code == 404:
|
| 79 |
+
r = client.post("/query", json=body)
|
| 80 |
+
return r
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _get_audit_logs(client: httpx.Client, *, limit: int, offset: int) -> httpx.Response:
|
| 84 |
+
params = {"limit": limit, "offset": offset}
|
| 85 |
+
r = client.get("/audit/logs", params=params)
|
| 86 |
+
if r.status_code == 404:
|
| 87 |
+
r = client.get("/audit", params=params)
|
| 88 |
+
return r
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _get_audit_event_detail(client: httpx.Client, event_id: str) -> httpx.Response:
|
| 92 |
+
r = client.get(f"/audit/logs/{event_id}")
|
| 93 |
+
if r.status_code == 404:
|
| 94 |
+
r = client.get(f"/audit/{event_id}")
|
| 95 |
+
return r
|
| 96 |
+
|
| 97 |
+
|
| 98 |
def _health_check() -> tuple[bool, str]:
|
| 99 |
try:
|
| 100 |
with _client() as c:
|
|
|
|
| 104 |
return True, str(data)
|
| 105 |
except httpx.ConnectError as e:
|
| 106 |
return False, f"Cannot connect to {_api_base()}: {e}"
|
| 107 |
+
except httpx.TimeoutException as e:
|
| 108 |
+
return False, f"Timed out: {e}. {_fmt_timeout_hint()}"
|
| 109 |
except httpx.HTTPStatusError as e:
|
| 110 |
return False, _fmt_api_error(e)
|
| 111 |
except Exception as e:
|
|
|
|
| 114 |
|
| 115 |
def main() -> None:
|
| 116 |
st.set_page_config(page_title="doc-audi-ai", layout="wide")
|
|
|
|
|
|
|
|
|
|
| 117 |
if "api_base" not in st.session_state:
|
| 118 |
st.session_state.api_base = DEFAULT_API_BASE
|
| 119 |
|
| 120 |
+
st.title("doc-audi-ai")
|
| 121 |
+
st.caption("Ingest, query, and audit via the FastAPI backend.")
|
| 122 |
+
st.caption(f"Requests go to: `{_api_base()}`")
|
| 123 |
+
|
| 124 |
with st.sidebar:
|
| 125 |
st.subheader("Backend")
|
| 126 |
+
st.text_input(
|
| 127 |
+
"API base URL",
|
| 128 |
+
key="api_base",
|
| 129 |
+
placeholder=DEFAULT_API_BASE,
|
| 130 |
+
help=f"Default: {DEFAULT_API_BASE}. Clear the field to use the default.",
|
| 131 |
+
)
|
| 132 |
+
st.caption(
|
| 133 |
+
f"Ask/Summarise wait up to **{int(_http_read_timeout_seconds())}s** per request "
|
| 134 |
+
"(env `DOC_AUDI_HTTP_READ_TIMEOUT`, range 60–3600)."
|
| 135 |
+
)
|
| 136 |
if st.button("Test connection"):
|
| 137 |
ok, msg = _health_check()
|
| 138 |
if ok:
|
|
|
|
| 319 |
st.warning("Enter a question.")
|
| 320 |
else:
|
| 321 |
try:
|
| 322 |
+
with st.spinner(
|
| 323 |
+
"Calling the API (embeddings + LLM can take several minutes on a slow machine; "
|
| 324 |
+
"ensure Ollama is running). Timeout is controlled by DOC_AUDI_HTTP_READ_TIMEOUT…"
|
| 325 |
+
):
|
| 326 |
+
with _client() as c:
|
| 327 |
+
r = _post_query_ask(
|
| 328 |
+
c,
|
| 329 |
+
question=question,
|
| 330 |
+
collection_name=q_col,
|
| 331 |
+
)
|
| 332 |
+
r.raise_for_status()
|
| 333 |
+
ans = r.json()
|
| 334 |
+
msg = ans.get("message") or ""
|
| 335 |
+
st.success(msg if msg else "Request completed.")
|
| 336 |
if ans.get("answer"):
|
| 337 |
st.markdown("### Answer")
|
| 338 |
st.markdown(ans["answer"])
|
| 339 |
+
else:
|
| 340 |
+
st.warning(
|
| 341 |
+
"The API returned no **answer** text. "
|
| 342 |
+
"Check the collection has ingested chunks, LLM env, and expand **Raw response** below."
|
| 343 |
+
)
|
| 344 |
src = ans.get("sources") or []
|
| 345 |
if src:
|
| 346 |
with st.expander(f"Sources ({len(src)})"):
|
| 347 |
st.json(src)
|
| 348 |
+
else:
|
| 349 |
+
st.caption("No sources in this response (empty retrieval or model returned nothing).")
|
| 350 |
+
with st.expander("Raw response (debug)"):
|
| 351 |
+
st.json(ans)
|
| 352 |
except httpx.HTTPStatusError as e:
|
| 353 |
st.error(_fmt_api_error(e))
|
| 354 |
except httpx.ConnectError as e:
|
|
|
|
| 365 |
body: dict[str, Any] = {"collection_name": s_col}
|
| 366 |
if focus.strip():
|
| 367 |
body["focus"] = focus.strip()
|
| 368 |
+
with st.spinner("Calling summarise (can take 1–2 minutes on a cold model)…"):
|
| 369 |
+
with _client() as c:
|
| 370 |
+
r = c.post("/query/summarise", json=body)
|
| 371 |
+
r.raise_for_status()
|
| 372 |
+
ans = r.json()
|
| 373 |
+
msg = ans.get("message") or ""
|
| 374 |
+
st.success(msg if msg else "Request completed.")
|
| 375 |
if ans.get("answer"):
|
| 376 |
st.markdown("### Summary")
|
| 377 |
st.markdown(ans["answer"])
|
| 378 |
+
else:
|
| 379 |
+
st.warning("No summary text in the response; see **Raw response** below.")
|
| 380 |
src = ans.get("sources") or []
|
| 381 |
if src:
|
| 382 |
with st.expander(f"Sources ({len(src)})"):
|
| 383 |
st.json(src)
|
| 384 |
+
with st.expander("Raw response (debug)"):
|
| 385 |
+
st.json(ans)
|
| 386 |
except httpx.HTTPStatusError as e:
|
| 387 |
st.error(_fmt_api_error(e))
|
| 388 |
except httpx.ConnectError as e:
|
| 389 |
st.error(f"Connection failed: {e}")
|
| 390 |
+
except httpx.ReadTimeout as e:
|
| 391 |
+
st.error(f"Read timed out: {e}\n\n{_fmt_timeout_hint()}")
|
| 392 |
+
except httpx.TimeoutException as e:
|
| 393 |
+
st.error(f"Request timed out: {e}\n\n{_fmt_timeout_hint()}")
|
| 394 |
except Exception as e:
|
| 395 |
st.exception(e)
|
| 396 |
|
|
|
|
| 404 |
if st.button("List audit events", key="btn_audit_list"):
|
| 405 |
try:
|
| 406 |
with _client() as c:
|
| 407 |
+
r = _get_audit_logs(
|
| 408 |
+
c,
|
| 409 |
+
limit=int(a_limit),
|
| 410 |
+
offset=int(a_offset),
|
| 411 |
+
)
|
| 412 |
r.raise_for_status()
|
| 413 |
payload = r.json()
|
| 414 |
events = payload.get("events", [])
|
|
|
|
| 437 |
if st.button("Load detail", key="btn_audit_detail") and ev_id:
|
| 438 |
try:
|
| 439 |
with _client() as c:
|
| 440 |
+
r = _get_audit_event_detail(c, ev_id)
|
| 441 |
r.raise_for_status()
|
| 442 |
st.json(r.json())
|
| 443 |
except httpx.HTTPStatusError as e:
|
uv.lock
CHANGED
|
@@ -585,6 +585,7 @@ dependencies = [
|
|
| 585 |
{ name = "langchain-text-splitters" },
|
| 586 |
{ name = "onnxruntime", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
|
| 587 |
{ name = "openai" },
|
|
|
|
| 588 |
{ name = "pydantic-settings" },
|
| 589 |
{ name = "pymupdf" },
|
| 590 |
{ name = "python-multipart" },
|
|
@@ -611,6 +612,7 @@ requires-dist = [
|
|
| 611 |
{ name = "langchain-text-splitters", specifier = "==0.2.0" },
|
| 612 |
{ name = "onnxruntime", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==1.23.2" },
|
| 613 |
{ name = "openai", specifier = "==1.30.1" },
|
|
|
|
| 614 |
{ name = "pydantic-settings", specifier = "==2.3.4" },
|
| 615 |
{ name = "pymupdf", specifier = "==1.24.3" },
|
| 616 |
{ name = "python-multipart", specifier = "==0.0.9" },
|
|
@@ -1655,6 +1657,15 @@ wheels = [
|
|
| 1655 |
{ url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" },
|
| 1656 |
]
|
| 1657 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1658 |
[[package]]
|
| 1659 |
name = "mpmath"
|
| 1660 |
version = "1.3.0"
|
|
@@ -1869,7 +1880,7 @@ name = "nvidia-cudnn-cu12"
|
|
| 1869 |
version = "8.9.2.26"
|
| 1870 |
source = { registry = "https://pypi.org/simple" }
|
| 1871 |
dependencies = [
|
| 1872 |
-
{ name = "nvidia-cublas-cu12" },
|
| 1873 |
]
|
| 1874 |
wheels = [
|
| 1875 |
{ url = "https://files.pythonhosted.org/packages/ff/74/a2e2be7fb83aaedec84f391f082cf765dfb635e7caa9b49065f73e4835d8/nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9", size = 731725872, upload-time = "2023-06-01T19:24:57.328Z" },
|
|
@@ -1896,9 +1907,9 @@ name = "nvidia-cusolver-cu12"
|
|
| 1896 |
version = "11.4.5.107"
|
| 1897 |
source = { registry = "https://pypi.org/simple" }
|
| 1898 |
dependencies = [
|
| 1899 |
-
{ name = "nvidia-cublas-cu12" },
|
| 1900 |
-
{ name = "nvidia-cusparse-cu12" },
|
| 1901 |
-
{ name = "nvidia-nvjitlink-cu12" },
|
| 1902 |
]
|
| 1903 |
wheels = [
|
| 1904 |
{ url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928, upload-time = "2023-04-19T15:51:25.781Z" },
|
|
@@ -1909,7 +1920,7 @@ name = "nvidia-cusparse-cu12"
|
|
| 1909 |
version = "12.1.0.106"
|
| 1910 |
source = { registry = "https://pypi.org/simple" }
|
| 1911 |
dependencies = [
|
| 1912 |
-
{ name = "nvidia-nvjitlink-cu12" },
|
| 1913 |
]
|
| 1914 |
wheels = [
|
| 1915 |
{ url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278, upload-time = "2023-04-19T15:51:49.939Z" },
|
|
@@ -2378,17 +2389,19 @@ wheels = [
|
|
| 2378 |
|
| 2379 |
[[package]]
|
| 2380 |
name = "posthog"
|
| 2381 |
-
version = "
|
| 2382 |
source = { registry = "https://pypi.org/simple" }
|
| 2383 |
dependencies = [
|
| 2384 |
{ name = "backoff" },
|
| 2385 |
{ name = "distro" },
|
|
|
|
|
|
|
| 2386 |
{ name = "requests" },
|
| 2387 |
-
{ name = "
|
| 2388 |
]
|
| 2389 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2390 |
wheels = [
|
| 2391 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2392 |
]
|
| 2393 |
|
| 2394 |
[[package]]
|
|
|
|
| 585 |
{ name = "langchain-text-splitters" },
|
| 586 |
{ name = "onnxruntime", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" },
|
| 587 |
{ name = "openai" },
|
| 588 |
+
{ name = "posthog" },
|
| 589 |
{ name = "pydantic-settings" },
|
| 590 |
{ name = "pymupdf" },
|
| 591 |
{ name = "python-multipart" },
|
|
|
|
| 612 |
{ name = "langchain-text-splitters", specifier = "==0.2.0" },
|
| 613 |
{ name = "onnxruntime", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==1.23.2" },
|
| 614 |
{ name = "openai", specifier = "==1.30.1" },
|
| 615 |
+
{ name = "posthog", specifier = ">=3.7.0,<4" },
|
| 616 |
{ name = "pydantic-settings", specifier = "==2.3.4" },
|
| 617 |
{ name = "pymupdf", specifier = "==1.24.3" },
|
| 618 |
{ name = "python-multipart", specifier = "==0.0.9" },
|
|
|
|
| 1657 |
{ url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" },
|
| 1658 |
]
|
| 1659 |
|
| 1660 |
+
[[package]]
|
| 1661 |
+
name = "monotonic"
|
| 1662 |
+
version = "1.6"
|
| 1663 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1664 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ea/ca/8e91948b782ddfbd194f323e7e7d9ba12e5877addf04fb2bf8fca38e86ac/monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7", size = 7615, upload-time = "2021-08-11T14:37:28.79Z" }
|
| 1665 |
+
wheels = [
|
| 1666 |
+
{ url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154, upload-time = "2021-04-09T21:58:05.122Z" },
|
| 1667 |
+
]
|
| 1668 |
+
|
| 1669 |
[[package]]
|
| 1670 |
name = "mpmath"
|
| 1671 |
version = "1.3.0"
|
|
|
|
| 1880 |
version = "8.9.2.26"
|
| 1881 |
source = { registry = "https://pypi.org/simple" }
|
| 1882 |
dependencies = [
|
| 1883 |
+
{ name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
| 1884 |
]
|
| 1885 |
wheels = [
|
| 1886 |
{ url = "https://files.pythonhosted.org/packages/ff/74/a2e2be7fb83aaedec84f391f082cf765dfb635e7caa9b49065f73e4835d8/nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9", size = 731725872, upload-time = "2023-06-01T19:24:57.328Z" },
|
|
|
|
| 1907 |
version = "11.4.5.107"
|
| 1908 |
source = { registry = "https://pypi.org/simple" }
|
| 1909 |
dependencies = [
|
| 1910 |
+
{ name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
| 1911 |
+
{ name = "nvidia-cusparse-cu12", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
| 1912 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
| 1913 |
]
|
| 1914 |
wheels = [
|
| 1915 |
{ url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928, upload-time = "2023-04-19T15:51:25.781Z" },
|
|
|
|
| 1920 |
version = "12.1.0.106"
|
| 1921 |
source = { registry = "https://pypi.org/simple" }
|
| 1922 |
dependencies = [
|
| 1923 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
| 1924 |
]
|
| 1925 |
wheels = [
|
| 1926 |
{ url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278, upload-time = "2023-04-19T15:51:49.939Z" },
|
|
|
|
| 2389 |
|
| 2390 |
[[package]]
|
| 2391 |
name = "posthog"
|
| 2392 |
+
version = "3.25.0"
|
| 2393 |
source = { registry = "https://pypi.org/simple" }
|
| 2394 |
dependencies = [
|
| 2395 |
{ name = "backoff" },
|
| 2396 |
{ name = "distro" },
|
| 2397 |
+
{ name = "monotonic" },
|
| 2398 |
+
{ name = "python-dateutil" },
|
| 2399 |
{ name = "requests" },
|
| 2400 |
+
{ name = "six" },
|
| 2401 |
]
|
| 2402 |
+
sdist = { url = "https://files.pythonhosted.org/packages/85/a9/ec3bbc23b6f3c23c52e0b5795b1357cca74aa5cfb254213f1e471fef9b4d/posthog-3.25.0.tar.gz", hash = "sha256:9168f3e7a0a5571b6b1065c41b3c171fbc68bfe72c3ac0bfd6e3d2fcdb7df2ca", size = 75968, upload-time = "2025-04-15T21:15:45.552Z" }
|
| 2403 |
wheels = [
|
| 2404 |
+
{ url = "https://files.pythonhosted.org/packages/54/e2/c158366e621562ef224f132e75c1d1c1fce6b078a19f7d8060451a12d4b9/posthog-3.25.0-py2.py3-none-any.whl", hash = "sha256:85db78c13d1ecb11aed06fad53759c4e8fb3633442c2f3d0336bc0ce8a585d30", size = 89115, upload-time = "2025-04-15T21:15:43.934Z" },
|
| 2405 |
]
|
| 2406 |
|
| 2407 |
[[package]]
|