Skip to content

Commit e2d8ecc

Browse files
Document Opik integration and version prompts in Opik Prompt Library
1 parent 6e8f070 commit e2d8ecc

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

docs/opik.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Opik integration
2+
3+
We trace the FAQ automation with Opik and score it with Opik experiments.
4+
The local eval framework (`faq_automation/evals/`) stays the source of truth;
5+
Opik mirrors it. To go back, delete the Opik files and env vars below.
6+
7+
## Platforms
8+
9+
| | Opik Cloud (shared, Friday demos) | Local (private dev) |
10+
|---|---|---|
11+
| URL | `https://www.comet.com/opik/api` | `http://localhost:5173/api` (`cd ../opik && ./opik.sh`) |
12+
| Workspace | `default` | `default` |
13+
| Auth | `OPIK_API_KEY` (GitHub secret in CI, never in git) | none |
14+
| Automation project | `faq-automation-ci` | `faq-automation` |
15+
| Assistant project | `faq-assistant-lambda` | `faq-assistant` |
16+
17+
## Tracing
18+
19+
`faq_automation/rag_agent.py` (full SDK, 2 lines + 1 decorator):
20+
21+
```python
22+
from opik import track
23+
from opik.integrations.openai import track_openai
24+
25+
self.openai_client = track_openai(OpenAI(api_key=openai_api_key))
26+
27+
@track
28+
def process_proposal(self, ...): ...
29+
```
30+
31+
The Slack worker (`faq-assistant`, separate repo) intentionally does NOT ship
32+
the SDK — Lambda stays zero-dependency. It uses a ~140-line stdlib-only
33+
`@track` drop-in (`opik_lite.py`) that POSTs one trace per answer. Same
34+
annotation, one-line import swap.
35+
36+
## Evals on Opik
37+
38+
`faq_automation/evals/opik_eval.py` (additive port, same cases and check
39+
predicates as `runner.py`):
40+
41+
```bash
42+
source .env # OPIK_API_KEY
43+
# push cases once:
44+
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api OPIK_WORKSPACE=default \
45+
OPIK_PROJECT_NAME=faq-automation-ci \
46+
uv run --project faq_automation python -m faq_automation.evals.opik_eval --push-dataset
47+
# before/after (deterministic action_match + placement_match, no judge cost):
48+
uv run --project faq_automation python -m faq_automation.evals.opik_eval \
49+
--experiment friday-before --num-results 1
50+
uv run --project faq_automation python -m faq_automation.evals.opik_eval \
51+
--experiment friday-after --num-results 5
52+
```
53+
54+
## Prompts in Opik
55+
56+
We do NOT load prompts from Opik — `rag_agent.SYSTEM_PROMPT` and
57+
`PROMPT_TEMPLATE` remain the single source of truth. We mirror them into the
58+
Prompt Library so every version sits next to the traces/experiments that used
59+
it. Re-running with unchanged templates creates no new version:
60+
61+
```bash
62+
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api OPIK_WORKSPACE=default \
63+
OPIK_PROJECT_NAME=faq-automation-ci \
64+
uv run --project faq_automation python scripts/push_prompts_to_opik.py
65+
```
66+
67+
Library entries: `faq-triage-system`, `faq-triage-user-template`
68+
(metadata records the source symbol and the model from `DEFAULT_MODEL`).
69+
70+
## History backfill
71+
72+
`scripts/backfill_opik_history.py` logs past `faq-proposal` issues as
73+
`faq-proposal-triage` traces with real timestamps: input = original issue +
74+
regenerated retrieval context, output = the actual historical decision from
75+
the bot PR or close comment (or MANUAL). `--dry-run` first, `--limit N` to
76+
bound it.
77+
78+
## CI wiring
79+
80+
`.github/workflows/faq-automation.yml` ("Process FAQ with AI" step) sets
81+
`OPIK_URL_OVERRIDE` / `OPIK_WORKSPACE` / `OPIK_PROJECT_NAME=faq-automation-ci`
82+
and `OPIK_API_KEY` from secrets. Without a key the SDK degrades to no-op —
83+
automation never breaks because of tracing.

scripts/push_prompts_to_opik.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
"""Version our prompts in the Opik Prompt Library (without using them from there).
3+
4+
Single source of truth stays faq_automation/rag_agent.py — this script only
5+
mirrors SYSTEM_PROMPT and PROMPT_TEMPLATE into Opik so every version is
6+
stored next to the traces and experiments that used it. Re-running with
7+
unchanged templates creates nothing (Opik versions on diff).
8+
9+
Respects OPIK_URL_OVERRIDE / OPIK_WORKSPACE / OPIK_PROJECT_NAME / OPIK_API_KEY.
10+
11+
Usage:
12+
source .env
13+
# Cloud:
14+
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api OPIK_WORKSPACE=default \\
15+
OPIK_PROJECT_NAME=faq-automation-ci \\
16+
uv run --project faq_automation python scripts/push_prompts_to_opik.py
17+
# Local (http://localhost:5173):
18+
OPIK_PROJECT_NAME=faq-automation \\
19+
uv run --project faq_automation python scripts/push_prompts_to_opik.py
20+
"""
21+
22+
import os
23+
import sys
24+
from pathlib import Path
25+
26+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
27+
28+
os.environ.setdefault("OPIK_URL_OVERRIDE", "http://localhost:5173/api")
29+
os.environ.setdefault("OPIK_WORKSPACE", "default")
30+
os.environ.setdefault("OPIK_PROJECT_NAME", "faq-automation-ci")
31+
32+
PROMPTS = [
33+
("faq-triage-system",
34+
"System prompt: triage rules for NEW/UPDATE/DUPLICATE/WRONG_COURSE.",
35+
"SYSTEM_PROMPT"),
36+
("faq-triage-user-template",
37+
"User prompt template: course, catalog, entry, search results, sections.",
38+
"PROMPT_TEMPLATE"),
39+
]
40+
41+
42+
def main():
43+
from opik import Opik
44+
from faq_automation.rag_agent import DEFAULT_MODEL
45+
import faq_automation.rag_agent as rag
46+
47+
project = os.environ.get("OPIK_PROJECT_NAME", "faq-automation-ci")
48+
client = Opik(project_name=project)
49+
for name, description, attr in PROMPTS:
50+
template = getattr(rag, attr)
51+
prompt = client.create_prompt(
52+
name=name,
53+
prompt=template,
54+
description=description,
55+
metadata={"source": "faq_automation/rag_agent.py:" + attr,
56+
"format": "python-str-format",
57+
"model": DEFAULT_MODEL},
58+
tags=["faq-automation", "triage"],
59+
project_name=project,
60+
)
61+
print(f"{name}: commit {prompt.commit} ({project})")
62+
63+
64+
if __name__ == "__main__":
65+
main()

0 commit comments

Comments
 (0)