Skip to content

Commit e26263c

Browse files
committed
feat(gooddata-eval): support requesting a per-message reasoning effort
GoodData Cloud's chat-conversations endpoint accepts an experimental options.reasoningEffort (LOW/MEDIUM/HIGH) on each POST .../messages call. Thread it through ChatClient.send_message/ask, all 7 agentic evaluators, the CLI dispatcher, and RunConfig, exposed as --reasoning-effort / GD_EVAL_REASONING_EFFORT. Not persisted server-side, so every message the client sends must carry it (unlike agentId, which is set once at conversation creation).
1 parent acfcc1a commit e26263c

16 files changed

Lines changed: 346 additions & 27 deletions

packages/gooddata-eval/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Both provider name and provider id are accepted as the prefix.
9292
|---|---|---|
9393
| `--runs K` | `2` | Independent runs per item (pass@K). An item passes if any run passes. |
9494
| `--concurrency K` | `1` | Number of items evaluated concurrently. `1` = sequential (default). Increase to load-test the agent under simultaneous requests. Progress output interleaves when K > 1. |
95+
| `--reasoning-effort {LOW,MEDIUM,HIGH}` || Requested LLM reasoning effort, sent with every message this run makes (or set `GD_EVAL_REASONING_EFFORT`). See [Requesting a reasoning effort](#requesting-a-reasoning-effort) below. |
9596

9697
#### Output
9798

@@ -106,6 +107,25 @@ Both provider name and provider id are accepted as the prefix.
106107
|---|---|
107108
| `--langfuse` | Log scores and traces to Langfuse after each item. Requires `--langfuse-dataset`. Creates one named experiment run per model (`gd-eval-{timestamp}-{model}`). Requires `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST`. |
108109

110+
### Requesting a reasoning effort
111+
112+
GoodData Cloud has an experimental per-message `reasoningEffort` option (`LOW`, `MEDIUM`, or `HIGH`) that hints how much the LLM should reason before answering. `gd-eval` can send it on every message it makes:
113+
114+
```bash
115+
# One-off, via flag
116+
gd-eval run --workspace my-ws --dataset ./data --reasoning-effort HIGH
117+
118+
# Session-wide, via env var
119+
export GD_EVAL_REASONING_EFFORT=LOW
120+
gd-eval run --workspace my-ws --dataset ./data
121+
```
122+
123+
Things to know before using it:
124+
125+
- **Not persisted server-side.** The setting applies only to the messages this run sends — it is not saved as a conversation or workspace default. Every `gd-eval` message in the run carries the value; nothing else on the server is affected.
126+
- **Feature-flag gated.** GoodData Cloud must have the reasoning-effort feature enabled for the org; when it isn't, the value is ignored and the platform falls back to `MEDIUM` regardless of what was requested.
127+
- **A hint, not a hard budget.** Providers with adaptive-thinking models (e.g. Anthropic, Bedrock) treat the value as a hint rather than an exact token allocation, so actual reasoning depth can still vary by model.
128+
109129
### JSON report shape
110130

111131
The JSON report always uses the nested multi-model shape:

packages/gooddata-eval/src/gooddata_eval/cli/agentic_runner.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def _dispatch_agentic(
8080
langfuse: Any,
8181
run_ts: str,
8282
model_version_override: str | None,
83+
reasoning_effort: str | None = None,
8384
) -> None:
8485
"""Call the appropriate evaluate_agentic_* function for the item's test_kind."""
8586
kind = item.test_kind
@@ -100,6 +101,7 @@ def _dispatch_agentic(
100101
question=item.question,
101102
expected_outputs=_parse_visualization_expected(eo),
102103
k=k,
104+
reasoning_effort=reasoning_effort,
103105
**lf_kw,
104106
)
105107
elif kind == "agentic_metric_skill":
@@ -110,6 +112,7 @@ def _dispatch_agentic(
110112
question=item.question,
111113
expected_output=eo if isinstance(eo, (dict, list)) else {},
112114
k=k,
115+
reasoning_effort=reasoning_effort,
113116
**lf_kw,
114117
)
115118
elif kind == "agentic_alert_skill":
@@ -120,6 +123,7 @@ def _dispatch_agentic(
120123
question=item.question,
121124
expected_output=eo if isinstance(eo, dict) else {},
122125
k=k,
126+
reasoning_effort=reasoning_effort,
123127
**lf_kw,
124128
)
125129
elif kind == "agentic_search":
@@ -133,6 +137,7 @@ def _dispatch_agentic(
133137
question=item.question,
134138
expected_tool_call=expected_args,
135139
k=k,
140+
reasoning_effort=reasoning_effort,
136141
**lf_kw,
137142
)
138143
elif kind == "agentic_general_question":
@@ -143,6 +148,7 @@ def _dispatch_agentic(
143148
question=item.question,
144149
expected_output=eo if isinstance(eo, str) else str(eo),
145150
k=k,
151+
reasoning_effort=reasoning_effort,
146152
**lf_kw,
147153
)
148154
elif kind == "agentic_guardrail":
@@ -153,6 +159,7 @@ def _dispatch_agentic(
153159
question=item.question,
154160
expected_output=eo if isinstance(eo, str) else str(eo),
155161
k=k,
162+
reasoning_effort=reasoning_effort,
156163
**lf_kw,
157164
)
158165
elif kind == "agentic_conversation":
@@ -162,6 +169,7 @@ def _dispatch_agentic(
162169
token=token,
163170
workspace_id=workspace_id,
164171
fixture=ConversationFixture.model_validate(fixture_data),
172+
reasoning_effort=reasoning_effort,
165173
**lf_kw,
166174
)
167175
else:
@@ -180,6 +188,7 @@ def run_agentic_items(
180188
run_ts: str,
181189
on_item_start: Any = None,
182190
on_item_done: Any = None,
191+
reasoning_effort: str | None = None,
183192
) -> EvalReport:
184193
"""Run agentic items through evaluate_agentic_* and return an EvalReport."""
185194
langfuse = make_langfuse_client() if use_langfuse else None
@@ -202,7 +211,7 @@ def run_agentic_items(
202211
)
203212
t0 = time.perf_counter()
204213
try:
205-
_dispatch_agentic(item, host, token, workspace_id, k, langfuse, run_ts, model_version)
214+
_dispatch_agentic(item, host, token, workspace_id, k, langfuse, run_ts, model_version, reasoning_effort)
206215
item_report.pass_at_k = True
207216
item_report.runs = k
208217
except AssertionError as exc:

packages/gooddata-eval/src/gooddata_eval/cli/main.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""`gd-eval` command-line entry point."""
33

44
import argparse
5+
import os
56
import sys
67
import threading
78
from datetime import datetime, timezone
@@ -37,14 +38,15 @@ class _RoutingBackend:
3738
else uses the conversational chat endpoint.
3839
"""
3940

40-
def __init__(self, chat: ChatClient, summary: SummaryClient):
41+
def __init__(self, chat: ChatClient, summary: SummaryClient, *, reasoning_effort: str | None = None):
4142
self._chat = chat
4243
self._summary = summary
44+
self._reasoning_effort = reasoning_effort
4345

4446
def ask(self, item: DatasetItem) -> ChatResult:
4547
if item.test_kind == _SUMMARY_TEST_KIND:
4648
return self._summary.ask(item)
47-
return self._chat.ask(item)
49+
return self._chat.ask(item, reasoning_effort=self._reasoning_effort)
4850

4951
def close(self) -> None:
5052
for backend in (self._chat, self._summary):
@@ -109,6 +111,17 @@ def _build_parser() -> argparse.ArgumentParser:
109111
action="store_true",
110112
help="Log scores and traces to Langfuse (requires --langfuse-dataset and LANGFUSE_* env vars).",
111113
)
114+
run.add_argument(
115+
"--reasoning-effort",
116+
dest="reasoning_effort",
117+
choices=["LOW", "MEDIUM", "HIGH"],
118+
default=None,
119+
help=(
120+
"Requested LLM reasoning effort for this run's messages (or set GD_EVAL_REASONING_EFFORT). "
121+
"Experimental GoodData feature, gated behind an org-level flag — when disabled, the value is "
122+
"ignored and MEDIUM is used. Not persisted server-side: applies only to messages this run sends."
123+
),
124+
)
112125
models_cmd = sub.add_parser("models", help="List LLM providers and models configured in the org.")
113126
models_cmd.add_argument("--host", help="GoodData host URL.")
114127
models_cmd.add_argument("--token", help="API token (or set GOODDATA_TOKEN).")
@@ -333,6 +346,7 @@ def on_langfuse_item_done(
333346
run_ts=run_ts,
334347
on_item_start=on_item_start,
335348
on_item_done=on_item_done,
349+
reasoning_effort=config.reasoning_effort,
336350
)
337351

