Skip to content

Commit 5ca860b

Browse files
chore: standalone repo setup
Add CI/publish workflows, contract spec, .gitignore, and repo URLs. Split from monorepo (trylitmus/litmus) with history preserved.
1 parent 620e83f commit 5ca860b

5 files changed

Lines changed: 260 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
ci:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.12", "3.13", "3.14"]
15+
fail-fast: false
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- uses: astral-sh/setup-uv@v6
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
23+
- run: uv sync --extra dev
24+
25+
- name: Lint
26+
run: |
27+
uvx ruff check src/ tests/
28+
uvx ruff format --check src/ tests/
29+
30+
- name: Test
31+
run: uv run pytest tests/ -v
32+
33+
- name: Build
34+
run: uv build

.github/workflows/publish.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
permissions:
8+
contents: read
9+
id-token: write
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
environment: pypi
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: astral-sh/setup-uv@v6
19+
20+
- name: Verify version matches tag
21+
run: |
22+
tag_version="${GITHUB_REF#refs/tags/v}"
23+
pkg_version=$(uv run python -c "from litmus.version import VERSION; print(VERSION)")
24+
if [ "$pkg_version" != "$tag_version" ]; then
25+
echo "::error::Version mismatch: package has $pkg_version but tag is $tag_version"
26+
exit 1
27+
fi
28+
29+
- name: Build
30+
run: uv build
31+
32+
- name: Publish to PyPI
33+
uses: pypa/gh-action-pypi-publish@release/v1
34+
with:
35+
packages-dir: dist/

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
__pycache__/
2+
*.pyc
3+
*.pyo
4+
*.egg-info/
5+
dist/
6+
build/
7+
.venv/
8+
.pytest_cache/
9+
.ruff_cache/

contract/openapi.yaml

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
openapi: "3.0.3"
2+
info:
3+
title: Litmus Ingest API
4+
version: "1.0.0"
5+
description: Event ingestion API for Litmus implicit evals.
6+
7+
servers:
8+
- url: https://ingest.trylitmus.app
9+
description: Production
10+
11+
security:
12+
- bearerAuth: []
13+
14+
paths:
15+
/v1/events:
16+
post:
17+
operationId: ingestEvents
18+
summary: Ingest a batch of events
19+
parameters:
20+
- name: token
21+
in: query
22+
required: false
23+
schema:
24+
type: string
25+
description: |
26+
Alternative to the Authorization header for environments where
27+
custom headers cannot be set (e.g. navigator.sendBeacon). Pass
28+
the full API key as the value. When both this parameter and the
29+
Authorization header are present, the header takes precedence.
30+
requestBody:
31+
required: true
32+
content:
33+
application/json:
34+
schema:
35+
$ref: "#/components/schemas/IngestRequest"
36+
responses:
37+
"202":
38+
description: Events accepted
39+
content:
40+
application/json:
41+
schema:
42+
$ref: "#/components/schemas/IngestResponse"
43+
"400":
44+
description: Validation error
45+
content:
46+
application/json:
47+
schema:
48+
$ref: "#/components/schemas/ErrorResponse"
49+
"401":
50+
description: Unauthorized
51+
content:
52+
application/json:
53+
schema:
54+
$ref: "#/components/schemas/ErrorResponse"
55+
"403":
56+
description: Insufficient scope
57+
content:
58+
application/json:
59+
schema:
60+
$ref: "#/components/schemas/ErrorResponse"
61+
62+
/health:
63+
get:
64+
operationId: healthCheck
65+
summary: Health check
66+
security: []
67+
responses:
68+
"200":
69+
description: Service is healthy
70+
content:
71+
application/json:
72+
schema:
73+
$ref: "#/components/schemas/HealthResponse"
74+
75+
components:
76+
securitySchemes:
77+
bearerAuth:
78+
type: http
79+
scheme: bearer
80+
description: |
81+
Litmus API key (ltm_pk_live_... or ltm_sk_live_...).
82+
83+
The key can also be passed as a ?token= query parameter on any
84+
authenticated endpoint. This exists to support navigator.sendBeacon(),
85+
which cannot set custom headers. When both the header and query
86+
parameter are present, the Authorization header takes precedence.
87+
88+
schemas:
89+
EventType:
90+
type: string
91+
maxLength: 64
92+
pattern: "^(\\$[a-z][a-z0-9_:]*|[a-z][a-z0-9_:]*)$"
93+
description: |
94+
Event type identifier. Lowercase a-z, 0-9, underscore, colon only.
95+
System events use a $ prefix (reserved namespace).
96+
Known system events: $generation, $regenerate, $copy, $edit, $abandon, $accept,
97+
$view, $partial_copy, $refine, $followup, $rephrase, $undo, $share, $flag,
98+
$rate, $escalate, $switch_model, $retry_context, $post_accept_edit,
99+
$blur, $return, $scroll_regression, $navigate, $interrupt.
100+
101+
Event:
102+
type: object
103+
required:
104+
- id
105+
- session_id
106+
- type
107+
properties:
108+
id:
109+
type: string
110+
description: Unique event ID (UUID)
111+
project_id:
112+
type: string
113+
description: Injected by the server from the authenticated token
114+
session_id:
115+
type: string
116+
description: User session identifier
117+
type:
118+
$ref: "#/components/schemas/EventType"
119+
prompt_id:
120+
type: string
121+
description: Identifier for the prompt template
122+
prompt_version:
123+
type: string
124+
description: Version of the prompt template (set explicitly or auto-hashed)
125+
generation_id:
126+
type: string
127+
description: Identifier for the specific generation
128+
user_id:
129+
type: string
130+
description: Identifier for the end user. Required for cross-session signals (Tier 4+).
131+
metadata:
132+
type: object
133+
additionalProperties: true
134+
description: Arbitrary key-value metadata
135+
timestamp:
136+
type: string
137+
format: date-time
138+
description: ISO 8601 timestamp. Server defaults to now if omitted.
139+
140+
IngestRequest:
141+
type: object
142+
required:
143+
- events
144+
properties:
145+
events:
146+
type: array
147+
items:
148+
$ref: "#/components/schemas/Event"
149+
minItems: 1
150+
maxItems: 1000
151+
152+
IngestResponse:
153+
type: object
154+
required:
155+
- accepted
156+
properties:
157+
accepted:
158+
type: integer
159+
description: Number of events accepted
160+
161+
ErrorResponse:
162+
type: object
163+
required:
164+
- error
165+
properties:
166+
error:
167+
type: string
168+
169+
HealthResponse:
170+
type: object
171+
required:
172+
- status
173+
properties:
174+
status:
175+
type: string
176+
enum:
177+
- ok

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ description = "Litmus Python SDK - implicit evals for AI products"
55
readme = "README.md"
66
requires-python = ">=3.12"
77
license = "MIT"
8+
89
dependencies = [
910
"httpx>=0.27.0",
1011
]
1112

13+
[project.urls]
14+
Repository = "https://github.com/trylitmus/litmus-python"
15+
Homepage = "https://trylitmus.com"
16+
1217
[project.optional-dependencies]
1318
dev = [
1419
"pytest>=8.0",

0 commit comments

Comments
 (0)