Skip to content

Commit 3e226a6

Browse files
authored
Async server (#73)
1 parent 93ffe56 commit 3e226a6

File tree

21 files changed

+280
-333
lines changed

21 files changed

+280
-333
lines changed

.github/workflows/pypi-publish.yml renamed to .github/workflows/cd.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
name: Publish Python Package
1+
name: Publish PyPi Package
22

3-
on: [push, pull_request]
3+
on:
4+
push:
5+
tags:
6+
- 'v*.*.*' # triggers on tags like v1.2.3
47

58
jobs:
69
test_and_build:
@@ -30,7 +33,7 @@ jobs:
3033
deploy:
3134
needs: test_and_build
3235
runs-on: ubuntu-latest
33-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
36+
if: startsWith(github.ref, 'refs/tags/')
3437
steps:
3538
- name: Download artifact
3639
uses: actions/download-artifact@v4

.github/workflows/tests.yml renamed to .github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
name: PromptLab Tests
1+
name: Tests
22

33
on:
44
push:
5-
branches: [main, feature/async-support]
5+
branches: [main, feature/*]
66
pull_request:
77
branches: [main]
88

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,6 @@ ide_docs/*
180180
.cursor/
181181
.trunk/
182182
AGENTS.md
183-
.env
183+
.env
184+
185+
*.whl

docker/Dockerfile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ RUN apt-get update && apt-get install -y \
1313
gcc \
1414
&& rm -rf /var/lib/apt/lists/*
1515

16-
# Copy requirements file and wheel file
17-
COPY requirements.txt .
16+
# # Uncomment for PROD image build
17+
# COPY requirements.txt .
18+
# RUN pip install --no-cache-dir -r requirements.txt
1819

19-
# Install Python dependencies from requirements.txt
20-
RUN pip install --no-cache-dir -r requirements.txt
20+
# Uncomment for TEST image build
21+
COPY promptlab-0.1.6-py3-none-any.whl .
22+
RUN pip install --no-cache-dir promptlab-0.1.6-py3-none-any.whl
2123

2224
# Create directory for database
2325
RUN mkdir -p /app/data

docker/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
promptlab==0.1.5
1+
promptlab==0.1.6

docs/self_hosting.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,37 @@ This guide covers how to deploy PromptLab using Docker containers, both locally
2929
Run PromptLab with default settings:
3030

3131
```bash
32-
docker run -p 8000:8000 -p 8001:8001 --name promptlab-container promptlab:latest
32+
docker run -p 8000:8000 --name promptlab-container promptlab:latest
3333
```
3434

3535
#### Production Setup with Persistent Data
3636

37-
For production use, mount a volume to persist your database:
37+
For production use, mount a volume to persist your database and set security environment variables:
3838

3939
```bash
4040
docker run -p 8000:8000 -p 8001:8001 \
4141
-v /your/local/data/path:/app/data \
42+
-e PROMPTLAB_SECRET_KEY="your-secure-32-character-secret-key-here" \
4243
--name promptlab-container \
4344
--restart unless-stopped \
4445
promptlab:latest
4546
```
4647

4748
**Windows Example:**
4849
```powershell
49-
docker run -p 8000:8000 -p 8001:8001 -v C:\work\promptlab_data:/app/data --name promptlab-container --restart unless-stopped promptlab:latest
50+
docker run -p 8000:8000 -p 8001:8001 -v C:\work\promptlab_data:/app/data -e PROMPTLAB_SECRET_KEY="your-secure-32-character-secret-key-here" --name promptlab-container --restart unless-stopped promptlab:latest
5051
```
5152

53+
#### Environment Variables
54+
55+
| Variable | Required | Description | Default |
56+
|----------|----------|-------------|---------|
57+
| `PROMPTLAB_SECRET_KEY` | **Recommended** | JWT signing key for authentication. Should be 32+ characters for security. | Auto-generated (not persistent) |
58+
| `PROMPTLAB_LOG_LEVEL` | Optional | Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) | `INFO` |
59+
| `PROMPTLAB_LOG_DIR` | Optional | Custom directory for log files | Platform-specific default |
60+
61+
**⚠️ Security Note:** Always set `PROMPTLAB_SECRET_KEY` in production to ensure consistent JWT token validation across container restarts.
62+
5263
### Accessing the Application
5364

5465
- **Main Application:** http://localhost:8000
@@ -109,7 +120,8 @@ az container create \
109120
--cpu 2 \
110121
--memory 4 \
111122
--os-type Linux \
112-
--restart-policy Always
123+
--restart-policy Always \
124+
--environment-variables PROMPTLAB_SECRET_KEY="your-secure-32-character-secret-key-here"
113125
```
114126

115127
#### Step 4: Access Your Application
@@ -150,5 +162,8 @@ az containerapp create \
150162
--cpu 1.0 \
151163
--memory 2.0Gi \
152164
--min-replicas 1 \
153-
--max-replicas 3
154-
```
165+
--max-replicas 3 \
166+
--env-vars PROMPTLAB_SECRET_KEY="your-secure-32-character-secret-key-here"
167+
```
168+
169+
**Note:** For Container Apps, you can also set environment variables through the Azure portal or use Azure Key Vault for enhanced security.

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "promptlab"
7-
version = "0.1.5"
7+
version = "0.1.6"
88
description = "PromptLab is a free, lightweight, open-source experimentation tool for Gen AI applications."
99
readme = "README.md"
1010
authors = [{name = "ra1han", email = "[email protected]"}]
@@ -30,12 +30,14 @@ dependencies = [
3030
"art>=6.5",
3131
"setuptools",
3232
"fastapi",
33-
"uvicorn",
33+
"uvicorn[standard]>=0.18.0",
3434
"sqlalchemy",
3535
"passlib",
3636
"python-jose",
3737
"python-multipart",
38-
"bcrypt"
38+
"bcrypt",
39+
"uvloop>=0.17.0; sys_platform != 'win32'",
40+
"httptools>=0.5.0; sys_platform != 'win32'",
3941
]
4042
requires-python = ">=3.8"
4143

samples/agent_evaluation/agent_evaluation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async def agent_proxy(inputs: dict) -> ModelResponse:
2727
dataset = Dataset(
2828
name=dataset_name, description=dataset_description, file_path=dataset_file_path
2929
)
30-
# ds = pl.asset.create(dataset)
30+
ds = pl.asset.create(dataset)
3131

3232
# Retrieve assets
3333
ds = pl.asset.get(asset_name=dataset_name, version=0)

samples/hosted/README.md

Lines changed: 13 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
# Quickstart
1+
# Self-hosting
22

3-
This sample ([quickstart.py](quickstart.py)) demonstrates how to use PromptLab to evaluate a simple prompt.
3+
This sample ([hosted.py](hosted.py)) demonstrates how to use self-hosted PromptLab.
44

55
## Installation and Setup
66

7-
It's highly recommended to use a virtual environment (try venv or conda or uv).
7+
To host PromptLab in your environment, please follow this guide - [Self Hosting PromptLab](../../docs/self_hosting.md).
8+
9+
To interact with the hosted PromptLab service, locally install the PromptLab package in a virtual environment (try venv or conda or uv).
810

911
```bash
1012
pip install promptlab
@@ -15,79 +17,15 @@ pip install promptlab
1517
The first step to use PromptLab is to initialize the PromptLab object. Please check [Tracer](../../docs/README.md#tracer) to learn more about the tracer configuration.
1618

1719
```python
18-
tracer_config = {"type": "sqlite", "db_file": "./promptlab.db"}
19-
pl = PromptLab(tracer_config)
20-
```
21-
22-
Once the PromptLab object is ready, you can start the PromptLab Studio to check the assets and experiments.
23-
24-
```python
25-
pl.studio.start(8000)
26-
```
27-
28-
## Create a Prompt Template
29-
30-
A prompt template is a prompt with or without placeholders. Please check [Prompt Template](../../docs/README.md#prompt-template) to learn more about it.
31-
32-
A prompt template has two main attributes - `system_prompt` and `user_prompt`. The sample prompt used in this example is:
33-
34-
```python
35-
prompt_name = "essay_feedback"
36-
prompt_description = "A prompt for generating feedback on essays"
37-
system_prompt = "You are a helpful assistant who can provide feedback on essays."
38-
user_prompt = """The essay topic is - <essay_topic>.
39-
The submitted essay is - <essay>
40-
Now write feedback on this essay."""
41-
prompt_template = PromptTemplate(name=prompt_name, description=prompt_description, system_prompt=system_prompt, user_prompt=user_prompt)
42-
pt = pl.asset.create(prompt_template)
43-
```
44-
45-
Here, `<essay_topic>` and `<essay>` are placeholders that will be replaced with real data before sending to the LLM. PromptLab will search the dataset for columns with these exact names and use their values to replace the corresponding placeholders. Ensure that the dataset contains columns named `essay_topic` and `essay`.
46-
47-
![PromptLab Studio](../../img/studio-pt.png)
48-
49-
## Create Dataset
50-
51-
A dataset is a JSONL file used to design the experiment. Please check [Dataset](../../docs/README.md#dataset) to learn more about it.
52-
53-
```python
54-
dataset_name = "essay_samples"
55-
dataset_description = "dataset for evaluating the essay_feedback prompt"
56-
dataset_file_path = "./samples/data/essay_feedback.jsonl"
57-
dataset = Dataset(name=dataset_name, description=dataset_description, file_path=dataset_file_path)
58-
ds = pl.asset.create(dataset)
59-
```
60-
61-
![PromptLab Studio](../../img/studio-ds.png)
62-
63-
## Create Experiment
64-
65-
An experiment evaluates the outcome of a prompt against a set of metrics for a given dataset. Developers can modify hyperparameters (such as prompt template and models) and compare experiment results to determine the best prompt to deploy in production. Please check [Experiment](../../docs/README.md#experiment) to learn more about it.
66-
67-
In the [quickstart.py](quickstart.py), we are using the prompt template and dataset created in the previous steps to design an experiment. It also uses two evaluation metrics: `semantic_similarity` and `fluency`. Please check [Metric](../../docs/README.md#metric) to learn more about evaluation metrics.
68-
69-
```python
70-
experiment_config = {
71-
"completion_model": completion_model,
72-
"embedding_model": embedding_model,
73-
"prompt_template": pt,
74-
"dataset": ds,
75-
"evaluation": [
76-
{
77-
"metric": "semantic_similarity",
78-
"column_mapping": {"response": "$completion", "reference": "feedback"},
79-
},
80-
{
81-
"metric": "fluency",
82-
"column_mapping": {"response": "$completion"},
83-
},
84-
],
20+
tracer_config = {
21+
"type": "api",
22+
"endpoint": "http://HOST-URL:8001",
23+
"jwt_token": "JWT_TOKEN",
8524
}
86-
pl.experiment.run(experiment_config)
25+
pl = PromptLab(tracer_config)
8726
```
8827

89-
![PromptLab Studio](../../img/studio-exp.png)
90-
91-
Details of the experiment:
28+
- HOST-URL is the URL of the hosted service.
29+
- JWT_TOKEN can be found in the PromptLab page. Copy it from the top right corner profile link.
9230

93-
![PromptLab Studio](../../img/studio-exp-details.png)
31+
Everything else for creating assets and running experiments are same as other examples.

samples/hosted/hosted.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
from promptlab import PromptLab
33
from promptlab.types import PromptTemplate, Dataset
44

5-
# Initialize PromptLab with api tracer
5+
# Initialize PromptLab with remote tracer
66
tracer_config = {
7-
"type": "api",
7+
"type": "remote",
88
"endpoint": "http://HOST-URL:8001",
9-
"jwt_token": "your_jwt_token_here",
9+
"jwt_token": "JWT_TOKEN",
1010
}
1111
pl = PromptLab(tracer_config)
1212

@@ -68,6 +68,3 @@
6868

6969
# # Run the experiment asynchronously
7070
asyncio.run(pl.experiment.run_async(experiment_config))
71-
72-
# # Start the PromptLab Studio to view results
73-
# asyncio.run(pl.studio.start_async(8000))

0 commit comments

Comments
 (0)