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.
@@ -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()
@@ -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")