-
Notifications
You must be signed in to change notification settings - Fork 0
Browser edge compute optimization #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
26b9769
861eea9
83dca61
440383e
a23ae21
9dd54fa
57b7cc8
a408caf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,14 @@ class GridifyFlightServer: | |
| Each dataset is published under a Flight ``descriptor`` whose command is the | ||
| dataset name (e.g. ``b"devices"``). Consumers call ``get_flight_info`` then | ||
| ``do_get`` to stream the columnar data with zero JSON serialization. | ||
|
|
||
| Flight SQL support | ||
| ------------------ | ||
| When a client sends a SQL statement as the ticket (or as a Flight | ||
| descriptor command starting with ``b"SQL "``), the server executes the | ||
| query against DuckDB and streams the resulting record batch back as an | ||
| Arrow stream. This lets any Arrow Flight SQL client query the telemetry | ||
| catalog directly without hitting the HTTP REST layer. | ||
| """ | ||
|
|
||
| def __init__( | ||
|
|
@@ -156,6 +164,22 @@ def list_flights(self, context, criteria): # noqa: D401, ANN001 | |
| ) | ||
|
|
||
| def get_flight_info(self, context, descriptor): # noqa: ANN001 | ||
| command = descriptor.command | ||
| if command and command.startswith(b"SQL "): | ||
| sql = command[4:].decode("utf-8", errors="replace").strip() | ||
| if not sql: | ||
| raise ValueError("Empty SQL command") | ||
| from app.services.duckdb_service import get_duckdb_service | ||
| db = get_duckdb_service() | ||
| table = db.query_to_arrow(sql) | ||
|
Comment on lines
+168
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this Flight server is launched, any client that can reach Prompt To Fix With AIThis is a comment left during a code review.
Path: backend/app/services/arrow_service.py
Line: 168-174
Comment:
**Flight Descriptor Executes Raw SQL**
When this Flight server is launched, any client that can reach `0.0.0.0:8815` can send a descriptor command starting with `SQL ` and the remainder is executed on the shared DuckDB connection. This bypasses the FastAPI guardrails and can run DDL or DML, including against attached PostgreSQL tables.
How can I resolve this? If you propose a fix, please make it concise. |
||
| endpoint = _flight.FlightEndpoint( | ||
| command, | ||
| [_flight.Location.for_grpc_tcp("0.0.0.0", 8815)], | ||
| ) | ||
|
Comment on lines
+175
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return _flight.FlightInfo( | ||
| table.schema, descriptor, [endpoint], table.num_rows, -1 | ||
| ) | ||
|
|
||
| ds = descriptor.command.decode() if descriptor.command else "devices" | ||
| table = telemetry_to_table(provider(), ds) | ||
| endpoint = _flight.FlightEndpoint( | ||
|
|
@@ -167,12 +191,26 @@ def get_flight_info(self, context, descriptor): # noqa: ANN001 | |
| ) | ||
|
|
||
| def do_get(self, context, ticket): # noqa: ANN001 | ||
| ds = ticket.ticket.decode() | ||
| ticket_str = ticket.ticket.decode("utf-8", errors="replace") | ||
|
|
||
| if ticket_str.startswith("SQL "): | ||
| sql = ticket_str[4:].strip() | ||
| if not sql: | ||
| raise ValueError("Empty SQL ticket") | ||
| from app.services.duckdb_service import get_duckdb_service | ||
| db = get_duckdb_service() | ||
| table = db.query_to_arrow(sql) | ||
|
Comment on lines
+196
to
+202
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A client can send a Flight ticket beginning with Prompt To Fix With AIThis is a comment left during a code review.
Path: backend/app/services/arrow_service.py
Line: 196-202
Comment:
**Flight Ticket Executes Raw SQL**
A client can send a Flight ticket beginning with `SQL ` and this path sends the rest directly to `db.query_to_arrow(sql)`. Because tickets are accepted without auth, statement checks, or a read-only guard, reachable clients can mutate or drop data through the Flight port.
How can I resolve this? If you propose a fix, please make it concise. |
||
| return _flight.RecordBatchStream(table) | ||
|
|
||
| ds = ticket_str | ||
| if ds not in DATASETS: | ||
| raise ValueError( | ||
| f"Unknown dataset {ds!r}; expected one of {DATASETS!r}" | ||
| ) | ||
| table = telemetry_to_table(provider(), ds) | ||
| return _flight.RecordBatchStream(table) | ||
|
|
||
| def do_put(self, context, descriptor, reader): # noqa: ANN001 | ||
| # Read-and-discard: this server is a read-only publisher. | ||
| for _ in reader: | ||
| pass | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||||||||||||||||||||||||||||||||||
| """LLM gateway abstraction for Portkey / Langfuse. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Instead of managing fallback logic directly inside the core application code, | ||||||||||||||||||||||||||||||||||||||
| all LiteLLM calls can be routed through a standalone gateway. This cleanly | ||||||||||||||||||||||||||||||||||||||
| offloads tracking, automatic retries, budget tracking, and fallbacks away from | ||||||||||||||||||||||||||||||||||||||
| the central FastAPI code. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Supported providers: | ||||||||||||||||||||||||||||||||||||||
| - ``litellm`` (default): direct LiteLLM calls, no gateway overhead. | ||||||||||||||||||||||||||||||||||||||
| - ``portkey``: routes through Portkey's virtual keys and gateway. | ||||||||||||||||||||||||||||||||||||||
| - ``langfuse``: routes through Langfuse's proxy for observability. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from typing import Any, Callable, Dict, List, Optional | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from app.config import settings | ||||||||||||||||||||||||||||||||||||||
| from app.utils.logger import setup_logger | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| logger = setup_logger(__name__) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| try: # LiteLLM is optional at import time so tests run without it. | ||||||||||||||||||||||||||||||||||||||
| import litellm # type: ignore | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| LITELLM_AVAILABLE = True | ||||||||||||||||||||||||||||||||||||||
| except Exception: # pragma: no cover | ||||||||||||||||||||||||||||||||||||||
| LITELLM_AVAILABLE = False | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| class LLMGateway: | ||||||||||||||||||||||||||||||||||||||
| """Routes LLM completions through an optional gateway provider. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| When no gateway is configured, calls fall back to plain LiteLLM so the | ||||||||||||||||||||||||||||||||||||||
| rest of the code path is unchanged. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||
| self.provider = settings.LLM_GATEWAY_PROVIDER.lower() | ||||||||||||||||||||||||||||||||||||||
| self._completion: Callable[..., Any] = self._build_completion_fn() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def _build_completion_fn(self) -> Callable[..., Any]: | ||||||||||||||||||||||||||||||||||||||
| if not LITELLM_AVAILABLE: | ||||||||||||||||||||||||||||||||||||||
| logger.warning("LiteLLM not installed; gateway unavailable") | ||||||||||||||||||||||||||||||||||||||
| return lambda **kwargs: (_ for _ in ()).throw( | ||||||||||||||||||||||||||||||||||||||
| RuntimeError("LiteLLM is not installed") | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if self.provider == "portkey" and settings.LLM_GATEWAY_PORTKEY_API_KEY: | ||||||||||||||||||||||||||||||||||||||
| logger.info("Routing LLM calls through Portkey gateway") | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| import portkey_ai # type: ignore | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| portkey_ai.api_key = settings.LLM_GATEWAY_PORTKEY_API_KEY | ||||||||||||||||||||||||||||||||||||||
| if settings.LLM_GATEWAY_BASE_URL: | ||||||||||||||||||||||||||||||||||||||
| portkey_ai.api_base = settings.LLM_GATEWAY_BASE_URL | ||||||||||||||||||||||||||||||||||||||
| return portkey_ai.Completions.acompletions | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Portkey branch stores Prompt To Fix With AIThis is a comment left during a code review.
Path: backend/app/services/llm_gateway.py
Line: 52-57
Comment:
**Portkey Returns Unawaited Coroutine**
The Portkey branch stores `acompletions` as the completion function, but `complete()` calls it synchronously and `LLMService` parses the returned object immediately. With `LLM_GATEWAY_PROVIDER=portkey`, this can hand `_extract_content` a coroutine instead of a response, producing empty completions rather than a real model answer.
How can I resolve this? If you propose a fix, please make it concise.
Comment on lines
+49
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| except Exception as exc: # pragma: no cover - optional dep | ||||||||||||||||||||||||||||||||||||||
| logger.warning("Portkey gateway unavailable (%s); using LiteLLM", exc) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if self.provider == "langfuse" and ( | ||||||||||||||||||||||||||||||||||||||
| settings.LLM_GATEWAY_LANGFUSE_PUBLIC_KEY | ||||||||||||||||||||||||||||||||||||||
| and settings.LLM_GATEWAY_LANGFUSE_SECRET_KEY | ||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||
| logger.info("Routing LLM calls through Langfuse gateway") | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| from langfuse.openai import OpenAI # type: ignore | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| client = OpenAI( | ||||||||||||||||||||||||||||||||||||||
| base_url=settings.LLM_GATEWAY_BASE_URL or settings.LLM_GATEWAY_LANGFUSE_HOST, | ||||||||||||||||||||||||||||||||||||||
| api_key=settings.LLM_GATEWAY_LANGFUSE_SECRET_KEY, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| return lambda **kwargs: client.chat.completions.create(**kwargs).model_dump() | ||||||||||||||||||||||||||||||||||||||
| except Exception as exc: # pragma: no cover - optional dep | ||||||||||||||||||||||||||||||||||||||
| logger.warning("Langfuse gateway unavailable (%s); using LiteLLM", exc) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| logger.debug("No LLM gateway configured; using direct LiteLLM") | ||||||||||||||||||||||||||||||||||||||
| return litellm.completion | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def complete(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||
| """Run a completion through the configured gateway. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||
| kwargs: Keyword arguments forwarded to the underlying client | ||||||||||||||||||||||||||||||||||||||
| (model, messages, temperature, etc.). | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||
| OpenAI-style response dict. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| return self._completion(**kwargs) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def is_available(self) -> bool: | ||||||||||||||||||||||||||||||||||||||
| return LITELLM_AVAILABLE | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| _gateway: Optional[LLMGateway] = None | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def get_llm_gateway() -> LLMGateway: | ||||||||||||||||||||||||||||||||||||||
| """Return the shared :class:`LLMGateway` singleton.""" | ||||||||||||||||||||||||||||||||||||||
| global _gateway | ||||||||||||||||||||||||||||||||||||||
| if _gateway is None: | ||||||||||||||||||||||||||||||||||||||
| _gateway = LLMGateway() | ||||||||||||||||||||||||||||||||||||||
| return _gateway | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Celery/Kombu and standard
redis-pydo not support thevalkey://URL scheme out of the box. Attempting to usevalkey://will cause Celery to fail to start with a scheme parsing error. Since Valkey is fully Redis-compatible and speaks the RESP protocol, use theredis://scheme instead.