Skip to content

Commit 7dbf70e

Browse files
committed
Removes hardcoded paths
1 parent 1e1725a commit 7dbf70e

9 files changed

Lines changed: 67 additions & 133 deletions

File tree

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ cython_debug/
173173
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174174
# and can be added to the global gitignore or merged into this file. For a more nuclear
175175
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
176-
#.idea/
176+
.idea/
177177

178178
# Abstra
179179
# Abstra is an AI-powered process automation framework.
@@ -210,4 +210,7 @@ __marimo__/
210210
# Cursor
211211
.cursor/
212212
AGENTS.md
213-
spec/
213+
spec/
214+
215+
# macOS
216+
.DS_Store

examples/opik_vs_elastic/.env.example

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ KIBANA_PASSWORD=<PLACEHOLDER>
1515
CONNECTOR_ID=gemini-25-flash # alternatives: eis-google-gemini-3-1-pro # gemini-2.5-flash-preconfigured
1616
EVALUATION_CONNECTOR_ID=gemini-25-pro # alternatives: eis-openai-gpt-5-4 # gemini-2.5-pro-preconfigured
1717

18+
# GCS dataset paths. Only needed when USE_GCP = True:
19+
WIX_QA_DATASET_PATH=gs://<BUCKET>/queries/wix_qa.csv
20+
WIX_KNOWLEDGE_BASE_PATH=gs://<BUCKET>/knowledge_bases/wix_knowledge_base.csv
21+
1822
# Opik:
1923
OPIK_API_KEY=<PLACEHOLDER>
20-
OPIK_PROJECT_NAME=poc-evals-agent-builder
21-
OPIK_WORKSPACE=context-and-research-playground
24+
OPIK_PROJECT_NAME=<PLACEHOLDER>
25+
OPIK_WORKSPACE=<PLACEHOLDER>
2226
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api
2327

2428
# Tracing / Elastic evals:

examples/opik_vs_elastic/Instructions.md

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,13 @@ This PoC demonstrates how Agent Builder can use the `kbn/evals` Python SDK for d
44
management, experiment tracing, and evaluator score ingestion—capabilities previously
55
handled through Opik—and how to build custom evaluators.
66

7-
## 1. Dependencies
8-
9-
`run2.py` uses external Orca evaluators. Clone the `orca` repo as a sibling of
10-
`elastic-evals-sdk-python/` so the layout is:
11-
12-
```
13-
── elastic-evals-sdk-python/
14-
── orca/
15-
```
16-
17-
## 2. Python environment
7+
## 1. Python environment
188

199
From the SDK repo root:
2010

2111
```bash
2212
uv sync --group dev --extra runner --extra poc
2313
source .venv/bin/activate
24-
uv pip install --editable ../orca
2514
```
2615

2716
Register the venv as a Jupyter kernel. Only needed if your notebook/IDE doesn't pick up `.venv` automatically:
@@ -30,7 +19,7 @@ Register the venv as a Jupyter kernel. Only needed if your notebook/IDE doesn't
3019
uv run --no-sync python -m ipykernel install --user --name elastic-evals-poc
3120
```
3221

33-
## 3. Secrets
22+
## 2. Secrets
3423

3524
Create `.env` next to `.env.example`:
3625

@@ -39,19 +28,18 @@ cp examples/opik_vs_elastic/.env.example examples/opik_vs_elastic/.env
3928
```
4029

4130
Set the local URLs, `ELASTICSEARCH_API_KEY`, `KIBANA_API_KEY`, `CONNECTOR_ID`,
42-
and `EVALUATION_CONNECTOR_ID`. The Opik variables are used when `run2.py` runs
43-
the tracked external Orca evaluators. Retrieve internal credentials from Vault
44-
when needed:
31+
and `EVALUATION_CONNECTOR_ID`. Retrieve internal credentials from Vault when
32+
needed:
4533

4634
```bash
4735
VAULT_ADDR=https://secrets.elastic.co:8200 vault login --method oidc
4836
```
4937

5038
The public Hugging Face dataset does not require an API key.
5139

52-
## 4. Data source and sample size
40+
## 3. Data source and sample size
5341

54-
Set these values near the top of the script before running it:
42+
Set these values near the top of the script you're running:
5543

