trace-agent-server 0.0.1__py3-none-any.whl
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.
- trace_agent_server/__init__.py +5 -0
- trace_agent_server/api/__init__.py +1 -0
- trace_agent_server/api/runs.py +1214 -0
- trace_agent_server/cli.py +29 -0
- trace_agent_server/config.py +57 -0
- trace_agent_server/db.py +24 -0
- trace_agent_server/main.py +73 -0
- trace_agent_server/models.py +131 -0
- trace_agent_server/schemas.py +549 -0
- trace_agent_server/services/__init__.py +1 -0
- trace_agent_server/services/analytics.py +305 -0
- trace_agent_server/services/audit_guardrails.py +303 -0
- trace_agent_server/services/compare.py +851 -0
- trace_agent_server/services/dashboard.py +125 -0
- trace_agent_server/services/events.py +131 -0
- trace_agent_server/services/explanation.py +230 -0
- trace_agent_server/services/graph.py +103 -0
- trace_agent_server/services/observability.py +140 -0
- trace_agent_server/services/provider.py +547 -0
- trace_agent_server/services/run_summary.py +363 -0
- trace_agent_server-0.0.1.dist-info/METADATA +21 -0
- trace_agent_server-0.0.1.dist-info/RECORD +25 -0
- trace_agent_server-0.0.1.dist-info/WHEEL +5 -0
- trace_agent_server-0.0.1.dist-info/entry_points.txt +2 -0
- trace_agent_server-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""CLI entry point for trace-agent-server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
parser = argparse.ArgumentParser(description="TraceAgent Server")
|
|
11
|
+
parser.add_argument("--host", default="0.0.0.0", help="Bind host (default: 0.0.0.0)")
|
|
12
|
+
parser.add_argument("--port", type=int, default=8000, help="Bind port (default: 8000)")
|
|
13
|
+
parser.add_argument("--reload", action="store_true", help="Enable auto-reload")
|
|
14
|
+
parser.add_argument("--workers", type=int, default=1, help="Number of worker processes")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
import uvicorn
|
|
18
|
+
|
|
19
|
+
uvicorn.run(
|
|
20
|
+
"trace_agent_server.main:app",
|
|
21
|
+
host=args.host,
|
|
22
|
+
port=args.port,
|
|
23
|
+
reload=args.reload,
|
|
24
|
+
workers=args.workers if not args.reload else 1,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Settings(BaseSettings):
|
|
10
|
+
app_name: str = "TraceAgent"
|
|
11
|
+
database_url: str = "sqlite:///./trace_agent.db"
|
|
12
|
+
openai_api_key: str | None = None
|
|
13
|
+
openai_base_url: str | None = None
|
|
14
|
+
anthropic_api_key: str | None = None
|
|
15
|
+
anthropic_base_url: str | None = None
|
|
16
|
+
google_api_key: str | None = None
|
|
17
|
+
kimi_api_key: str | None = None
|
|
18
|
+
kimi_base_url: str | None = None
|
|
19
|
+
lmstudio_base_url: str | None = None
|
|
20
|
+
lmstudio_mode: str = "openai"
|
|
21
|
+
lmstudio_api_key: str | None = None
|
|
22
|
+
cors_origins: list[str] = Field(default_factory=lambda: ["http://localhost:5173"])
|
|
23
|
+
model_pricing: dict[str, dict[str, float]] = Field(default_factory=dict)
|
|
24
|
+
audit_enable_guardrails: bool = False
|
|
25
|
+
audit_fail_on_prompt_injection: bool = False
|
|
26
|
+
audit_enable_pii_anonymization: bool = False
|
|
27
|
+
audit_prompt_injection_threshold: float = 0.85
|
|
28
|
+
audit_pii_entities: list[str] = Field(
|
|
29
|
+
default_factory=lambda: [
|
|
30
|
+
"CREDIT_CARD",
|
|
31
|
+
"CRYPTO",
|
|
32
|
+
"EMAIL_ADDRESS",
|
|
33
|
+
"IBAN_CODE",
|
|
34
|
+
"IP_ADDRESS",
|
|
35
|
+
"PERSON",
|
|
36
|
+
"PHONE_NUMBER",
|
|
37
|
+
"US_SSN",
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
audit_metrics_enabled: bool = True
|
|
41
|
+
audit_enable_otel: bool = False
|
|
42
|
+
audit_otel_service_name: str = "trace-agent-backend"
|
|
43
|
+
audit_otel_exporter_endpoint: str | None = None
|
|
44
|
+
audit_otel_exporter_protocol: str = "grpc"
|
|
45
|
+
capture_full_payloads: bool = True
|
|
46
|
+
|
|
47
|
+
model_config = SettingsConfigDict(
|
|
48
|
+
env_prefix="TRACE_AGENT_",
|
|
49
|
+
env_file=".env",
|
|
50
|
+
env_file_encoding="utf-8",
|
|
51
|
+
extra="ignore",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@lru_cache(maxsize=1)
|
|
56
|
+
def get_settings() -> Settings:
|
|
57
|
+
return Settings()
|
trace_agent_server/db.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
|
|
5
|
+
from fastapi import Request
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Base(DeclarativeBase):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_session_factory(database_url: str) -> tuple[object, sessionmaker[Session]]:
|
|
15
|
+
connect_args = {"check_same_thread": False} if database_url.startswith("sqlite") else {}
|
|
16
|
+
engine = create_engine(database_url, connect_args=connect_args, future=True)
|
|
17
|
+
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False)
|
|
18
|
+
return engine, session_factory
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_session(request: Request) -> Generator[Session, None, None]:
|
|
22
|
+
session_factory: sessionmaker[Session] = request.app.state.session_factory
|
|
23
|
+
with session_factory() as session:
|
|
24
|
+
yield session
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
|
|
6
|
+
from trace_agent_server.api.runs import router as runs_router
|
|
7
|
+
from trace_agent_server.config import Settings, get_settings
|
|
8
|
+
from trace_agent_server.db import Base, create_session_factory
|
|
9
|
+
from trace_agent_server.services.audit_guardrails import AuditGuardrails
|
|
10
|
+
from trace_agent_server.services.observability import AppObservability, configure_opentelemetry
|
|
11
|
+
from trace_agent_server.services.provider import (
|
|
12
|
+
AnthropicProviderAdapter,
|
|
13
|
+
BaseProviderAdapter,
|
|
14
|
+
GoogleProviderAdapter,
|
|
15
|
+
KimiProviderAdapter,
|
|
16
|
+
LMStudioProviderAdapter,
|
|
17
|
+
OpenAIProviderAdapter,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_provider_registry(settings: Settings) -> dict[str, BaseProviderAdapter]:
|
|
22
|
+
"""Dynamically instantiate provider adapters based on available configuration."""
|
|
23
|
+
registry: dict[str, BaseProviderAdapter] = {}
|
|
24
|
+
|
|
25
|
+
# OpenAI is always registered (it can work with just a base_url for local proxies)
|
|
26
|
+
registry["openai"] = OpenAIProviderAdapter(settings)
|
|
27
|
+
|
|
28
|
+
if settings.anthropic_api_key or settings.anthropic_base_url:
|
|
29
|
+
registry["anthropic"] = AnthropicProviderAdapter(settings)
|
|
30
|
+
|
|
31
|
+
if settings.google_api_key:
|
|
32
|
+
registry["google"] = GoogleProviderAdapter(settings)
|
|
33
|
+
|
|
34
|
+
if settings.kimi_api_key or settings.kimi_base_url:
|
|
35
|
+
registry["kimi"] = KimiProviderAdapter(settings)
|
|
36
|
+
|
|
37
|
+
if settings.lmstudio_base_url:
|
|
38
|
+
registry["lmstudio"] = LMStudioProviderAdapter(settings)
|
|
39
|
+
|
|
40
|
+
return registry
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_app(settings: Settings | None = None) -> FastAPI:
|
|
44
|
+
current_settings = settings or get_settings()
|
|
45
|
+
app = FastAPI(title=current_settings.app_name)
|
|
46
|
+
app.add_middleware(
|
|
47
|
+
CORSMiddleware,
|
|
48
|
+
allow_origins=current_settings.cors_origins,
|
|
49
|
+
allow_credentials=True,
|
|
50
|
+
allow_methods=["*"],
|
|
51
|
+
allow_headers=["*"],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
engine, session_factory = create_session_factory(current_settings.database_url)
|
|
55
|
+
Base.metadata.create_all(bind=engine)
|
|
56
|
+
app.state.engine = engine
|
|
57
|
+
app.state.session_factory = session_factory
|
|
58
|
+
app.state.settings = current_settings
|
|
59
|
+
app.state.provider_registry = build_provider_registry(current_settings)
|
|
60
|
+
app.state.observability = AppObservability(current_settings.audit_metrics_enabled)
|
|
61
|
+
app.state.observability.instrument_app(app)
|
|
62
|
+
app.state.guardrails = AuditGuardrails(current_settings, app.state.observability)
|
|
63
|
+
app.state.otel = configure_opentelemetry(app, current_settings)
|
|
64
|
+
|
|
65
|
+
@app.get("/health")
|
|
66
|
+
def healthcheck() -> dict[str, str]:
|
|
67
|
+
return {"status": "ok"}
|
|
68
|
+
|
|
69
|
+
app.include_router(runs_router)
|
|
70
|
+
return app
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
app = create_app()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import DateTime, ForeignKey, Integer, JSON, String, Text, UniqueConstraint
|
|
7
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
8
|
+
|
|
9
|
+
from trace_agent_server.db import Base
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def utcnow() -> datetime:
|
|
13
|
+
return datetime.now(timezone.utc)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Run(Base):
|
|
17
|
+
__tablename__ = "runs"
|
|
18
|
+
|
|
19
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
20
|
+
agent_name: Mapped[str] = mapped_column(String(255))
|
|
21
|
+
goal: Mapped[str] = mapped_column(Text)
|
|
22
|
+
status: Mapped[str] = mapped_column(String(32), default="running")
|
|
23
|
+
provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
24
|
+
model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
25
|
+
redaction_level: Mapped[str] = mapped_column(String(32), default="summary")
|
|
26
|
+
final_output_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
27
|
+
run_metadata: Mapped[dict] = mapped_column("metadata", JSON, default=dict)
|
|
28
|
+
explanation_cache: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
29
|
+
flag_cache: Mapped[list] = mapped_column(JSON, default=list)
|
|
30
|
+
tool_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
31
|
+
error_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
32
|
+
retry_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
33
|
+
artifact_count: Mapped[int] = mapped_column(Integer, default=0)
|
|
34
|
+
total_prompt_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
35
|
+
total_completion_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
|
36
|
+
last_event_seq: Mapped[int] = mapped_column(Integer, default=0)
|
|
37
|
+
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
38
|
+
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
39
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
40
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
|
|
41
|
+
|
|
42
|
+
events: Mapped[list["Event"]] = relationship(back_populates="run", cascade="all, delete-orphan")
|
|
43
|
+
decisions: Mapped[list["Decision"]] = relationship(back_populates="run", cascade="all, delete-orphan")
|
|
44
|
+
artifacts: Mapped[list["Artifact"]] = relationship(back_populates="run", cascade="all, delete-orphan")
|
|
45
|
+
turns: Mapped[list["Turn"]] = relationship(back_populates="run", cascade="all, delete-orphan")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Turn(Base):
|
|
49
|
+
__tablename__ = "turns"
|
|
50
|
+
|
|
51
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
52
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("runs.id", ondelete="CASCADE"), index=True)
|
|
53
|
+
seq: Mapped[int] = mapped_column(Integer, index=True)
|
|
54
|
+
provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
55
|
+
model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
56
|
+
messages: Mapped[list] = mapped_column(JSON, default=list)
|
|
57
|
+
assistant_message: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
58
|
+
tool_calls: Mapped[list] = mapped_column(JSON, default=list)
|
|
59
|
+
prompt_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
60
|
+
completion_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
61
|
+
total_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
62
|
+
reasoning_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
63
|
+
finish_reason: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
64
|
+
response_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
65
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
66
|
+
|
|
67
|
+
run: Mapped["Run"] = relationship(back_populates="turns")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Event(Base):
|
|
71
|
+
__tablename__ = "events"
|
|
72
|
+
__table_args__ = (UniqueConstraint("run_id", "seq", name="uq_event_run_seq"),)
|
|
73
|
+
|
|
74
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
75
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("runs.id", ondelete="CASCADE"), index=True)
|
|
76
|
+
turn_id: Mapped[str | None] = mapped_column(ForeignKey("turns.id", ondelete="SET NULL"), nullable=True, index=True)
|
|
77
|
+
seq: Mapped[int] = mapped_column(Integer, index=True)
|
|
78
|
+
timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
79
|
+
step_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
|
80
|
+
parent_step_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
81
|
+
actor: Mapped[str] = mapped_column(String(64))
|
|
82
|
+
type: Mapped[str] = mapped_column(String(64), index=True)
|
|
83
|
+
summary: Mapped[str] = mapped_column(Text)
|
|
84
|
+
status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
85
|
+
duration_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
86
|
+
provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
87
|
+
model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
88
|
+
tool_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
89
|
+
artifact_refs: Mapped[list] = mapped_column(JSON, default=list)
|
|
90
|
+
error_code: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
91
|
+
payload_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
92
|
+
payload_full: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
93
|
+
event_metadata: Mapped[dict] = mapped_column("metadata", JSON, default=dict)
|
|
94
|
+
|
|
95
|
+
run: Mapped["Run"] = relationship(back_populates="events")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Decision(Base):
|
|
99
|
+
__tablename__ = "decisions"
|
|
100
|
+
|
|
101
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
102
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("runs.id", ondelete="CASCADE"), index=True)
|
|
103
|
+
seq: Mapped[int] = mapped_column(Integer, index=True)
|
|
104
|
+
kind: Mapped[str] = mapped_column(String(64))
|
|
105
|
+
label: Mapped[str] = mapped_column(String(255))
|
|
106
|
+
chosen_action: Mapped[str] = mapped_column(String(255))
|
|
107
|
+
rationale_summary: Mapped[str] = mapped_column(Text)
|
|
108
|
+
trigger_event_ids: Mapped[list] = mapped_column(JSON, default=list)
|
|
109
|
+
evidence_event_ids: Mapped[list] = mapped_column(JSON, default=list)
|
|
110
|
+
alternatives: Mapped[list] = mapped_column(JSON, default=list)
|
|
111
|
+
outcome: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
112
|
+
decision_metadata: Mapped[dict] = mapped_column("metadata", JSON, default=dict)
|
|
113
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
114
|
+
|
|
115
|
+
run: Mapped["Run"] = relationship(back_populates="decisions")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Artifact(Base):
|
|
119
|
+
__tablename__ = "artifacts"
|
|
120
|
+
|
|
121
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4()))
|
|
122
|
+
run_id: Mapped[str] = mapped_column(ForeignKey("runs.id", ondelete="CASCADE"), index=True)
|
|
123
|
+
source_event_id: Mapped[int | None] = mapped_column(ForeignKey("events.id", ondelete="SET NULL"), nullable=True)
|
|
124
|
+
kind: Mapped[str] = mapped_column(String(64))
|
|
125
|
+
label: Mapped[str] = mapped_column(String(255))
|
|
126
|
+
summary: Mapped[str] = mapped_column(Text)
|
|
127
|
+
artifact_metadata: Mapped[dict] = mapped_column("metadata", JSON, default=dict)
|
|
128
|
+
content_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
129
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|
130
|
+
|
|
131
|
+
run: Mapped["Run"] = relationship(back_populates="artifacts")
|