338352
# --- non-agentic items (single-turn, use Evaluator) ---
@@ -344,6 +358,7 @@ def on_langfuse_item_done(
344358
preserve_failed=config.preserve_failed,
345359
),
346360
SummaryClient(host=config.host, token=config.token, workspace_id=config.workspace_id),
361+
reasoning_effort=config.reasoning_effort,
347362
)
348363
try:
349364
single_report = run_items(
@@ -433,6 +448,7 @@ def main(argv: list[str] | None = None) -> int:
433448
quiet=args.quiet,
434449
kind=args.kind,
435450
preserve_failed=args.preserve_failed,
451+
reasoning_effort=args.reasoning_effort or os.environ.get("GD_EVAL_REASONING_EFFORT"),
436452
)
437453
return _run(config)
438454
except (

packages/gooddata-eval/src/gooddata_eval/core/agentic/alert_skill.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def run_agentic_alert_skill(
342342
k: int = _DEFAULT_K,
343343
max_iterations: int = _DEFAULT_MAX_ITERATIONS,
344344
initial_conversation_id: str | None = None,
345+
reasoning_effort: str | None = None,
345346
) -> AgenticAlertSummary:
346347
"""Run the alert-skill agentic evaluation K times and return a summary."""
347348
expected = _normalize_expected_output(expected_output)
@@ -361,7 +362,7 @@ def _run_once(conv_id: str) -> AlertRunResult:
361362
current_question = question
362363

363364
for _iteration in range(max_iterations):
364-
chat_result = client.send_message(conv_id, current_question)
365+
chat_result = client.send_message(conv_id, current_question, reasoning_effort=reasoning_effort)
365366
alert_id, actual_args, tool_called = _extract_alert_call(chat_result.tool_call_events or [])
366367
if tool_called:
367368
alert_id_to_delete = alert_id
@@ -462,6 +463,7 @@ def evaluate_agentic_alert_skill(
462463
run_timestamp: str | None = None,
463464
model_version_override: str | None = None,
464465
run_metadata_extra: dict | None = None,
466+
reasoning_effort: str | None = None,
465467
) -> None:
466468
"""Run alert-skill evaluation, log to Langfuse, and raise AlertSkillAssertionError on failure."""
467469
from datetime import datetime as _dt # noqa: PLC0415
@@ -481,6 +483,7 @@ def evaluate_agentic_alert_skill(
481483
k=k,
482484
max_iterations=max_iterations,
483485
initial_conversation_id=initial_conversation_id,
486+
reasoning_effort=reasoning_effort,
484487
)
485488

486489
if langfuse is not None and dataset_item_id:

packages/gooddata-eval/src/gooddata_eval/core/agentic/conversation.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ def run_agentic_conversation(
278278
fixture: ConversationFixture,
279279
max_clarification_turns: int = 20,
280280
initial_conversation_id: str | None = None,
281+
reasoning_effort: str | None = None,
281282
) -> ConversationResult:
282283
"""Run a multi-turn, multi-skill conversation evaluation (no K-runs).
283284
@@ -315,7 +316,7 @@ def run_agentic_conversation(
315316
final_result: ChatResult | None = None
316317

317318
for _iter in range(max_clarification_turns + 1):
318-
chat_result = client.send_message(conversation_id, current_message)
319+
chat_result = client.send_message(conversation_id, current_message, reasoning_effort=reasoning_effort)
319320
final_result = chat_result
320321
all_tool_calls.extend(chat_result.tool_call_events or [])
321322

@@ -403,6 +404,7 @@ def evaluate_agentic_conversation(
403404
run_timestamp: str | None = None,
404405
model_version_override: str | None = None,
405406
run_metadata_extra: dict | None = None,
407+
reasoning_effort: str | None = None,
406408
) -> None:
407409
"""Run conversation evaluation, log to Langfuse, and raise on failure."""
408410
from datetime import datetime as _dt # noqa: PLC0415
@@ -420,6 +422,7 @@ def evaluate_agentic_conversation(
420422
fixture=fixture,
421423
max_clarification_turns=max_clarification_turns,
422424
initial_conversation_id=initial_conversation_id,
425+
reasoning_effort=reasoning_effort,
423426
)
424427

425428
if langfuse is not None and dataset_item_id:

packages/gooddata-eval/src/gooddata_eval/core/agentic/general_question.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def run_agentic_general_question(
7171
expected_output: str,
7272
k: int = _DEFAULT_K,
7373
initial_conversation_id: str | None = None,
74+
reasoning_effort: str | None = None,
7475
) -> AgenticGeneralQuestionSummary:
7576
"""Run the general-question agentic evaluation K times and return a summary."""
7677
run_results: list[GeneralQuestionResult] = []
@@ -80,7 +81,7 @@ def run_agentic_general_question(
8081
try:
8182
conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client.create_conversation()
8283
try:
83-
chat_result = client.send_message(conv_id_0, question)
84+
chat_result = client.send_message(conv_id_0, question, reasoning_effort=reasoning_effort)
8485
actual_output = (chat_result.text_response or "").strip()
8586
passed, reasoning = judge.score(
8687
input=question, expected_output=expected_output, actual_output=actual_output
@@ -102,7 +103,7 @@ def run_agentic_general_question(
102103
for _ in range(1, k):
103104
conv_id = client.create_conversation()
104105
try:
105-
chat_result = client.send_message(conv_id, question)
106+
chat_result = client.send_message(conv_id, question, reasoning_effort=reasoning_effort)
106107
actual_output = (chat_result.text_response or "").strip()
107108
passed, reasoning = judge.score(
108109
input=question, expected_output=expected_output, actual_output=actual_output
@@ -153,6 +154,7 @@ def evaluate_agentic_general_question(
153154
run_timestamp: str | None = None,
154155
model_version_override: str | None = None,
155156
run_metadata_extra: dict | None = None,
157+
reasoning_effort: str | None = None,
156158
) -> None:
157159
"""Run general-question evaluation, log to Langfuse, and raise on failure."""
158160
from datetime import datetime as _dt # noqa: PLC0415
@@ -171,6 +173,7 @@ def evaluate_agentic_general_question(
171173
expected_output=expected_output,
172174
k=k,
173175
initial_conversation_id=initial_conversation_id,
176+
reasoning_effort=reasoning_effort,
174177
)
175178

176179
if langfuse is not None and dataset_item_id:

packages/gooddata-eval/src/gooddata_eval/core/agentic/guardrail.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def run_agentic_guardrail(
6868
expected_output: str,
6969
k: int = _DEFAULT_K,
7070
initial_conversation_id: str | None = None,
71+
reasoning_effort: str | None = None,
7172
) -> AgenticGuardrailSummary:
7273
"""Run the guardrail agentic evaluation K times and return a summary."""
7374
run_results: list[GuardrailResult] = []
@@ -77,7 +78,7 @@ def run_agentic_guardrail(
7778
try:
7879
conv_id_0 = initial_conversation_id if initial_conversation_id is not None else client.create_conversation()
7980
try:
80-
chat_result = client.send_message(conv_id_0, question)
81+
chat_result = client.send_message(conv_id_0, question, reasoning_effort=reasoning_effort)
8182
actual_output = (chat_result.text_response or "").strip()
8283
passed, reasoning = judge.score(
8384
input=question, expected_output=expected_output, actual_output=actual_output
@@ -99,7 +100,7 @@ def run_agentic_guardrail(
99100
for _ in range(1, k):
100101
conv_id = client.create_conversation()
101102
try:
102-
chat_result = client.send_message(conv_id, question)
103+
chat_result = client.send_message(conv_id, question, reasoning_effort=reasoning_effort)
103104
actual_output = (chat_result.text_response or "").strip()
104105
passed, reasoning = judge.score(
105106
input=question, expected_output=expected_output, actual_output=actual_output
@@ -150,6 +151,7 @@ def evaluate_agentic_guardrail(
150151
run_timestamp: str | None = None,
151152
model_version_override: str | None = None,
152153
run_metadata_extra: dict | None = None,
154+
reasoning_effort: str | None = None,
153155
) -> None:
154156
"""Run guardrail evaluation, log to Langfuse, and raise on failure."""
155157
from datetime import datetime as _dt # noqa: PLC0415
@@ -168,6 +170,7 @@ def evaluate_agentic_guardrail(
168170
expected_output=expected_output,
169171
k=k,
170172
initial_conversation_id=initial_conversation_id,
173+
reasoning_effort=reasoning_effort,
171174
)
172175

173176
if langfuse is not None and dataset_item_id:

packages/gooddata-eval/src/gooddata_eval/core/agentic/metric_skill.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def _execute_single_metric_run(
179179
question: str,
180180
expected_outputs: list[dict],
181181
max_iterations: int,
182+
reasoning_effort: str | None = None,
182183
) -> MetricRunResult:
183184
"""Drive one full multi-turn metric-skill conversation and evaluate the result.
184185
@@ -195,7 +196,7 @@ def _execute_single_metric_run(
195196
try:
196197
for _iteration in range(max_iterations):
197198
turns += 1
198-
chat_result = client.send_message(conversation_id, current_question)
199+
chat_result = client.send_message(conversation_id, current_question, reasoning_effort=reasoning_effort)
199200
candidate = _extract_metric_result(chat_result.tool_call_events or [])
200201
if candidate is not None:
201202
metric_result = candidate
@@ -232,6 +233,7 @@ def run_agentic_metric_skill(
232233
k: int = _DEFAULT_K,
233234
max_iterations: int = _DEFAULT_MAX_ITERATIONS,
234235
initial_conversation_id: str | None = None,
236+
reasoning_effort: str | None = None,
235237
) -> AgenticMetricSummary:
236238
"""Run the metric-skill agentic evaluation K times and return a summary.
237239
@@ -248,7 +250,7 @@ def run_agentic_metric_skill(
248250
try:
249251
run_results.append(
250252
_execute_single_metric_run(
251-
client, sdk, workspace_id, conv_id_0, question, expected_outputs, max_iterations
253+
client, sdk, workspace_id, conv_id_0, question, expected_outputs, max_iterations, reasoning_effort
252254
)
253255
)
254256
finally:
@@ -260,7 +262,7 @@ def run_agentic_metric_skill(
260262
try:
261263
run_results.append(
262264
_execute_single_metric_run(
263-
client, sdk, workspace_id, conv_id, question, expected_outputs, max_iterations
265+
client, sdk, workspace_id, conv_id, question, expected_outputs, max_iterations, reasoning_effort
264266
)
265267
)
266268
finally:
@@ -300,6 +302,7 @@ def evaluate_agentic_metric_skill(
300302
run_timestamp: str | None = None,
301303
model_version_override: str | None = None,
302304
run_metadata_extra: dict | None = None,
305+
reasoning_effort: str | None = None,
303306
) -> None:
304307
"""Run metric-skill evaluation, log to Langfuse, and raise MetricSkillAssertionError on failure."""
305308
from datetime import datetime as _dt # noqa: PLC0415
@@ -319,6 +322,7 @@ def evaluate_agentic_metric_skill(
319322
k=k,
320323
max_iterations=max_iterations,
321324
initial_conversation_id=initial_conversation_id,
325+
reasoning_effort=reasoning_effort,
322326
)
323327

324328
if langfuse is not None and dataset_item_id:

0 commit comments

Comments
 (0)