5644
```python
5745
USE_ENTIRE_DATASET = False
@@ -65,19 +53,30 @@ files. `DATASET_SAMPLE_SIZE` is ignored when `USE_ENTIRE_DATASET` is `True`.
6553
The entire knowledge base is always indexed.
6654

6755
The available examples and their order may differ between Hugging Face and GCS.
68-
`run.py` defaults to 10 examples and `run2.py` defaults to 3.
56+
Both scripts default to 10 examples.
6957

70-
## 5. GCP access
58+
## 4. GCP access
7159

7260
Only needed when `USE_GCP = True`. Authenticate with your `@elastic.co` account:
7361

7462
```bash
7563
gcloud auth application-default login
7664
```
7765

78-
## 6. Local stack
66+
## 5. Local stack
7967

80-
Use a separate terminal for each service and leave it running.
68+
The steps below run from a local Kibana checkout. If you don't have one already,
69+
clone [elastic/kibana](https://github.com/elastic/kibana) and bootstrap it:
70+
71+
```bash
72+
git clone https://github.com/elastic/kibana.git
73+
cd kibana
74+
nvm use
75+
yarn kbn bootstrap
76+
```
77+
78+
Run the remaining commands from that `kibana` directory, using a separate
79+
terminal for each service and leaving it running.
8180

8281
In `kibana/config/kibana.dev.yml`, enable evals and OTLP tracing:
8382

@@ -100,7 +99,6 @@ uiSettings:
10099
### Elasticsearch
101100
102101
```bash
103-
cd /Users/mafaldasavelho/Documents/work-repos/kibana-fork/kibana
104102
nvm use
105103
yarn es snapshot --license trial
106104
```
@@ -118,7 +116,6 @@ curl --user elastic:changeme \
118116
Start Docker Desktop, then run:
119117

120118
```bash
121-
cd /Users/mafaldasavelho/Documents/work-repos/kibana-fork/kibana
122119
nvm use
123120
node scripts/edot_collector.js
124121
```
@@ -134,7 +131,6 @@ docker ps \
134131
### Kibana
135132

136133
```bash
137-
cd /Users/mafaldasavelho/Documents/work-repos/kibana-fork/kibana
138134
nvm use
139135
node scripts/kibana --dev --verbose
140136
```
@@ -149,7 +145,7 @@ curl --silent --show-error \
149145
http://localhost:5601/dev/api/status
150146
```
151147

152-
## 7. Elasticsearch API key
148+
## 6. Elasticsearch API key
153149

154150
Both scripts authenticate to Elasticsearch and Kibana. Create an API key against
155151
the local cluster and paste the `encoded` field into both
@@ -164,7 +160,9 @@ This step is optional if both variables already contain a valid key for the
164160
current cluster. API keys are cluster-specific, so a key from a previous local
165161
Elasticsearch snapshot returns `401`.
166162

167-
## 8. Run the PoC
163+
## 7. Run the PoC
164+
165+
Run both scripts from the SDK repo root.
168166

169167
### `run.py`: managed workflow
170168

@@ -173,15 +171,14 @@ Demonstrates the higher-level workflow. It uses
173171
run SDK-side and custom evaluators, and ingest their scores.
174172

175173
```bash
176-
cd /Users/mafaldasavelho/Documents/work-repos/kibana-fork/evals-python-sdk/elastic-evals-sdk-python
177174
uv run --no-sync python -m examples.opik_vs_elastic.run
178175
```
179176

180177
### `run2.py`: granular workflow
181178

182179
Demonstrates the lower-level workflow without `run_experiment()`. It directly
183-
coordinates the Dataset, Evaluators, and Score Ingestion APIs, runs the custom
184-
Document Recall evaluator, and attaches external Orca scores.
180+
coordinates the Dataset, Evaluators, and Score Ingestion APIs, and runs the
181+
custom Document Recall evaluator alongside the Kibana ones.
185182

