Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
monorepo_root = true

[monorepo]
config_roots = [".", "cli", "typescript", "python"]
config_roots = [".", "cli", "typescript", "python", "python-examples"]

[tools]
# keep this version inline with the version declared in .github/workflows/typescript
Expand Down
1 change: 1 addition & 0 deletions python-examples/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
10 changes: 10 additions & 0 deletions python-examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Quantiles Python SDK Examples

This directory in the Quantiles monorepo contains examples to illustrate how to build [`custom_code` evals](https://quantiles.io/documentation/custom-evaluations) on the Quantiles platform using the [Python SDK](https://quantiles.io/documentation/reference/python-sdk).

The following `custom_code` evals are available along with a [`quantiles.toml` configuration file](https://quantiles.io/documentation/configuration) to make them easily runnable. They are detailed below:

| Eval | `qt run` command | Source file | Notes |
| --- | --- | --- | --- |
| PubMedQA | `qt run custom_pubmedqa` | [`src/pubmedqa.py`](./src/pubmedqa.py) | [PubMedQA](https://pubmedqa.github.io/) is a biomedical question-answering benchmark. This file shows how a custom evaluation can be implemented, but PubMedQA is also available as a built-in Quantiles benchmark: `qt run pubmedqa`. |
| Example prompt eval | `qt run custom_prompt_eval` | [`src/prompt_eval.py`](./src/prompt_eval.py) | This is a [`custom_code`](https://quantiles.io/documentation/custom-evaluations) eval to illustrate how you might use Quantiles to evaluate the performance of a customer support chatbot with various system prompts |
5 changes: 5 additions & 0 deletions python-examples/mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[tasks.run-pubmedqa]
run = "qt run custom_pubmedqa"

[tasks.run_prompt_eval]
run = "qt run custom_prompt_eval"
10 changes: 10 additions & 0 deletions python-examples/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "python-examples"
version = "0.1.0"
description = "Example custom evals using the Quantiles Python SDK"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pydantic>=2.13.4",
"quantiles>=0.1.0,<1.0.0",
]
10 changes: 10 additions & 0 deletions python-examples/quantiles.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# PubMedQA built with the Python SDK (not using the built-in benchmark)
[benchmarks.custom_pubmedqa]
type = "custom_code"
command = ["uv", "run", "src/pubmedqa.py"]
input = { model_name = "openai:gpt_5_nano", num_examples = 500 }

# PubMedQA built with the Python SDK (not using the built-in benchmark)
[benchmarks.custom_prompt_eval]
type = "custom_code"
command = ["uv", "run", "src/prompt_eval.py"]
120 changes: 120 additions & 0 deletions python-examples/src/prompt_eval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from typing import Literal, TypedDict, cast

from quantiles import (
JsonValue,
Statistics,
emit,
entrypoint,
step,
workflow,
)
from quantiles.workflow_context import WorkflowContext

Label = Literal["billing", "bug", "how_to"]


class EvalInput(TypedDict):
prompt_version: str


class EvalCase(TypedDict):
id: str
ticket: str
expected: Label


class CaseResult(TypedDict):
id: str
expected: Label
prediction: Label
correct: bool
tokens_used: int


CASES: list[EvalCase] = [
{
"id": "double-charge",
"ticket": "I was charged twice for my subscription renewal.",
"expected": "billing",
},
{
"id": "csv-crash",
"ticket": "The app crashes every time I upload a CSV file.",
"expected": "bug",
},
{
"id": "invite-teammate",
"ticket": "Can you show me how to invite another teammate?",
"expected": "how_to",
},
]


async def evaluate_case(
ctx: WorkflowContext,
item: EvalCase,
prompt_version: str,
) -> CaseResult:
prediction = cast(
Label,
await step(
ctx,
step_key=f"case:{item['id']}",
input_value={
"sample_id": item["id"],
"ticket": item["ticket"],
"expected": item["expected"],
"prompt_version": prompt_version,
},
execute=lambda: call_model(item["ticket"]),
),
)

return {
"id": item["id"],
"expected": item["expected"],
"prediction": prediction,
"correct": prediction == item["expected"],
"tokens_used": len(item["ticket"].split()),
}


async def handler(input_value: EvalInput | None, ctx: WorkflowContext) -> JsonValue:
prompt_version = (input_value or {}).get("prompt_version", "A")
results: list[CaseResult] = []
for item in CASES:
results.append(await evaluate_case(ctx, item, prompt_version))

correct = sum(1 for result in results if result["correct"])
accuracy = Statistics.accuracy(correct, len(results))
tokens_used = sum(result["tokens_used"] for result in results)

await emit(ctx, "accuracy", accuracy)
await emit(ctx, "correct_count", float(correct))
await emit(ctx, "total_count", float(len(results)))
await emit(ctx, "tokens_used", float(tokens_used), "tokens")

return cast(
JsonValue,
# the below value is a valid JsonValue
{
"accuracy": accuracy,
"correct_count": correct,
"total_count": len(results),
"results": results,
},
)


async def call_model(ticket: str) -> Label:
text = ticket.lower()
if "crash" in text:
return "bug"
if "invite" in text or "show me" in text:
return "how_to"
return "billing"


if __name__ == "__main__":
support_triage = workflow("custom_prompt_eval", handler)
entrypoint(support_triage)
Loading
Loading