Files
wehub-resource-sync c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:02:24 +08:00

125 lines
4.0 KiB
Python

"""Per-session token / cost tracking via a ContextVar scope.
Call sites that know the active session_id wrap their work in
``track_session_usage(session_id, user_id)``. Inside that scope,
``LLMGateway.acreate_structured_output`` (and any other caller that
opts in) calls ``record_llm_call`` after each LLM completion. The
tracker accumulates into the ``SessionRecord`` row.
Token counts are approximate — we don't currently extract
``response.usage`` from the litellm/instructor client (requires
changes deeper in the stack). A ~chars/4 heuristic is close enough
for the dashboard's "are we spending?" question without plumbing
upstream.
"""
from contextlib import asynccontextmanager
from contextvars import ContextVar
from typing import Optional
from uuid import UUID as UUIDType
from cognee.shared.logging_utils import get_logger
logger = get_logger("session_usage")
# (session_id, user_id) when active, else None.
_active_session: ContextVar[Optional[tuple[str, UUIDType]]] = ContextVar(
"cognee_session_usage_target", default=None
)
@asynccontextmanager
async def track_session_usage(session_id: str, user_id: UUIDType):
"""Bind a session as the target for LLM-usage accumulation inside this scope."""
if not session_id or user_id is None:
yield
return
token = _active_session.set((session_id, user_id))
try:
yield
finally:
_active_session.reset(token)
def _estimate_tokens(text: str) -> int:
"""Very rough char-based estimate. Good enough for dashboard aggregates."""
if not text:
return 0
return max(1, len(text) // 4)
# Minimal per-model pricing table. Conservative and incomplete —
# unrecognized models cost $0.
# USD per 1M tokens: (input, output)
_PRICING_PER_M_TOKENS = {
"gpt-4o": (2.50, 10.00),
"gpt-4o-mini": (0.15, 0.60),
"gpt-4-turbo": (10.00, 30.00),
"gpt-4": (30.00, 60.00),
"gpt-3.5-turbo": (0.50, 1.50),
"claude-3-5-sonnet": (3.00, 15.00),
"claude-3-opus": (15.00, 75.00),
"claude-3-haiku": (0.25, 1.25),
"gemini-1.5-pro": (1.25, 5.00),
"gemini-1.5-flash": (0.075, 0.30),
}
# Longest prefix first so specific models (e.g. ``gpt-4o-mini``) win
# over their more general family (``gpt-4o``). Computed once at import.
_PRICING_SORTED = sorted(_PRICING_PER_M_TOKENS.items(), key=lambda kv: -len(kv[0]))
def _estimate_cost_usd(model: Optional[str], tokens_in: int, tokens_out: int) -> float:
if not model:
return 0.0
# Normalize: strip provider prefix ("openai/gpt-4o" → "gpt-4o"), drop date suffix.
key = model.split("/")[-1].lower()
for base, (pin, pout) in _PRICING_SORTED:
if key.startswith(base):
return (tokens_in / 1_000_000) * pin + (tokens_out / 1_000_000) * pout
return 0.0
async def record_llm_call(
*,
input_text: str,
output_text: str,
model: Optional[str] = None,
tokens_in_override: Optional[int] = None,
tokens_out_override: Optional[int] = None,
) -> None:
"""If there's an active session, accumulate this call's usage into it.
Pass ``tokens_in_override`` / ``tokens_out_override`` when the
caller has exact counts from ``response.usage``; otherwise the
char-based estimate is used.
"""
target = _active_session.get()
if target is None:
return
session_id, user_id = target
tokens_in = (
tokens_in_override if tokens_in_override is not None else _estimate_tokens(input_text)
)
tokens_out = (
tokens_out_override if tokens_out_override is not None else _estimate_tokens(output_text)
)
cost = _estimate_cost_usd(model, tokens_in, tokens_out)
try:
from cognee.modules.session_lifecycle.metrics import accumulate_usage
await accumulate_usage(
session_id=session_id,
user_id=user_id,
tokens_in=tokens_in,
tokens_out=tokens_out,
cost_usd=cost,
model=model,
)
except Exception as exc:
logger.debug("record_llm_call: accumulate failed (%s)", exc)