186183
```bash
187184
uv run --no-sync python -m examples.opik_vs_elastic.run2

examples/opik_vs_elastic/helpers/data.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515
from examples.opik_vs_elastic.helpers.helpers import (
1616
GROUND_TRUTH_COLUMN,
17-
WIX_KNOWLEDGE_BASE_PATH,
18-
WIX_QA_DATASET_PATH,
17+
WIX_KNOWLEDGE_BASE_PATH_ENV,
18+
WIX_QA_DATASET_PATH_ENV,
1919
_parse_relevant_doc_ids, # noqa: PLC2701
20+
get_dataset_path,
2021
)
2122

2223
HUGGING_FACE_DATASET = "Wix/WixQA"
@@ -85,9 +86,9 @@ def _normalize_knowledge_base(dataframe: pd.DataFrame, *, source: str) -> pd.Dat
8586
def load_wix_data(*, use_gcp: bool) -> tuple[pd.DataFrame, pd.DataFrame]:
8687
"""Load WixQA examples and their knowledge-base corpus."""
8788
if use_gcp:
88-
qa = _normalize_gcs_qa(pd.read_csv(WIX_QA_DATASET_PATH))
89+
qa = _normalize_gcs_qa(pd.read_csv(get_dataset_path(WIX_QA_DATASET_PATH_ENV)))
8990
knowledge_base = _normalize_knowledge_base(
90-
pd.read_csv(WIX_KNOWLEDGE_BASE_PATH),
91+
pd.read_csv(get_dataset_path(WIX_KNOWLEDGE_BASE_PATH_ENV)),
9192
source="GCS Wix knowledge base",
9293
)
9394
else:

examples/opik_vs_elastic/helpers/helpers.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,30 @@
77
from __future__ import annotations
88

99
import ast
10+
import os
1011
from pathlib import Path
1112
from typing import Any
1213

1314
ENV_PATH = Path(__file__).parent.parent / ".env"
1415
INDEX_NAME = "wix_knowledge_base"
1516
SEARCH_TOOL_ID = "wix-knowledge-search"
1617
GROUND_TRUTH_COLUMN = "gt_customer_support_wix_knowledge_base"
17-
WIX_QA_DATASET_PATH = "gs://agent-builder-data-science-datasets/queries/wix_qa.csv"
18-
WIX_KNOWLEDGE_BASE_PATH = (
19-
"gs://agent-builder-data-science-datasets/knowledge_bases/cleaned/"
20-
"customer_support/wix_knowledge_base/wix_knowledge_base.csv"
21-
)
18+
WIX_QA_DATASET_PATH_ENV = "WIX_QA_DATASET_PATH"
19+
WIX_KNOWLEDGE_BASE_PATH_ENV = "WIX_KNOWLEDGE_BASE_PATH"
2220
AGENT_ID = "wix-eval-agent"
2321
AGENT_NAME = "Wix Agent"
2422
AGENT_INSTRUCTIONS = "Answer questions using the Wix knowledge base."
2523
SEARCH_TOOL_DESCRIPTION = "Search the Wix knowledge base articles."
2624

2725

26+
def get_dataset_path(variable: str) -> str:
27+
"""Read a GCS dataset path from the environment. Only needed when USE_GCP is True."""
28+
value = os.getenv(variable)
29+
if not value:
30+
raise ValueError(f"Set {variable} in .env to load the Wix data from GCS, or run with USE_GCP = False.")
31+
return value
32+
33+
2834
def _parse_relevant_doc_ids(value: Any) -> list[str]:
2935
if value is None:
3036
return []

examples/opik_vs_elastic/run.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,8 @@ async def main() -> None:
196196
)
197197

198198
# [IMPORTANT] NOTE: if the dataset doesn't exist, then it creates a new one with the given name. If the dataset already exists,
199-
# then it's going to cross-check examples and fully rewrite/update accordingly. Example of a response after sending
200-
# the request for the same dataset, but with only the first 5 examples:
201-
# UpsertDatasetResponse(dataset_id='0b5ee7b6-9f4a-5c66-b196-6b8cc5154eec', added=0, removed=47, unchanged=5)
199+
# then it's going to cross-check examples and fully rewrite/update accordingly: re-running with a smaller sample
200+
# removes the examples that are no longer present.
202201

203202
upsert_dataset_response = await elastic_evals_client._datasets_client.upsert(
204203
name=DATASET_NAME,

examples/opik_vs_elastic/run2.py

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
from typing import Any, cast
1414

1515
from dotenv import load_dotenv
16-
from orca.evaluation.evaluators.retrieval import F1AtK, PrecisionAtK, RecallAtK # type: ignore[import-untyped]
17-
from orca.evaluation.evidence.types import EvaluationEvidence, EvidenceArtifact # type: ignore[import-untyped]
1816

1917
from elastic_evals.api import (
2018
Environment,
@@ -305,60 +303,6 @@ async def _resolve_subject(
305303
)
306304

307305

308-
def _orca_evidence(document_ids: list[str]) -> EvaluationEvidence:
309-
return EvaluationEvidence(
310-
artifacts=[
311-
EvidenceArtifact(
312-
type="resource_list",
313-
data={"resources": [{"reference": {"id": document_id}} for document_id in document_ids]},
314-
)
315-
]
316-
)
317-
318-
319-
def _evaluate_with_orca(
320-
executed_runs: list[dict[str, Any]],
321-
) -> list[tuple[dict[str, Any], EvaluationRun]]:
322-
scored: list[tuple[dict[str, Any], EvaluationRun]] = []
323-
for executed in executed_runs:
324-
run_data = executed["data"]
325-
retrieved = _extract_retrieved_doc_ids(run_data.output, tool_id=SEARCH_TOOL_ID)
326-
relevant = _to_string_list((run_data.metadata or {}).get("relevant_doc_ids"))
327-
evidence = _orca_evidence(retrieved)
328-
harness = {
329-
"name": "agent_builder_converse",
330-
"adapter": "run2_task_output",
331-
"locator": None,
332-
"metadata": {"trace_id": run_data.trace_id},
333-
}
334-
for metric in (PrecisionAtK(k=3), RecallAtK(k=3), F1AtK(k=3)):
335-
score = metric.score(
336-
evidence=evidence.model_dump(mode="json"),
337-
harness=harness,
338-
relevant_doc_ids=relevant,
339-
)
340-
if isinstance(score, list):
341-
raise TypeError(f"Orca evaluator {metric.name} returned multiple scores")
342-
scored.append(
343-
_scored_run(
344-
executed,
345-
name=f"orca.{score.name}",
346-
result=EvaluationResult(
347-
score=None if score.scoring_failed else float(score.value),
348-
label="unavailable" if score.scoring_failed else None,
349-
explanation=score.reason,
350-
metadata={
351-
"source": "orca",
352-
"scoring_failed": score.scoring_failed,
353-
"k": 3,
354-
},
355-
),
356-
)
357-
)
358-
print(f"Orca evaluation complete: scores={len(scored)}")
359-
return scored
360-
361-
362306
async def main() -> None:
363307
print("[1] Load configuration and prepare the knowledge base")
364308
load_dotenv(ENV_PATH)
@@ -597,7 +541,7 @@ async def document_recall_runner() -> EvaluationResult:
597541
}
598542
connector_id = config.evaluator_connector_id or config.connector_id
599543

600-
print("[6] Create the experiment with the initial scores")
544+
print("[6] Create the experiment and ingest the scores")
601545
await _export_scores(
602546
scores_client,
603547
export_context,
@@ -613,15 +557,6 @@ async def document_recall_runner() -> EvaluationResult:
613557
scored_runs=document_recall_scores,
614558
)
615559

616-
print("[7] Run external Orca evaluators and attach their scores")
617-
await _export_scores(
618-
scores_client,
619-
export_context,
620-
label="External Orca scores",
621-
evaluator_model_id="orca",
622-
scored_runs=_evaluate_with_orca(executed_runs),
623-
)
624-
625560
print(f"Finished.\nExperiment '{EXPERIMENT_NAME}' is ready in Kibana.\nExperiment ID: {experiment_id}")
626561

627562

tests/examples/test_opik_vs_elastic.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import pytest
1212

1313
pytest.importorskip("pandas")
14-
pytest.importorskip("orca")
1514

1615
from elastic_evals.api import ( # noqa: E402
1716
Environment,
@@ -241,25 +240,6 @@ def test_granular_score_request_preserves_experiment_name_and_batches_scores() -
241240
assert all(score.task.trace_id == "1" * 32 for score in payload.scores)
242241

243242

244-
def test_granular_orca_scores_are_namespaced_and_use_stored_run() -> None:
245-
scored_runs = granular_run._evaluate_with_orca([_executed_run()])
246-
247-
assert [evaluation.name for _, evaluation in scored_runs] == [
248-
"orca.precision-at-3",
249-
"orca.recall-at-3",
250-
"orca.f1-at-3",
251-
]
252-
assert [evaluation.result.score for _, evaluation in scored_runs if evaluation.result] == [
253-
pytest.approx(1 / 3),
254-
pytest.approx(1 / 2),
255-
pytest.approx(0.4),
256-
]
257-
assert all(
258-
evaluation.result and evaluation.result.metadata and evaluation.result.metadata["source"] == "orca"
259-
for _, evaluation in scored_runs
260-
)
261-
262-
263243
def test_granular_evaluator_configs_use_reference_data_and_llm_connector() -> None:
264244
definitions = [
265245
EvaluatorDefinition(

0 commit comments

Comments
 (0)