Commit graph

8121 commits

Author SHA1 Message Date
Harshit Jain
456d8f5524 feat: add session_id to have better routing 2026-02-21 18:45:50 +05:30
Zhenting Huang
e0aaedc9d1
feat(semantic-cache): support configurable vector dimensions for Qdrant (#21649)
Add vector_size parameter to QdrantSemanticCache and expose it through
the Cache facade as qdrant_semantic_cache_vector_size. This allows users
to use embedding models with dimensions other than the default 1536,
enabling cheaper/stronger models like Stella (1024d), bge-en-icl (4096d),
voyage, cohere, etc.

The parameter defaults to QDRANT_VECTOR_SIZE (env var or 1536) for
backward compatibility. When creating new collections, the configured
vector_size is used instead of the hardcoded constant.

Closes #9377
2026-02-21 00:51:15 -08:00
Zhenting Huang
647ea3bfcb
feat(proxy): add custom favicon support\n\nAdd ability to configure a custom favicon for the litellm proxy UI.\n\n- Add favicon_url field to UIThemeConfig model\n- Add LITELLM_FAVICON_URL env var support\n- Add /get_favicon endpoint to serve custom favicons\n- Update ThemeContext to dynamically set favicon\n- Add favicon URL input to UI theme settings page\n- Add comprehensive tests\n\nCloses #8323 (#21653) 2026-02-21 00:46:51 -08:00
Zhenting Huang
c845c334b4
fix(proxy): add model_ids param to access group endpoints for precise deployment tagging (#21655)
POST /access_group/new and PUT /access_group/{name}/update now accept an
optional model_ids list that targets specific deployments by their unique
model_id, instead of tagging every deployment that shares a model_name.

When model_ids is provided it takes priority over model_names, giving
API callers the same single-deployment precision that the UI already has
via PATCH /model/{model_id}/update.

Backward compatible: model_names continues to work as before.

Closes #21544
2026-02-21 00:39:57 -08:00
Zhenting Huang
4cc58fe058
fix(bedrock): correct modelInput format for Converse API batch models (#21656) 2026-02-20 22:27:22 -08:00
yuneng-jiang
1fb7320b6d
Merge pull request #21745 from BerriAI/litellm_org_member_email_ui
[Feature] UI - Organization Info: Show member email, AntD tabs, reusable MemberTable
2026-02-20 21:49:27 -08:00
yuneng-jiang
b61537cd0f feat: expose user_email in organization members response
Add user_email field to LiteLLM_OrganizationMembershipTable with a model_validator
that populates it from the nested user object, and update the /organization/info
Prisma query to select user_email from the related user record.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 21:41:53 -08:00
yuneng-jiang
9f3e20510d feat(usage): prefix credential tags and update Usage page banners
- Prefix credential name tags with "Credential: " to distinguish them
  from user-defined tags when litellm_credential_name is injected
- Remove stale "new feature" banners from Organization, Customer, and
  A2A usage views
- Add closable info banner to Tag usage view noting that reusable
  credentials are automatically tracked and appear as "Credential: <name>"
2026-02-20 20:55:57 -08:00
yuneng-jiang
b7efca22a2
Merge pull request #21723 from BerriAI/litellm_fix_cost_token_masking
[Fix] Model Info: input_cost_per_token masked in UI
2026-02-20 20:36:22 -08:00
Krish Dholakia
e8d0afd7cb
Guardrail - competitor name blocker (#21719)
* feat: add competitor name blocker guardrail

* fix: fix batch test endpoint for compliance playground

* fix(airline.py): add list of all known airlines to airline competitor name detector

prevent competitor discussion on company chatbot

* feat: ui tweaks for prod
2026-02-20 18:52:40 -08:00
yuneng-jiang
ac7446bb76 fix: don't mask cost_per_token fields in SensitiveDataMasker
Fields like input_cost_per_token contain "token" as a key segment,
which incorrectly matched the sensitive patterns list and caused values
like 3.6e-06 to be displayed as "3.60*******e-06" in the model info UI.

Add a non_sensitive_overrides set (defaulting to {"cost"}) so that any
key containing "cost" as a segment is never masked, regardless of other
matching patterns.
2026-02-20 18:13:27 -08:00
Ishaan Jaff
e0129710c8
fix(proxy): self-heal Prisma connection for auth and runtime (#21706)
* fix(proxy): add prisma reconnect primitive and db watchdog

* fix(proxy): start and stop prisma watchdog in lifecycle

* fix(auth): retry key lookup once after prisma reconnect

* test(proxy): add prisma self-heal watchdog coverage

* test(auth): cover reconnect-once behavior for key lookup

* refactor(auth): extract db reconnect helper and remove inline import

* fix(proxy): apply reconnect cooldown after attempt and add auth timeout path

* fix(auth): bound reconnect latency on key lookup path

* test(auth): assert reconnect timeout argument in key lookup

* test(proxy): verify reconnect cooldown timestamp set after attempt

* fix(proxy): harden prisma reconnect cycle semantics

* test(proxy): cover watchdog reconnect + timeout budget

* fix(proxy): bound watchdog probe and reconnect paths

* test(proxy): cover watchdog timeout and probe behavior

* fix(proxy): narrow prisma db connection error classification

* fix(proxy): add auth reconnect lock timeout budget

* fix(auth): pass lock timeout for db reconnect retries

* test(proxy): cover narrow prisma connection error detection

* test(proxy): add reconnect lock-timeout behavior coverage

* test(auth): assert reconnect lock timeout argument

* fix(proxy): avoid lock leak race in reconnect lock timeout path

* test(proxy): cover reconnect lock-timeout race cleanup
2026-02-20 18:11:36 -08:00
Ishaan Jaff
5246e64b98
Add topic blocker guardrail with keyword and embedding implementations (#21713)
* Add keyword-based topic blocker implementation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add embedding-based topic blocker using MiniLM

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add topic blocker package init with exports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add synthetic engine eval set (34 cases)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment questions eval set (207 cases)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add engine eval synthetic policy config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add engine keyword blocker eval results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment keyword blocker eval results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment embedding blocker eval results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment embedding MiniLM eval results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment embedding MPNet eval results (historical)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add investment TF-IDF eval results (historical)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add unified eval runner with confusion matrix reporting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add benchmarks comparison table in markdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Clean up topic blocker: remove unused blockers, add phrase_patterns to content filter

- Remove embedding_blocker.py, api_embedding_blocker.py, nli_blocker.py,
  tfidf_blocker.py, onnx_blocker.py (heavy deps not in Docker, inferior accuracy)
- Remove airline_off_topic_restriction policy template and its test
- Fix __init__.py to only export DeniedTopic and TopicBlocker (no eager import crash)
- Add phrase_patterns support to ContentFilterGuardrail for regex-based paraphrase detection
- Rewrite denied_financial_advice.yaml with conditional matching (identifier + block word),
  always-block keywords, phrase patterns, and exception phrases
- Clean up test_eval.py: only keyword blocker + content filter tests remain (no network calls)
- All 207 eval cases pass at 100% F1, 0 FP, 0 FN, <0.1ms latency

Addresses all Greptile review comments:
- Eager import crash (embedding deps) → fixed
- Undeclared dependencies → fixed (files deleted)
- lru_cache memory leak → fixed (file deleted)
- Real network calls in tests → fixed (embedding tests removed)
- Unused Dict import → already fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add LLM-as-judge eval and update BENCHMARKS.md

- Add TestInvestmentLlmJudgeGpt4oMini and TestInvestmentLlmJudgeClaude
  test classes that use litellm.completion() to classify messages
- System prompt instructs LLM to act as airline chatbot content moderator
- Tests skip gracefully when API keys aren't set
- Update BENCHMARKS.md with production results table, historical comparison,
  and instructions for running LLM judge evals

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Move evals and benchmarks to guardrail_benchmarks folder

Move eval runner, eval data (JSONL), and results from
tests/test_litellm/.../topic_blocker/ into the guardrail implementation
folder at litellm/.../litellm_content_filter/guardrail_benchmarks/.

This keeps benchmarks co-located with the guardrail code they test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove standalone topic_blocker package, consolidate into content_filter

The standalone keyword_blocker.py was redundant with content_filter.py +
denied_financial_advice.yaml. Removed the entire topic_blocker/ package,
engine eval files, and old keyword blocker results. Simplified test_eval.py
to only test ContentFilter + LLM judge baselines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix compliance playground batch scoring bug, add display_name support

The compliance playground was sending all texts in a single batch API call,
but the content filter raises HTTPException on the first blocked text. This
caused a single blocked/allowed result to be applied to all rows, producing
incorrect scores (e.g. 41% instead of 100%). Fix by sending each text
individually to get per-text results with progressive UI updates.

Also add display_name field support for category YAML files so
denied_financial_advice shows as "Denied Financial / Investment Advice"
in the UI dropdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add block_investment CSV eval set and update benchmark result JSON

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* address greptile review feedback (greploop iteration 1)

Fix stale test path in denied_financial_advice.yaml comment.
Other comments were on files already deleted in prior commits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 18:02:04 -08:00
yuneng-jiang
5084fe9538
Merge pull request #21715 from BerriAI/litellm_credential_tag_usage
[Feature] Inject Credential Name as Tag for Usage Page Filtering
2026-02-20 17:51:28 -08:00
ryan-crabbe
dbff558d96
Merge pull request #21717 from BerriAI/litellm_fix_redis_connection_pool_leaks
fix: redis connection pool reliability
2026-02-20 17:42:48 -08:00
Sameer Kankute
36fd14357c FIx: replace deprecated claude-3-7-sonnet-20250219 with claude-4-sonnet-20250514 2026-02-20 17:27:59 -08:00
yuneng-jiang
e6b9bef949 [Fix] Fix flaky tests: spend logs metadata keys, proxy CLI isolation, Redis TTL uniqueness
- Add new SpendLogsMetadata keys to ignored_keys in spend logs tests
  (regression from ccecc10c82 which intentionally includes all keys)
- Mock PrismaManager.setup_database and should_update_prisma_schema in
  proxy CLI tests to prevent real DB migrations from running in CI
- Use CliRunner(mix_stderr=False) to fix Click stream lifecycle issues
- Use unique UUID suffix for Redis TTL test keys to avoid stale state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-20 17:26:44 -08:00
yuneng-jiang
e08989dd8f Fix credential name tag injection by moving to Router level
The previous approach tried to inject litellm_credential_name as a tag
in get_standard_logging_object_payload, but the credential name was never
available in litellm_params because the Logging object is created by the
proxy BEFORE the Router selects a deployment. The credential name only
exists in the deployment's litellm_params, which is resolved later.

This fix injects the credential name as a tag in
Router._update_kwargs_with_deployment(), right alongside the existing
deployment-level tags mechanism. This ensures the credential name flows
through the normal metadata.tags pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 17:16:36 -08:00
Ryan Crabbe
d6c6d12549 rename test file to test_redis_connection_pool.py 2026-02-20 17:10:08 -08:00
Ryan Crabbe
6931fea929 fix: close leaked Redis connection pools on cache eviction and disconnect
- RC1: Override _remove_key() in LLMClientCache to schedule aclose() on
  evicted async clients instead of relying on GC
- RC2: Use passed connection_pool for URL configs instead of creating an
  orphaned pool via from_url()
- RC3: Pass max_connections through to BlockingConnectionPool.from_url()
  for URL configs, with input validation for invalid values
- RC5: Close sync redis_client in disconnect() with try/except guard
2026-02-20 17:09:32 -08:00
yuneng-jiang
8c5d48348c Inject credential name as tag in standard logging payload for Usage page filtering
When a model has a litellm_credential_name, append it to request_tags during
logging so it flows into DailyTagSpend and becomes filterable in the Usage page.

- Add litellm_credential_name to _OPTIONAL_KWARGS_KEYS so it survives into
  litellm_params during get_litellm_params() filtering
- Read credential name from litellm_params in get_standard_logging_object_payload()
  and append to request_tags if not already present

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:44:34 -08:00
Ishaan Jaff
d11832bfad
fix(responses): eliminate per-chunk thread spawning in async streaming path (#21709)
* fix(responses): fix O(n²) CPU overhead in reasoning streaming path

stream_chunk_builder was called on every reasoning chunk, rebuilding the
entire response from all collected chunks each time. Replace with
incremental accumulation of reasoning_content parts, only joining at
reasoning end.

* fix(responses): eliminate per-chunk thread spawning in async streaming path

_process_chunk() called run_async_function() on every SSE chunk, which
when invoked from an async context spawns a thread + event loop per call.

Move the hook call out of _process_chunk into the callers: async __anext__
directly awaits it, sync __next__ uses run_async_function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* perf: reduce responses streaming CPU for text-only streams

* fix(test): replace deprecated claude-3-7-sonnet-latest in responses API test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): replace deprecated claude-3-7-sonnet-latest in tool result fix test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): replace deprecated claude-3-7-sonnet-latest in tool result empty call_id test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:26:23 -08:00
yuneng-jiang
f7fb4a270f Merge remote-tracking branch 'origin' into litellm_usage_perf_fix 2026-02-20 15:37:56 -08:00
yuneng-jiang
8f92e448ae
Merge pull request #21637 from BerriAI/litellm_fix_get_image_cache_bypass
[Fix] /get_image Ignores UI_LOGO_PATH When cached_logo.jpg Exists
2026-02-20 15:20:05 -08:00
yuneng-jiang
de1517411f [Feature] UI - Logs: Show retry count for requests
Add attempted_retries and max_retries fields to SpendLogsMetadata so the
Logs page can display how many retries occurred for each request. The
router now injects retry tracking metadata before each make_call, which
flows through the logging pipeline into the spend logs metadata JSON.

The UI shows "Not Retried" when the first attempt succeeded, and
"N / M" (attempted / max) when retries occurred. The field is hidden
for requests that did not go through the router.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:51:20 -08:00
Chesars
0f20976efa fix(types): remove StreamingChoices from ModelResponse, use ModelResponseStream
ModelResponse.choices was typed as List[Union[Choices, StreamingChoices]] which
caused Pydantic serialization warnings and false linting errors. Now that
ModelResponseStream exists for streaming, narrow ModelResponse.choices to
List[Choices] and migrate all ModelResponse(stream=True) call sites to use
ModelResponseStream() instead.
2026-02-20 17:47:42 -03:00
An Tang
08a561f78a fix env var leakage 2026-02-20 11:52:56 -08:00
An Tang
be885ad236 consistently passing region and endpoint args into explicit credentials irsa 2026-02-20 10:57:58 -08:00
An Tang
782df9283f Parametrize ambient-credentials test for no opts, region_name, and aws_sts_endpoint 2026-02-20 10:57:57 -08:00
An Tang
5584f2d612 update tests to include case with aws_region_name for _auth_with_aws_role 2026-02-20 10:57:57 -08:00
michelligabriele
a395a25705
fix(cost-calc): use per-image pricing for Bedrock multimodal embeddings (#21646)
Bedrock multimodal embedding models (Titan and Nova) were being costed
using the per-token text rate instead of the correct flat per-image rate
($0.00006/image). The pricing data was correct but never applied because
image_count was never populated in prompt_tokens_details.

Pass batch_data to Titan/Nova response transformers so they can count
image inputs and set PromptTokensDetailsWrapper(image_count=N) on Usage,
mirroring the existing Vertex AI pattern from PR #9623. Also fix the
text_tokens fallback in generic_cost_per_token to not override
text_tokens=0 when image_count > 0 (image-only requests).
2026-02-20 08:51:21 -08:00
michelligabriele
d001fe9a16
fix(model-pricing): add missing fireworks_ai model pricing for glm-4p7, minimax-m2p1, kimi-k2p5 (#21642)
* fix(model-pricing): add missing fireworks_ai model pricing for glm-4p7, minimax-m2p1, kimi-k2p5

Fireworks AI models called via short-form (fireworks_ai/<model>) were
reporting $0.00 cost because the pricing JSON lacked short-form entries.
The lookup fell through to the fireworks-ai-default bucket which has
zero cost.

Added 5 new entries to model_prices_and_context_window.json:
- fireworks_ai/accounts/fireworks/models/glm-4p7 (new long-form)
- fireworks_ai/accounts/fireworks/models/minimax-m2p1 (new long-form)
- fireworks_ai/glm-4p7 (new short-form)
- fireworks_ai/minimax-m2p1 (new short-form)
- fireworks_ai/kimi-k2p5 (new short-form; long-form already existed)

Pricing sourced from fireworks.ai model pages and pricing page.

* add cache_read_input_token_cost to kimi-k2p5 long-form entry for consistency
2026-02-20 08:31:52 -08:00
Julio Quinteros Pro
87577eb05c fix(tests): correct medium reasoning_effort assertion for gemini-3-pro-preview
For gemini-3-pro-preview (not gemini-3-flash or gemini-3.1-pro-preview),
reasoning_effort="medium" maps to thinkingLevel="high" because the "medium"
thinking level is not available on that model variant.

Both test_reasoning_effort_maps_to_thinking_level_gemini_3 and
test_reasoning_effort_dict_format_gemini_3 had the correct comment
("medium -> high") but the wrong assertion (== "medium"). Fixed to
match the production code and the comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 12:09:26 -03:00
Julio Quinteros Pro
963e0eabcb fix(tests): update test_max_effort_rejected_for_opus_45 regex to match new error message
The production error message was expanded when Sonnet 4.6 was also added as
a supported model for effort='max'. The test's match regex still referenced
the old "Claude Opus 4.6"-only message; update it to match the new
"Claude 4.6 models" wording.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 11:07:52 -03:00
Sameer Kankute
b62db9fcc9
Merge pull request #21663 from BerriAI/litellm_add_reasoning_support_config
[Feat] Add reasoning support via config
2026-02-20 18:17:09 +05:30
Sameer Kankute
164734fa45
Merge pull request #21662 from BerriAI/litellm_mcp_openapi_spec
[Fix]Add mcp via openapi spec
2026-02-20 18:16:30 +05:30
Sameer Kankute
9e0bd46c28
Merge pull request #21659 from BerriAI/litellm_fix_converse_disable_tool
Fix mapping of parallel_tool_calls for bedrock converse
2026-02-20 18:16:10 +05:30
Sameer Kankute
5d8f2196d1 Add test case 2026-02-20 16:22:44 +05:30
Sameer Kankute
ba74ee5a31 Add test for base url extraction and migration 2026-02-20 13:47:44 +05:30
Sameer Kankute
b579f56ca3 Fix mapping of parallel_tool_calls for bedrock converse 2026-02-20 12:27:41 +05:30
Sameer Kankute
cdb4917185 Fix: api_base is required. Unable to determine the correct api_base for the request 2026-02-20 12:09:25 +05:30
yuneng-jiang
6bfab8acd4 address greptile review feedback (greploop iteration 2)
Reset logo_path to default_logo when custom UI_LOGO_PATH file doesn't
exist, so the else branch at the bottom of get_image serves the default
logo instead of the non-existent custom path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 20:25:00 -08:00
yuneng-jiang
145efe2267 address greptile review feedback (greploop iteration 1)
Add os.path.exists check before serving custom local logo so that a
non-existent UI_LOGO_PATH gracefully falls through to the cache/default
instead of causing a FileResponse error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 20:09:42 -08:00
Sameer Kankute
fb75a7130f
Merge pull request #21630 from Chesars/fix/empty-system-message-anthropic
fix(anthropic): empty system messages in translate_system_message
2026-02-20 09:32:50 +05:30
Sameer Kankute
b405f64688
Merge pull request #21598 from jtsaw/fix/anthropic-sonnet-4-6-reasoning-effort
support reasoning and effort parameters on sonnet 4.6
2026-02-20 08:24:29 +05:30
yuneng-jiang
a8026154ab [Fix] /get_image returns stale cached logo instead of custom UI_LOGO_PATH
The /get_image endpoint checked for cached_logo.jpg before reading the
UI_LOGO_PATH env var, so a pre-existing cache (e.g. baked into the base
Docker image) would always be served, ignoring the user's custom logo.

Move the UI_LOGO_PATH read before the cache check and serve local file
paths directly, bypassing the cache. The cache optimization is preserved
for HTTP URLs and the default logo where it is actually needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 18:32:48 -08:00
Chesars
56386969b5 fix(anthropic): remove empty system messages from message list
Empty system messages were skipped for Anthropic's system param but
not removed from the messages list, causing BadRequestError when
anthropic_messages_pt encountered the unsupported "system" role.

Fixes #21622
2026-02-19 21:46:17 -03:00
Chesars
3564b8d83b fix(types): suppress Pydantic serialization warnings on ModelResponse choices
Pydantic v2's Union serializer for `List[Union[Choices, StreamingChoices]]`
tries both branches when serializing, emitting spurious
`PydanticSerializationUnexpectedValue` warnings (field count mismatch on
`Message` and type mismatch `Expected StreamingChoices but got Choices`).

Add a `WrapSerializer` on the `choices` field that serializes each item
individually via its own `model_dump()`, bypassing the Union dispatch
entirely while correctly propagating `exclude_none`, `exclude_unset`,
and `exclude_defaults` from the parent serialization context.
2026-02-19 21:43:19 -03:00
yuneng-jiang
c7fe400d4d [Fix] Service account visibility for team members
Regular team members could not see service accounts belonging to their
team. Additionally, former team members could still see service accounts
they created via the created_by filter after leaving the team.

- Add get_member_team_ids() to retrieve all teams a user belongs to
- Regular members now see team service accounts (user_id=NULL) but not
  other members' personal keys
- Scope created_by filter to the user's current team memberships
- Add 7 unit tests covering the new visibility rules
2026-02-19 16:29:43 -08:00
yuneng-jiang
e2e698944a perf: use SQL GROUP BY for aggregated daily activity endpoints
Replace find_many + Python-side aggregation with a single SQL GROUP BY
query via query_raw in get_daily_activity_aggregated. This collapses
rows across entities (users/teams/orgs) in the database, reducing ~150k
rows to ~2-3k grouped rows before transfer to Python.

Also adds composite indexes (entity_id, date) to all 6 daily spend
tables for faster filtered queries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-19 14:36:28 -08:00