mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-17 04:14:29 +00:00
Store execution traces in a new request_traces table
One row per execution holding its span tree as JSONB, linked to Logs rows by request, message, activity and workflow-run ids. message_id cascades so deleting or truncating a conversation drops its traces; a daily beat task enforces TRACES_RETENTION_DAYS.
This commit is contained in:
1 parent
f6269c483f
commit
f3f817b1e8
7 files changed
+686
-10
No files matched your search
@@ -0,0 +1,84 @@
|
||||
"""0037 request_traces — one execution trace per request for the Logs UI.
|
||||
|
||||
Each row is the full span tree of one execution (a chat turn, a tool-approval
|
||||
continuation, a scheduled or webhook run, a search, a graph extraction):
|
||||
agent runs, LLM calls, tool calls, retrieval and embeddings with their
|
||||
timings, stored as a ``spans`` JSONB array. The Logs UI loads a trace whole,
|
||||
so one row per trace keeps the write to a single INSERT and lets retention
|
||||
and conversation deletion remove a trace in one step.
|
||||
|
||||
``message_id`` cascades: deleting a conversation, or truncating it when a
|
||||
turn is superseded, removes that turn's traces with it. The link ids
|
||||
(``request_id``, ``activity_id``, ``workflow_run_id``) carry partial indexes
|
||||
because only the Logs rows that have them look traces up by them.
|
||||
|
||||
``status`` is the only CHECK; span kinds and sources are validated in code so
|
||||
new ones need no migration.
|
||||
|
||||
Idempotent both ways.
|
||||
|
||||
Revision ID: 0037_request_traces
|
||||
Revises: 0036_device_audit_created_idx
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
revision: str = "0037_request_traces"
|
||||
down_revision: Union[str, None] = "0036_device_audit_created_idx"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS request_traces (
|
||||
id UUID PRIMARY KEY,
|
||||
request_id TEXT,
|
||||
message_id UUID REFERENCES conversation_messages(id) ON DELETE CASCADE,
|
||||
conversation_id UUID,
|
||||
activity_id TEXT,
|
||||
workflow_run_id UUID,
|
||||
user_id TEXT,
|
||||
agent_id UUID,
|
||||
source TEXT NOT NULL,
|
||||
name TEXT,
|
||||
status TEXT NOT NULL
|
||||
CONSTRAINT request_traces_status_chk
|
||||
CHECK (status IN ('ok', 'error', 'paused', 'cancelled')),
|
||||
started_at TIMESTAMPTZ NOT NULL,
|
||||
duration_ms INTEGER NOT NULL DEFAULT 0,
|
||||
span_count INTEGER NOT NULL DEFAULT 0,
|
||||
dropped_spans INTEGER NOT NULL DEFAULT 0,
|
||||
summary JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
spans JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
otel_trace_id TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
"""
|
||||
)
|
||||
op.execute(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS request_traces_request_idx
|
||||
ON request_traces (request_id) WHERE request_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS request_traces_message_idx
|
||||
ON request_traces (message_id) WHERE message_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS request_traces_activity_idx
|
||||
ON request_traces (activity_id) WHERE activity_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS request_traces_workflow_run_idx
|
||||
ON request_traces (workflow_run_id) WHERE workflow_run_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS request_traces_user_started_idx
|
||||
ON request_traces (user_id, started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS request_traces_agent_started_idx
|
||||
ON request_traces (agent_id, started_at DESC) WHERE agent_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS request_traces_created_idx
|
||||
ON request_traces (created_at);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP TABLE IF EXISTS request_traces;")
|
||||
Reference in new issue
Block a user