magic-lab-observer-backend 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- magic_lab_observer_backend-0.1.1/.gitignore +68 -0
- magic_lab_observer_backend-0.1.1/Containerfile +19 -0
- magic_lab_observer_backend-0.1.1/PKG-INFO +62 -0
- magic_lab_observer_backend-0.1.1/README.md +32 -0
- magic_lab_observer_backend-0.1.1/alembic/env.py +71 -0
- magic_lab_observer_backend-0.1.1/alembic/script.py.mako +28 -0
- magic_lab_observer_backend-0.1.1/alembic/versions/20260720_manitos_ingest.py +83 -0
- magic_lab_observer_backend-0.1.1/alembic/versions/623c57886e29_initial_schema.py +87 -0
- magic_lab_observer_backend-0.1.1/alembic.ini +42 -0
- magic_lab_observer_backend-0.1.1/app/__init__.py +1 -0
- magic_lab_observer_backend-0.1.1/app/api/__init__.py +5 -0
- magic_lab_observer_backend-0.1.1/app/api/analytics.py +389 -0
- magic_lab_observer_backend-0.1.1/app/api/evaluations.py +314 -0
- magic_lab_observer_backend-0.1.1/app/api/manitos_ingest.py +454 -0
- magic_lab_observer_backend-0.1.1/app/api/traces.py +753 -0
- magic_lab_observer_backend-0.1.1/app/auth.py +36 -0
- magic_lab_observer_backend-0.1.1/app/db/__init__.py +20 -0
- magic_lab_observer_backend-0.1.1/app/db/postgres.py +27 -0
- magic_lab_observer_backend-0.1.1/app/evaluators/__init__.py +313 -0
- magic_lab_observer_backend-0.1.1/app/evaluators/rubric_engine.py +517 -0
- magic_lab_observer_backend-0.1.1/app/evaluators/rubrics/default.yaml +218 -0
- magic_lab_observer_backend-0.1.1/app/evaluators/rubrics/scoring_levels.json +182 -0
- magic_lab_observer_backend-0.1.1/app/main.py +123 -0
- magic_lab_observer_backend-0.1.1/app/models/__init__.py +16 -0
- magic_lab_observer_backend-0.1.1/app/models/evaluation.py +38 -0
- magic_lab_observer_backend-0.1.1/app/models/ingestion.py +27 -0
- magic_lab_observer_backend-0.1.1/app/models/trace.py +66 -0
- magic_lab_observer_backend-0.1.1/app/ops/__init__.py +1 -0
- magic_lab_observer_backend-0.1.1/app/ops/manitos_passive_gate.py +738 -0
- magic_lab_observer_backend-0.1.1/app/ratelimit.py +25 -0
- magic_lab_observer_backend-0.1.1/app/websocket.py +53 -0
- magic_lab_observer_backend-0.1.1/pyproject.toml +59 -0
- magic_lab_observer_backend-0.1.1/tests/conftest.py +56 -0
- magic_lab_observer_backend-0.1.1/tests/test_analytics_quality_window.py +84 -0
- magic_lab_observer_backend-0.1.1/tests/test_api.py +963 -0
- magic_lab_observer_backend-0.1.1/tests/test_manitos_ingest.py +338 -0
- magic_lab_observer_backend-0.1.1/tests/test_manitos_passive_gate.py +390 -0
- magic_lab_observer_backend-0.1.1/tests/test_rubric.py +617 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
|
|
35
|
+
# Testing
|
|
36
|
+
.coverage
|
|
37
|
+
htmlcov/
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
.mypy_cache/
|
|
40
|
+
|
|
41
|
+
# Node.js
|
|
42
|
+
node_modules/
|
|
43
|
+
npm-debug.log*
|
|
44
|
+
yarn-debug.log*
|
|
45
|
+
yarn-error.log*
|
|
46
|
+
|
|
47
|
+
# Build
|
|
48
|
+
dist/
|
|
49
|
+
build/
|
|
50
|
+
*.tsbuildinfo
|
|
51
|
+
|
|
52
|
+
# Environment
|
|
53
|
+
.env
|
|
54
|
+
.env.local
|
|
55
|
+
.env.*.local
|
|
56
|
+
|
|
57
|
+
# Podman
|
|
58
|
+
podman-compose.override.yml
|
|
59
|
+
|
|
60
|
+
# Database
|
|
61
|
+
*.db
|
|
62
|
+
*.sqlite3
|
|
63
|
+
.observer-state/
|
|
64
|
+
|
|
65
|
+
# OS
|
|
66
|
+
.DS_Store
|
|
67
|
+
Thumbs.db
|
|
68
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install system dependencies
|
|
6
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
7
|
+
build-essential \
|
|
8
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
# Install the complete application as an immutable production package.
|
|
11
|
+
COPY . .
|
|
12
|
+
RUN pip install --no-cache-dir .
|
|
13
|
+
|
|
14
|
+
# Expose port
|
|
15
|
+
EXPOSE 8000
|
|
16
|
+
|
|
17
|
+
# Migrations must run against the configured runtime database, not against a
|
|
18
|
+
# throwaway SQLite file while the image is being built.
|
|
19
|
+
CMD ["sh", "-c", "alembic upgrade head && exec uvicorn app.main:app --host 0.0.0.0 --port 8000"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: magic-lab-observer-backend
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Backend API for LLM Observatory
|
|
5
|
+
Project-URL: Homepage, https://github.com/Magic-Lab-Studio/observer
|
|
6
|
+
Project-URL: Repository, https://github.com/Magic-Lab-Studio/observer.git
|
|
7
|
+
Project-URL: Issues, https://github.com/Magic-Lab-Studio/observer/issues
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: alembic>=1.13.0
|
|
11
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
12
|
+
Requires-Dist: fastapi>=0.111.0
|
|
13
|
+
Requires-Dist: httpx>=0.27.0
|
|
14
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: slowapi>=0.1.9
|
|
19
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
20
|
+
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: aiosqlite>=0.20.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
27
|
+
Provides-Extra: sqlite
|
|
28
|
+
Requires-Dist: aiosqlite>=0.20.0; extra == 'sqlite'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# LLM Observatory Backend
|
|
32
|
+
|
|
33
|
+
FastAPI backend for LLM Observatory - Open-source LLM observability.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e ".[dev]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Running
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Start PostgreSQL
|
|
45
|
+
podman run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=observatory postgres:16
|
|
46
|
+
|
|
47
|
+
# Run migrations
|
|
48
|
+
alembic upgrade head
|
|
49
|
+
|
|
50
|
+
# Start server
|
|
51
|
+
uvicorn app.main:app --reload
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Documentation
|
|
55
|
+
|
|
56
|
+
Once running, visit:
|
|
57
|
+
- Swagger UI: http://localhost:8000/docs
|
|
58
|
+
- ReDoc: http://localhost:8000/redoc
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
Apache License 2.0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# LLM Observatory Backend
|
|
2
|
+
|
|
3
|
+
FastAPI backend for LLM Observatory - Open-source LLM observability.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Running
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Start PostgreSQL
|
|
15
|
+
podman run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=observatory postgres:16
|
|
16
|
+
|
|
17
|
+
# Run migrations
|
|
18
|
+
alembic upgrade head
|
|
19
|
+
|
|
20
|
+
# Start server
|
|
21
|
+
uvicorn app.main:app --reload
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API Documentation
|
|
25
|
+
|
|
26
|
+
Once running, visit:
|
|
27
|
+
- Swagger UI: http://localhost:8000/docs
|
|
28
|
+
- ReDoc: http://localhost:8000/redoc
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
Apache License 2.0
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Alembic environment configuration."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from logging.config import fileConfig
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import pool
|
|
8
|
+
from sqlalchemy.engine import Connection
|
|
9
|
+
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
10
|
+
|
|
11
|
+
from alembic import context
|
|
12
|
+
from app.models import Base
|
|
13
|
+
|
|
14
|
+
# Alembic Config object
|
|
15
|
+
config = context.config
|
|
16
|
+
if database_url := os.getenv("DATABASE_URL"):
|
|
17
|
+
config.set_main_option("sqlalchemy.url", database_url.replace("%", "%%"))
|
|
18
|
+
|
|
19
|
+
# Interpret the config file for Python logging
|
|
20
|
+
if config.config_file_name is not None:
|
|
21
|
+
fileConfig(config.config_file_name)
|
|
22
|
+
|
|
23
|
+
# Add model's MetaData object for autogenerate support
|
|
24
|
+
target_metadata = Base.metadata
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def run_migrations_offline() -> None:
|
|
28
|
+
"""Run migrations in 'offline' mode."""
|
|
29
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
30
|
+
context.configure(
|
|
31
|
+
url=url,
|
|
32
|
+
target_metadata=target_metadata,
|
|
33
|
+
literal_binds=True,
|
|
34
|
+
dialect_opts={"paramstyle": "named"},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
with context.begin_transaction():
|
|
38
|
+
context.run_migrations()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def do_run_migrations(connection: Connection) -> None:
|
|
42
|
+
"""Run migrations with a connection."""
|
|
43
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
44
|
+
|
|
45
|
+
with context.begin_transaction():
|
|
46
|
+
context.run_migrations()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def run_async_migrations() -> None:
|
|
50
|
+
"""Run migrations in 'online' mode."""
|
|
51
|
+
connectable = async_engine_from_config(
|
|
52
|
+
config.get_section(config.config_ini_section, {}),
|
|
53
|
+
prefix="sqlalchemy.",
|
|
54
|
+
poolclass=pool.NullPool,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async with connectable.connect() as connection:
|
|
58
|
+
await connection.run_sync(do_run_migrations)
|
|
59
|
+
|
|
60
|
+
await connectable.dispose()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run_migrations_online() -> None:
|
|
64
|
+
"""Run migrations in 'online' mode."""
|
|
65
|
+
asyncio.run(run_async_migrations())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if context.is_offline_mode():
|
|
69
|
+
run_migrations_offline()
|
|
70
|
+
else:
|
|
71
|
+
run_migrations_online()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
${imports if imports else ""}
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = ${repr(up_revision)}
|
|
16
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Upgrade schema."""
|
|
23
|
+
${upgrades if upgrades else "pass"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade() -> None:
|
|
27
|
+
"""Downgrade schema."""
|
|
28
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""add versioned ManitOS ingestion contract
|
|
2
|
+
|
|
3
|
+
Revision ID: 20260720_manitos
|
|
4
|
+
Revises: 623c57886e29
|
|
5
|
+
Create Date: 2026-07-20
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
revision: str = "20260720_manitos"
|
|
15
|
+
down_revision: str | None = "623c57886e29"
|
|
16
|
+
branch_labels: str | Sequence[str] | None = None
|
|
17
|
+
depends_on: str | Sequence[str] | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
"""Add ManitOS correlation fields and retry receipts."""
|
|
22
|
+
|
|
23
|
+
with op.batch_alter_table("traces") as batch_op:
|
|
24
|
+
batch_op.alter_column(
|
|
25
|
+
"session_id",
|
|
26
|
+
existing_type=sa.String(length=36),
|
|
27
|
+
type_=sa.String(length=255),
|
|
28
|
+
existing_nullable=True,
|
|
29
|
+
)
|
|
30
|
+
batch_op.add_column(sa.Column("turn_id", sa.String(length=128), nullable=True))
|
|
31
|
+
batch_op.add_column(sa.Column("project_id", sa.String(length=128), nullable=True))
|
|
32
|
+
batch_op.add_column(sa.Column("environment", sa.String(length=64), nullable=True))
|
|
33
|
+
batch_op.add_column(sa.Column("service_instance_id", sa.String(length=128), nullable=True))
|
|
34
|
+
batch_op.add_column(sa.Column("actor_id_hash", sa.String(length=128), nullable=True))
|
|
35
|
+
batch_op.add_column(
|
|
36
|
+
sa.Column(
|
|
37
|
+
"schema_version",
|
|
38
|
+
sa.String(length=64),
|
|
39
|
+
nullable=False,
|
|
40
|
+
server_default="observer.trace.v1",
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
batch_op.create_index("ix_traces_turn_id", ["turn_id"], unique=False)
|
|
44
|
+
batch_op.create_index("ix_traces_project_id", ["project_id"], unique=False)
|
|
45
|
+
|
|
46
|
+
op.create_table(
|
|
47
|
+
"ingestion_receipts",
|
|
48
|
+
sa.Column("id", sa.String(length=36), nullable=False),
|
|
49
|
+
sa.Column("project_id", sa.String(length=128), nullable=False),
|
|
50
|
+
sa.Column("idempotency_key", sa.String(length=255), nullable=False),
|
|
51
|
+
sa.Column("payload_sha256", sa.String(length=64), nullable=False),
|
|
52
|
+
sa.Column("trace_id", sa.String(length=36), nullable=False),
|
|
53
|
+
sa.Column("response", sa.JSON(), nullable=True),
|
|
54
|
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
|
55
|
+
sa.PrimaryKeyConstraint("id"),
|
|
56
|
+
sa.UniqueConstraint("project_id", "idempotency_key", name="uq_ingestion_project_key"),
|
|
57
|
+
)
|
|
58
|
+
op.create_index("ix_ingestion_receipts_project_id", "ingestion_receipts", ["project_id"])
|
|
59
|
+
op.create_index("ix_ingestion_receipts_trace_id", "ingestion_receipts", ["trace_id"])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def downgrade() -> None:
|
|
63
|
+
"""Remove ManitOS ingestion state."""
|
|
64
|
+
|
|
65
|
+
op.drop_index("ix_ingestion_receipts_trace_id", table_name="ingestion_receipts")
|
|
66
|
+
op.drop_index("ix_ingestion_receipts_project_id", table_name="ingestion_receipts")
|
|
67
|
+
op.drop_table("ingestion_receipts")
|
|
68
|
+
|
|
69
|
+
with op.batch_alter_table("traces") as batch_op:
|
|
70
|
+
batch_op.drop_index("ix_traces_project_id")
|
|
71
|
+
batch_op.drop_index("ix_traces_turn_id")
|
|
72
|
+
batch_op.drop_column("schema_version")
|
|
73
|
+
batch_op.drop_column("actor_id_hash")
|
|
74
|
+
batch_op.drop_column("service_instance_id")
|
|
75
|
+
batch_op.drop_column("environment")
|
|
76
|
+
batch_op.drop_column("project_id")
|
|
77
|
+
batch_op.drop_column("turn_id")
|
|
78
|
+
batch_op.alter_column(
|
|
79
|
+
"session_id",
|
|
80
|
+
existing_type=sa.String(length=255),
|
|
81
|
+
type_=sa.String(length=36),
|
|
82
|
+
existing_nullable=True,
|
|
83
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""initial schema
|
|
2
|
+
|
|
3
|
+
Revision ID: 623c57886e29
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-07-12 20:58:35.221373
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = '623c57886e29'
|
|
16
|
+
down_revision: str | None = None
|
|
17
|
+
branch_labels: str | Sequence[str] | None = None
|
|
18
|
+
depends_on: str | Sequence[str] | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Upgrade schema."""
|
|
23
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
24
|
+
op.create_table('traces',
|
|
25
|
+
sa.Column('id', sa.String(length=36), nullable=False),
|
|
26
|
+
sa.Column('name', sa.String(length=255), nullable=False),
|
|
27
|
+
sa.Column('session_id', sa.String(length=36), nullable=True),
|
|
28
|
+
sa.Column('start_time', sa.DateTime(timezone=True), nullable=False),
|
|
29
|
+
sa.Column('end_time', sa.DateTime(timezone=True), nullable=True),
|
|
30
|
+
sa.Column('status', sa.String(length=20), nullable=False),
|
|
31
|
+
sa.Column('metadata', sa.JSON(), nullable=True),
|
|
32
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
|
|
33
|
+
sa.PrimaryKeyConstraint('id')
|
|
34
|
+
)
|
|
35
|
+
op.create_index(op.f('ix_traces_session_id'), 'traces', ['session_id'], unique=False)
|
|
36
|
+
op.create_table('spans',
|
|
37
|
+
sa.Column('id', sa.String(length=36), nullable=False),
|
|
38
|
+
sa.Column('trace_id', sa.String(length=36), nullable=False),
|
|
39
|
+
sa.Column('parent_span_id', sa.String(length=36), nullable=True),
|
|
40
|
+
sa.Column('name', sa.String(length=255), nullable=False),
|
|
41
|
+
sa.Column('span_type', sa.String(length=50), nullable=False),
|
|
42
|
+
sa.Column('start_time', sa.DateTime(timezone=True), nullable=False),
|
|
43
|
+
sa.Column('end_time', sa.DateTime(timezone=True), nullable=True),
|
|
44
|
+
sa.Column('status', sa.String(length=20), nullable=False),
|
|
45
|
+
sa.Column('input', sa.JSON(), nullable=True),
|
|
46
|
+
sa.Column('output', sa.JSON(), nullable=True),
|
|
47
|
+
sa.Column('tokens_input', sa.Integer(), nullable=True),
|
|
48
|
+
sa.Column('tokens_output', sa.Integer(), nullable=True),
|
|
49
|
+
sa.Column('cost_usd', sa.Numeric(precision=10, scale=6), nullable=True),
|
|
50
|
+
sa.Column('metadata', sa.JSON(), nullable=True),
|
|
51
|
+
sa.Column('attributes', sa.JSON(), nullable=True),
|
|
52
|
+
sa.ForeignKeyConstraint(['parent_span_id'], ['spans.id'], ),
|
|
53
|
+
sa.ForeignKeyConstraint(['trace_id'], ['traces.id'], ondelete='CASCADE'),
|
|
54
|
+
sa.PrimaryKeyConstraint('id')
|
|
55
|
+
)
|
|
56
|
+
op.create_index(op.f('ix_spans_parent_span_id'), 'spans', ['parent_span_id'], unique=False)
|
|
57
|
+
op.create_index(op.f('ix_spans_trace_id'), 'spans', ['trace_id'], unique=False)
|
|
58
|
+
op.create_table('evaluations',
|
|
59
|
+
sa.Column('id', sa.String(length=36), nullable=False),
|
|
60
|
+
sa.Column('trace_id', sa.String(length=36), nullable=False),
|
|
61
|
+
sa.Column('span_id', sa.String(length=36), nullable=True),
|
|
62
|
+
sa.Column('evaluator_type', sa.String(length=20), nullable=False),
|
|
63
|
+
sa.Column('score', sa.Numeric(precision=5, scale=4), nullable=True),
|
|
64
|
+
sa.Column('criteria', sa.JSON(), nullable=True),
|
|
65
|
+
sa.Column('result', sa.JSON(), nullable=True),
|
|
66
|
+
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
|
|
67
|
+
sa.ForeignKeyConstraint(['span_id'], ['spans.id'], ondelete='CASCADE'),
|
|
68
|
+
sa.ForeignKeyConstraint(['trace_id'], ['traces.id'], ondelete='CASCADE'),
|
|
69
|
+
sa.PrimaryKeyConstraint('id')
|
|
70
|
+
)
|
|
71
|
+
op.create_index(op.f('ix_evaluations_span_id'), 'evaluations', ['span_id'], unique=False)
|
|
72
|
+
op.create_index(op.f('ix_evaluations_trace_id'), 'evaluations', ['trace_id'], unique=False)
|
|
73
|
+
# ### end Alembic commands ###
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def downgrade() -> None:
|
|
77
|
+
"""Downgrade schema."""
|
|
78
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
79
|
+
op.drop_index(op.f('ix_evaluations_trace_id'), table_name='evaluations')
|
|
80
|
+
op.drop_index(op.f('ix_evaluations_span_id'), table_name='evaluations')
|
|
81
|
+
op.drop_table('evaluations')
|
|
82
|
+
op.drop_index(op.f('ix_spans_trace_id'), table_name='spans')
|
|
83
|
+
op.drop_index(op.f('ix_spans_parent_span_id'), table_name='spans')
|
|
84
|
+
op.drop_table('spans')
|
|
85
|
+
op.drop_index(op.f('ix_traces_session_id'), table_name='traces')
|
|
86
|
+
op.drop_table('traces')
|
|
87
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Alembic Configuration File
|
|
2
|
+
# https://alembic.sqlalchemy.org/en/latest/tutorial.html
|
|
3
|
+
|
|
4
|
+
[alembic]
|
|
5
|
+
script_location = alembic
|
|
6
|
+
prepend_sys_path = .
|
|
7
|
+
# Default to SQLite for local development, override with DATABASE_URL env var
|
|
8
|
+
sqlalchemy.url = sqlite+aiosqlite:///./observatory.db
|
|
9
|
+
|
|
10
|
+
[loggers]
|
|
11
|
+
keys = root,sqlalchemy,alembic
|
|
12
|
+
|
|
13
|
+
[handlers]
|
|
14
|
+
keys = console
|
|
15
|
+
|
|
16
|
+
[formatters]
|
|
17
|
+
keys = generic
|
|
18
|
+
|
|
19
|
+
[logger_root]
|
|
20
|
+
level = WARN
|
|
21
|
+
handlers = console
|
|
22
|
+
qualname =
|
|
23
|
+
|
|
24
|
+
[logger_sqlalchemy]
|
|
25
|
+
level = WARN
|
|
26
|
+
handlers =
|
|
27
|
+
qualname = sqlalchemy.engine
|
|
28
|
+
|
|
29
|
+
[logger_alembic]
|
|
30
|
+
level = INFO
|
|
31
|
+
handlers =
|
|
32
|
+
qualname = alembic
|
|
33
|
+
|
|
34
|
+
[handler_console]
|
|
35
|
+
class = StreamHandler
|
|
36
|
+
args = (sys.stderr,)
|
|
37
|
+
level = NOTSET
|
|
38
|
+
formatter = generic
|
|
39
|
+
|
|
40
|
+
[formatter_generic]
|
|
41
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
42
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LLM Observatory Backend - FastAPI application."""
|