Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions application/alembic/versions/0015_token_usage_model_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""0015 token_usage model_id — record which model each call ran under.

Adds ``token_usage.model_id`` (canonical id: catalog name for built-ins,
UUID for BYOM) so analytics can group spend by model. The partial index
mirrors ``token_usage_request_id_idx`` — it excludes the NULL rows that
pre-date the column.

Revision ID: 0015_token_usage_model_id
Revises: 0014_device_token_hash_index
"""

from typing import Sequence, Union

from alembic import op


revision: str = "0015_token_usage_model_id"
down_revision: Union[str, None] = "0014_device_token_hash_index"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.execute("ALTER TABLE token_usage ADD COLUMN model_id TEXT;")
op.execute(
'CREATE INDEX token_usage_model_ts_idx '
'ON token_usage (model_id, "timestamp" DESC) '
"WHERE model_id IS NOT NULL;"
)


def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS token_usage_model_ts_idx;")
op.execute("ALTER TABLE token_usage DROP COLUMN IF EXISTS model_id;")
Comment on lines +1 to +34
1 change: 1 addition & 0 deletions application/api/user/scheduler_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ def execute_scheduled_run_body(run_id: str, celery_task_id: Optional[str]) -> Di
agent_id=str(agent_id_raw) if agent_id_raw else None,
source="schedule",
request_id=str(run_id),
model_id=outcome.get("model_id"),
)
except Exception:
logger.exception(
Expand Down
6 changes: 5 additions & 1 deletion application/llm/llm_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def create_llm(

# Forward model_user_id so backup/fallback resolves under the
# owner's scope on shared-agent dispatch.
return plugin.llm_class(
llm = plugin.llm_class(
api_key,
user_api_key,
decoded_token=decoded_token,
Expand All @@ -124,3 +124,7 @@ def create_llm(
*args,
**kwargs,
)
# llm.model_id is the upstream name (BYOM resolves it above); stamp
# the canonical id (UUID for BYOM) separately for token_usage.
llm._canonical_model_id = model_id
return llm
3 changes: 3 additions & 0 deletions application/storage/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
# N rows) count as a single request via DISTINCT in the repository
# query. NULL on side-channel sources by design.
Column("request_id", Text),
# Added in ``0015_token_usage_model_id``. Canonical model id (catalog
# name for built-ins, UUID for BYOM); NULL on un-backfilled rows.
Column("model_id", Text),
)

user_logs_table = Table(
Expand Down
6 changes: 4 additions & 2 deletions application/storage/db/repositories/token_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def insert(
generated_tokens: int = 0,
source: str = "agent_stream",
request_id: Optional[str] = None,
model_id: Optional[str] = None,
timestamp: Optional[datetime] = None,
) -> None:
# Attribution guard: the ``token_usage_attribution_chk`` CHECK
Expand All @@ -59,13 +60,13 @@ def insert(
INSERT INTO token_usage (
user_id, api_key, agent_id,
prompt_tokens, generated_tokens,
source, request_id, timestamp
source, request_id, model_id, timestamp
)
VALUES (
:user_id, :api_key,
CAST(:agent_id AS uuid),
:prompt_tokens, :generated_tokens,
:source, :request_id, COALESCE(:timestamp, now())
:source, :request_id, :model_id, COALESCE(:timestamp, now())
)
"""
),
Expand All @@ -77,6 +78,7 @@ def insert(
"generated_tokens": generated_tokens,
"source": source,
"request_id": request_id,
"model_id": model_id,
"timestamp": timestamp,
},
)
Expand Down
1 change: 1 addition & 0 deletions application/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def _persist_call_usage(llm, call_usage):
getattr(llm, "_token_usage_source", None) or "agent_stream"
),
request_id=getattr(llm, "_request_id", None),
model_id=getattr(llm, "_canonical_model_id", None),
)
except Exception:
logger.exception("token_usage persist failed")
Expand Down
1 change: 0 additions & 1 deletion frontend/public/toolIcons/tool_cryptoprice.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/public/toolIcons/tool_notes.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/public/toolIcons/tool_read_webpage.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/public/toolIcons/tool_scheduler.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/public/toolIcons/tool_todo_list.svg

This file was deleted.

3 changes: 2 additions & 1 deletion frontend/src/agents/NewAgent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type MultiSelectPopoverItem,
} from '../components/MultiSelectPopover';
import Spinner from '../components/Spinner';
import ToolIcon from '../components/ToolIcon';
import AgentDetailsModal from '../modals/AgentDetailsModal';
import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState, Doc, Prompt } from '../models/misc';
Expand Down Expand Up @@ -487,7 +488,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
const base: MultiSelectPopoverItem = {
id: tool.id,
label: getToolDisplayName(tool),
icon: `/toolIcons/tool_${tool.name}.svg`,
icon: <ToolIcon name={tool.name} className="h-5 w-5" />,
group: groupFor(tool),
};
if (tool.name === 'remote_device') {
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/agents/SharedAgentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next';

import EditIcon from '../assets/edit.svg';
import ToolIcon from '../components/ToolIcon';
import { Avatar } from '../components/ui/avatar';
import { Button } from '../components/ui/button';
import { getToolDisplayName } from '../utils/toolUtils';
Expand Down Expand Up @@ -87,9 +88,9 @@ export default function SharedAgentCard({
key={index}
className="bg-accent text-foreground dark:bg-card flex items-center gap-1 rounded-full px-3 py-1 text-xs font-light"
>
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${getToolDisplayName(tool)} icon`}
<ToolIcon
name={tool.name}
title={`${getToolDisplayName(tool)} icon`}
className="h-3 w-3"
/>{' '}
{getToolDisplayName(tool)}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/toolIcons/tool_cryptoprice.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/assets/toolIcons/tool_notes.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading