evalrx 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evalrx/__init__.py +139 -0
- evalrx/agent_assets/__init__.py +2 -0
- evalrx/agent_assets/skills/README.md +28 -0
- evalrx/agent_assets/skills/eval-chart-style/SKILL.md +172 -0
- evalrx/agent_assets/skills/evalrx-report-ui/SKILL.md +116 -0
- evalrx/agent_assets/skills/nature-figure/LICENSE +201 -0
- evalrx/agent_assets/skills/nature-figure/README.md +412 -0
- evalrx/agent_assets/skills/nature-figure/SKILL.md +60 -0
- evalrx/agent_assets/skills/nature-figure/manifest.yaml +59 -0
- evalrx/agent_assets/skills/nature-figure/references/api.md +436 -0
- evalrx/agent_assets/skills/nature-figure/references/backend-selection.md +100 -0
- evalrx/agent_assets/skills/nature-figure/references/chart-types.md +281 -0
- evalrx/agent_assets/skills/nature-figure/references/common-patterns.md +350 -0
- evalrx/agent_assets/skills/nature-figure/references/demos.md +65 -0
- evalrx/agent_assets/skills/nature-figure/references/design-theory.md +439 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-contract.md +93 -0
- evalrx/agent_assets/skills/nature-figure/references/figure-legend-conventions.md +71 -0
- evalrx/agent_assets/skills/nature-figure/references/nature-2026-observations.md +112 -0
- evalrx/agent_assets/skills/nature-figure/references/qa-contract.md +119 -0
- evalrx/agent_assets/skills/nature-figure/references/r-template-index.md +66 -0
- evalrx/agent_assets/skills/nature-figure/references/r-workflow.md +161 -0
- evalrx/agent_assets/skills/nature-figure/references/tutorials.md +251 -0
- evalrx/agent_assets/skills/nature-figure/static/core/contract.md +29 -0
- evalrx/agent_assets/skills/nature-figure/static/core/stance.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/python.md +37 -0
- evalrx/agent_assets/skills/nature-figure/static/fragments/backend/r.md +44 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/SKILL.md +213 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/assets/analysis_report_template.md +53 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/references/model_selection.md +72 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.R +130 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/explanatory_var_eda.py +150 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.R +181 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/fit_outcome_model.py +186 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.R +149 -0
- evalrx/agent_assets/skills/outcome-driver-analysis/scripts/univariate_eda.py +177 -0
- evalrx/agent_assets/skills.py +27 -0
- evalrx/agent_runtime/__init__.py +78 -0
- evalrx/agent_runtime/_docker_runner.py +89 -0
- evalrx/agent_runtime/cli_runtime.py +103 -0
- evalrx/agent_runtime/cli_transcript.py +138 -0
- evalrx/agent_runtime/cli_types.py +68 -0
- evalrx/agent_runtime/codegen/__init__.py +5 -0
- evalrx/agent_runtime/codegen/runner.py +94 -0
- evalrx/agent_runtime/experiment_harness.py +117 -0
- evalrx/agent_runtime/factory.py +102 -0
- evalrx/agent_runtime/json_shape.py +44 -0
- evalrx/agent_runtime/judges/__init__.py +28 -0
- evalrx/agent_runtime/judges/agy.py +179 -0
- evalrx/agent_runtime/judges/autodetect.py +135 -0
- evalrx/agent_runtime/judges/claude.py +159 -0
- evalrx/agent_runtime/judges/codex.py +120 -0
- evalrx/agent_runtime/providers/__init__.py +21 -0
- evalrx/agent_runtime/providers/antigravity.py +31 -0
- evalrx/agent_runtime/providers/base.py +145 -0
- evalrx/agent_runtime/providers/claude_code.py +49 -0
- evalrx/agent_runtime/providers/codex.py +37 -0
- evalrx/agent_runtime/providers/gemini_cli.py +26 -0
- evalrx/agent_runtime/providers/kimi_cli.py +27 -0
- evalrx/agent_runtime/providers/opencode.py +27 -0
- evalrx/agent_runtime/providers/registry.py +58 -0
- evalrx/agent_runtime/sandbox.py +517 -0
- evalrx/agent_runtime/skill_audit.py +143 -0
- evalrx/agent_runtime/skills/__init__.py +19 -0
- evalrx/agent_runtime/skills/installer.py +68 -0
- evalrx/agent_runtime/skills/prompt_policy.py +86 -0
- evalrx/agent_runtime/skills/resolver.py +19 -0
- evalrx/analysis/__init__.py +132 -0
- evalrx/analysis/adjudicate.py +154 -0
- evalrx/analysis/analysis_module.py +361 -0
- evalrx/analysis/api.py +171 -0
- evalrx/analysis/case_studio.py +651 -0
- evalrx/analysis/cli.py +114 -0
- evalrx/analysis/dashboard.py +350 -0
- evalrx/analysis/eval_case_matrix.py +118 -0
- evalrx/analysis/eval_viz_theme.py +833 -0
- evalrx/analysis/explore_run.py +333 -0
- evalrx/analysis/explorer.py +1276 -0
- evalrx/analysis/failure_modes.py +607 -0
- evalrx/analysis/fused_pipeline.py +489 -0
- evalrx/analysis/holdout.py +300 -0
- evalrx/analysis/hypothesis_agent.py +230 -0
- evalrx/analysis/narration.py +177 -0
- evalrx/analysis/operationalize.py +442 -0
- evalrx/analysis/plain_language.py +42 -0
- evalrx/analysis/planner.py +283 -0
- evalrx/analysis/probe_search.py +203 -0
- evalrx/analysis/profile.py +268 -0
- evalrx/analysis/prompts/__init__.py +0 -0
- evalrx/analysis/prompts/explorer.py +417 -0
- evalrx/analysis/prompts/failure_modes.py +33 -0
- evalrx/analysis/prompts/holdout.py +27 -0
- evalrx/analysis/prompts/hypothesis_agent.py +78 -0
- evalrx/analysis/prompts/run_codebase.py +47 -0
- evalrx/analysis/prompts/stats_agent.py +72 -0
- evalrx/analysis/prompts/stats_tool_generator.py +43 -0
- evalrx/analysis/result_marker.py +47 -0
- evalrx/analysis/run_codebase.py +242 -0
- evalrx/analysis/run_view.py +205 -0
- evalrx/analysis/stage_views.py +93 -0
- evalrx/analysis/stats_agent.py +944 -0
- evalrx/analysis/stats_tool_agent.py +261 -0
- evalrx/analysis/stats_tool_generator.py +415 -0
- evalrx/analysis/stats_tools.py +1153 -0
- evalrx/analysis/trajectory_records.py +193 -0
- evalrx/analysis/workbench.py +431 -0
- evalrx/analyzers/__init__.py +42 -0
- evalrx/analyzers/agent/__init__.py +25 -0
- evalrx/analyzers/agent/counterfactual.py +84 -0
- evalrx/analyzers/agent/first_error_judge.py +96 -0
- evalrx/analyzers/agent/ignored_obs.py +81 -0
- evalrx/analyzers/agent/loop_detect.py +79 -0
- evalrx/analyzers/agent/reliability.py +165 -0
- evalrx/analyzers/agent/tool_shap.py +225 -0
- evalrx/analyzers/agent/trajectory_rubric.py +168 -0
- evalrx/analyzers/attention/__init__.py +19 -0
- evalrx/analyzers/attention/relative_attn.py +610 -0
- evalrx/analyzers/attention/rollout.py +73 -0
- evalrx/analyzers/attention/sink.py +56 -0
- evalrx/analyzers/attention/summary.py +190 -0
- evalrx/analyzers/attribution/__init__.py +6 -0
- evalrx/analyzers/attribution/generic_attn.py +31 -0
- evalrx/analyzers/attribution/gradcam.py +30 -0
- evalrx/analyzers/base.py +12 -0
- evalrx/analyzers/geometry/__init__.py +6 -0
- evalrx/analyzers/geometry/cka.py +70 -0
- evalrx/analyzers/geometry/linear_probe.py +157 -0
- evalrx/analyzers/hallucination/__init__.py +9 -0
- evalrx/analyzers/hallucination/chair.py +78 -0
- evalrx/analyzers/hallucination/opera.py +29 -0
- evalrx/analyzers/hallucination/pope.py +119 -0
- evalrx/analyzers/hallucination/selfcheck.py +155 -0
- evalrx/analyzers/hallucination/vcd.py +29 -0
- evalrx/analyzers/lens/__init__.py +7 -0
- evalrx/analyzers/lens/layer_contrast.py +133 -0
- evalrx/analyzers/lens/logit_lens.py +138 -0
- evalrx/analyzers/lens/tuned_lens.py +30 -0
- evalrx/analyzers/patching/__init__.py +5 -0
- evalrx/analyzers/patching/causal_trace.py +30 -0
- evalrx/analyzers/perturbation/__init__.py +23 -0
- evalrx/analyzers/perturbation/_shapley.py +54 -0
- evalrx/analyzers/perturbation/context_shap.py +174 -0
- evalrx/analyzers/perturbation/cot_faithfulness.py +239 -0
- evalrx/analyzers/perturbation/format_sensitivity.py +237 -0
- evalrx/analyzers/perturbation/mm_shap.py +146 -0
- evalrx/analyzers/perturbation/modality_ablation.py +196 -0
- evalrx/analyzers/perturbation/perturbation_battery.py +274 -0
- evalrx/analyzers/perturbation/prompt_contrast.py +265 -0
- evalrx/analyzers/perturbation/rise.py +94 -0
- evalrx/analyzers/perturbation/vl_shap.py +102 -0
- evalrx/analyzers/reasoning/__init__.py +33 -0
- evalrx/analyzers/reasoning/_text.py +328 -0
- evalrx/analyzers/reasoning/answer_extraction_audit.py +327 -0
- evalrx/analyzers/reasoning/arith_audit.py +226 -0
- evalrx/analyzers/reasoning/contamination.py +214 -0
- evalrx/analyzers/reasoning/knowledge_split.py +253 -0
- evalrx/analyzers/reasoning/self_repair.py +246 -0
- evalrx/analyzers/reasoning/step_rollout_value.py +216 -0
- evalrx/analyzers/reasoning/termination_audit.py +258 -0
- evalrx/analyzers/uncertainty/__init__.py +18 -0
- evalrx/analyzers/uncertainty/calibration.py +174 -0
- evalrx/analyzers/uncertainty/coverage_gap.py +199 -0
- evalrx/analyzers/uncertainty/entropy.py +90 -0
- evalrx/analyzers/uncertainty/logprob_entropy.py +69 -0
- evalrx/analyzers/uncertainty/self_consistency.py +204 -0
- evalrx/analyzers/uncertainty/verbalized_conf.py +64 -0
- evalrx/cli.py +411 -0
- evalrx/config.py +77 -0
- evalrx/contract/__init__.py +179 -0
- evalrx/contract/common.py +452 -0
- evalrx/contract/emit.py +948 -0
- evalrx/contract/export.py +237 -0
- evalrx/contract/m1.py +325 -0
- evalrx/contract/m2.py +317 -0
- evalrx/contract/m3.py +165 -0
- evalrx/contract/m4.py +130 -0
- evalrx/contract/m5.py +292 -0
- evalrx/contract/methodology.py +76 -0
- evalrx/contract/pre_m1.py +58 -0
- evalrx/contract/typescript.py +140 -0
- evalrx/core/__init__.py +85 -0
- evalrx/core/analyzer.py +174 -0
- evalrx/core/capability.py +54 -0
- evalrx/core/case.py +443 -0
- evalrx/core/experiment.py +106 -0
- evalrx/core/model.py +198 -0
- evalrx/core/pipeline.py +42 -0
- evalrx/core/registry.py +142 -0
- evalrx/core/result.py +64 -0
- evalrx/core/spec.py +173 -0
- evalrx/core/tokentype.py +165 -0
- evalrx/core/tool.py +92 -0
- evalrx/datasets/__init__.py +41 -0
- evalrx/datasets/base.py +68 -0
- evalrx/datasets/gui_os.py +52 -0
- evalrx/datasets/llm_qa.py +57 -0
- evalrx/datasets/pure_qa.py +12 -0
- evalrx/datasets/vlm_qa.py +695 -0
- evalrx/datasets/web_search_qa.py +52 -0
- evalrx/eval_agent/__init__.py +341 -0
- evalrx/eval_agent/_tools.py +81 -0
- evalrx/eval_agent/ab_runner.py +50 -0
- evalrx/eval_agent/agentic/__init__.py +43 -0
- evalrx/eval_agent/agentic/actions.py +216 -0
- evalrx/eval_agent/agentic/board.py +107 -0
- evalrx/eval_agent/agentic/loop.py +190 -0
- evalrx/eval_agent/agentic/tools.py +538 -0
- evalrx/eval_agent/checkpoint.py +57 -0
- evalrx/eval_agent/cli_agent.py +59 -0
- evalrx/eval_agent/cli_skills.py +5 -0
- evalrx/eval_agent/evolution.py +396 -0
- evalrx/eval_agent/git_manager.py +215 -0
- evalrx/eval_agent/hypothesis.py +172 -0
- evalrx/eval_agent/label_quarantine.py +209 -0
- evalrx/eval_agent/legacy.py +530 -0
- evalrx/eval_agent/log_schema.py +497 -0
- evalrx/eval_agent/loop.py +2159 -0
- evalrx/eval_agent/loop_reports.py +116 -0
- evalrx/eval_agent/model_instrumentation.py +282 -0
- evalrx/eval_agent/narration.py +193 -0
- evalrx/eval_agent/nl_runner.py +460 -0
- evalrx/eval_agent/orchestrator.py +61 -0
- evalrx/eval_agent/preregister.py +93 -0
- evalrx/eval_agent/prompts/__init__.py +1 -0
- evalrx/eval_agent/prompts/agentic.py +46 -0
- evalrx/eval_agent/prompts/case_discovery.py +25 -0
- evalrx/eval_agent/prompts/diagnosis.py +125 -0
- evalrx/eval_agent/prompts/experiment_writer.py +265 -0
- evalrx/eval_agent/prompts/explore_step.py +37 -0
- evalrx/eval_agent/prompts/fix_agent.py +257 -0
- evalrx/eval_agent/prompts/hypothesis_tester.py +15 -0
- evalrx/eval_agent/prompts/nl_runner.py +38 -0
- evalrx/eval_agent/prompts/probe_agent.py +25 -0
- evalrx/eval_agent/prompts/probe_candidate_generator.py +14 -0
- evalrx/eval_agent/prompts/probe_generator.py +35 -0
- evalrx/eval_agent/prompts/whitebox_probe_generator.py +38 -0
- evalrx/eval_agent/report.py +58 -0
- evalrx/eval_agent/run_context.py +354 -0
- evalrx/eval_agent/run_log.schema.json +1215 -0
- evalrx/eval_agent/run_logger_v2.py +1764 -0
- evalrx/eval_agent/run_metadata.py +208 -0
- evalrx/eval_agent/stages/__init__.py +56 -0
- evalrx/eval_agent/stages/case_discovery.py +293 -0
- evalrx/eval_agent/stages/diagnosis.py +1017 -0
- evalrx/eval_agent/stages/experiment_writer.py +1634 -0
- evalrx/eval_agent/stages/fix_agent.py +3916 -0
- evalrx/eval_agent/stages/fix_internals.py +499 -0
- evalrx/eval_agent/stages/fix_pipeline.py +725 -0
- evalrx/eval_agent/stages/fix_tiers.py +187 -0
- evalrx/eval_agent/stages/fix_tools.py +1034 -0
- evalrx/eval_agent/stages/hypothesis_tester.py +1014 -0
- evalrx/eval_agent/stages/probe.py +439 -0
- evalrx/eval_agent/stages/probe_agent.py +1079 -0
- evalrx/eval_agent/stages/probe_candidate_generator.py +128 -0
- evalrx/eval_agent/stages/probe_generator.py +326 -0
- evalrx/eval_agent/stages/probe_search_agent.py +106 -0
- evalrx/eval_agent/stages/protocol.py +112 -0
- evalrx/eval_agent/stages/repair_catalog.py +273 -0
- evalrx/eval_agent/stages/surgery.py +524 -0
- evalrx/eval_agent/stages/whitebox_probe_generator.py +351 -0
- evalrx/eval_agent/store.py +231 -0
- evalrx/logging_utils.py +112 -0
- evalrx/models/__init__.py +161 -0
- evalrx/models/_discover.py +101 -0
- evalrx/models/agent.py +380 -0
- evalrx/models/backends/__init__.py +58 -0
- evalrx/models/backends/api.py +169 -0
- evalrx/models/backends/base.py +57 -0
- evalrx/models/backends/gemini_compat.py +579 -0
- evalrx/models/backends/hf_local.py +2074 -0
- evalrx/models/backends/openai_compat.py +301 -0
- evalrx/models/backends/vllm_offline.py +116 -0
- evalrx/models/base.py +24 -0
- evalrx/models/blackbox/__init__.py +4 -0
- evalrx/models/blackbox/agent.py +31 -0
- evalrx/models/blackbox/base.py +29 -0
- evalrx/models/blackbox/gemini.py +279 -0
- evalrx/models/blackbox/llm_api.py +17 -0
- evalrx/models/blackbox/vlm_api.py +17 -0
- evalrx/models/compose.py +66 -0
- evalrx/models/inference.py +88 -0
- evalrx/models/paper_methods/__init__.py +8 -0
- evalrx/models/paper_methods/aad.py +53 -0
- evalrx/models/paper_methods/ifcd.py +204 -0
- evalrx/models/paper_methods/pai.py +164 -0
- evalrx/models/paper_methods/tcd.py +202 -0
- evalrx/models/paper_methods/vcd.py +45 -0
- evalrx/models/paper_methods/vicrop.py +137 -0
- evalrx/models/toolcodec.py +143 -0
- evalrx/models/tools/__init__.py +20 -0
- evalrx/models/tools/perception.py +300 -0
- evalrx/models/tools/visual.py +174 -0
- evalrx/models/whitebox/__init__.py +26 -0
- evalrx/models/whitebox/agent.py +31 -0
- evalrx/models/whitebox/base.py +24 -0
- evalrx/models/whitebox/qwen.py +61 -0
- evalrx/models/whitebox/qwen2_5_omni.py +29 -0
- evalrx/models/whitebox/qwen2_audio.py +25 -0
- evalrx/models/whitebox/qwen_omni.py +53 -0
- evalrx/models/whitebox/qwen_vl.py +62 -0
- evalrx/observability/__init__.py +21 -0
- evalrx/observability/envelope.py +122 -0
- evalrx/observability/outbox.py +111 -0
- evalrx/observability/tracer.py +882 -0
- evalrx/reporting/__init__.py +28 -0
- evalrx/reporting/case_study.py +947 -0
- evalrx/reporting/compiler.py +587 -0
- evalrx/reporting/dynamic.py +1882 -0
- evalrx/reporting/html_report.py +2225 -0
- evalrx/reporting/langfuse_exporter.py +38 -0
- evalrx/reporting/langfuse_source.py +155 -0
- evalrx/reporting/model.py +151 -0
- evalrx/reporting/run_events.py +184 -0
- evalrx/reporting/server.py +557 -0
- evalrx/reporting/stages.py +58 -0
- evalrx/reporting/static_export.py +142 -0
- evalrx/reporting/web_dist/index.html +146 -0
- evalrx/specs.py +727 -0
- evalrx/stats/__init__.py +47 -0
- evalrx/stats/api.py +192 -0
- evalrx/stats/bootstrap.py +86 -0
- evalrx/stats/ebh.py +27 -0
- evalrx/stats/evalue.py +98 -0
- evalrx/stats/friedman.py +138 -0
- evalrx/stats/mcnemar.py +40 -0
- evalrx/stats/multiplicity.py +159 -0
- evalrx/stats/subset_sampling.py +55 -0
- evalrx/term_links.py +43 -0
- evalrx/viz/__init__.py +7 -0
- evalrx/viz/labels.py +77 -0
- evalrx/viz/prompts.py +39 -0
- evalrx/viz/renderer.py +590 -0
- evalrx/viz/schema.py +36 -0
- evalrx/viz/style.py +134 -0
- evalrx-0.1.2.dist-info/METADATA +532 -0
- evalrx-0.1.2.dist-info/RECORD +339 -0
- evalrx-0.1.2.dist-info/WHEEL +5 -0
- evalrx-0.1.2.dist-info/entry_points.txt +3 -0
- evalrx-0.1.2.dist-info/licenses/LICENSE +121 -0
- evalrx-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Built-in OpenAI-compatible client — chat_fn/generate_fn factories for APIModel.
|
|
2
|
+
|
|
3
|
+
Covers every OpenAI-compatible endpoint with one code path: a local
|
|
4
|
+
``vllm serve`` (the scale path for open Qwen3-VL agents — served with
|
|
5
|
+
``--enable-auto-tool-choice --tool-call-parser hermes`` it returns native
|
|
6
|
+
structured ``tool_calls``), OpenAI itself, or any compatible gateway.
|
|
7
|
+
|
|
8
|
+
Wire conversion: agent messages use the transformers-style content-block
|
|
9
|
+
convention (``{"type": "image", "image": PIL | path | URL}``); this module
|
|
10
|
+
converts image blocks to OpenAI ``image_url`` blocks (data URLs for local
|
|
11
|
+
images). Sampling defaults to ``temperature=0`` — diagnosed runs should be
|
|
12
|
+
as deterministic as the endpoint allows.
|
|
13
|
+
|
|
14
|
+
Torch-free; ``openai`` and ``PIL`` are imported lazily.
|
|
15
|
+
|
|
16
|
+
Usage::
|
|
17
|
+
|
|
18
|
+
from evalrx import compose
|
|
19
|
+
from evalrx.models.backends.openai_compat import openai_runtime
|
|
20
|
+
|
|
21
|
+
rt = openai_runtime(base_url="http://localhost:8901/v1") # vllm serve
|
|
22
|
+
vlm = compose("qwen3-vl-2b-instruct", "api", runtime=rt)
|
|
23
|
+
Agent(vlm, tools=[...]).run(case)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import base64
|
|
29
|
+
import io
|
|
30
|
+
from typing import Any, Callable, Optional
|
|
31
|
+
|
|
32
|
+
from evalrx.core.tool import ChatTurn
|
|
33
|
+
from evalrx.models.backends.api import parse_openai_logprobs
|
|
34
|
+
from evalrx.models.backends.base import RuntimeConfig
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _to_data_url(image: Any) -> str:
|
|
38
|
+
"""Encode *image* (PIL / path / URL) as an ``image_url`` value.
|
|
39
|
+
|
|
40
|
+
http(s) URLs pass through; everything else becomes a base64 PNG data URL.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(image, str) and image.startswith(("http://", "https://", "data:")):
|
|
43
|
+
return image
|
|
44
|
+
if not (hasattr(image, "save") and hasattr(image, "mode")): # a path -> open it
|
|
45
|
+
from PIL import Image
|
|
46
|
+
|
|
47
|
+
image = Image.open(image).convert("RGB")
|
|
48
|
+
buf = io.BytesIO()
|
|
49
|
+
image.save(buf, format="PNG")
|
|
50
|
+
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode("ascii")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_AUDIO_FORMATS = {".wav": "wav", ".mp3": "mp3", ".flac": "flac", ".ogg": "ogg", ".m4a": "m4a"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _to_input_audio(audio: Any, sample_rate: int = 16000) -> dict:
|
|
57
|
+
"""Encode *audio* as an OpenAI ``input_audio`` value (``{"data", "format"}``).
|
|
58
|
+
|
|
59
|
+
Accepts a path (format from the suffix), raw bytes (assumed WAV), or a
|
|
60
|
+
waveform -- ``np.ndarray`` or ``(np.ndarray, sample_rate)`` -- which is
|
|
61
|
+
written as 16-bit PCM WAV with the stdlib ``wave`` module so the endpoint
|
|
62
|
+
path needs no audio library of its own.
|
|
63
|
+
"""
|
|
64
|
+
import os
|
|
65
|
+
|
|
66
|
+
fmt = "wav"
|
|
67
|
+
if isinstance(audio, (bytes, bytearray)):
|
|
68
|
+
data = bytes(audio)
|
|
69
|
+
elif isinstance(audio, (str, os.PathLike)):
|
|
70
|
+
path = os.fspath(audio)
|
|
71
|
+
fmt = _AUDIO_FORMATS.get(os.path.splitext(path)[1].lower(), "wav")
|
|
72
|
+
with open(path, "rb") as handle:
|
|
73
|
+
data = handle.read()
|
|
74
|
+
else:
|
|
75
|
+
import io
|
|
76
|
+
import wave
|
|
77
|
+
|
|
78
|
+
import numpy as np
|
|
79
|
+
|
|
80
|
+
if isinstance(audio, tuple) and len(audio) == 2:
|
|
81
|
+
audio, sample_rate = audio
|
|
82
|
+
arr = np.asarray(audio)
|
|
83
|
+
if arr.ndim > 1: # (channels, n) or (n, channels) -> mono
|
|
84
|
+
arr = arr.mean(axis=0 if arr.shape[0] <= 8 else 1)
|
|
85
|
+
if np.issubdtype(arr.dtype, np.floating):
|
|
86
|
+
arr = np.clip(arr, -1.0, 1.0) * 32767.0
|
|
87
|
+
pcm = arr.astype("<i2").tobytes()
|
|
88
|
+
buf = io.BytesIO()
|
|
89
|
+
with wave.open(buf, "wb") as wav:
|
|
90
|
+
wav.setnchannels(1)
|
|
91
|
+
wav.setsampwidth(2)
|
|
92
|
+
wav.setframerate(int(sample_rate))
|
|
93
|
+
wav.writeframes(pcm)
|
|
94
|
+
data = buf.getvalue()
|
|
95
|
+
return {"data": base64.b64encode(data).decode("ascii"), "format": fmt}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def user_message(prompt: str, *, image: Any = None, audio: Any = None) -> dict:
|
|
99
|
+
"""One user turn: a plain string, or content blocks when a media slot is set.
|
|
100
|
+
|
|
101
|
+
The blocks use the internal convention (``{"type": "image", "image": ...}``
|
|
102
|
+
/ ``{"type": "audio", "audio": ...}``) that :func:`to_openai_messages`
|
|
103
|
+
converts, so ``chat_fn`` callers and ``generate_fn`` callers meet the wire
|
|
104
|
+
in one place.
|
|
105
|
+
"""
|
|
106
|
+
if image is None and audio is None:
|
|
107
|
+
return {"role": "user", "content": prompt}
|
|
108
|
+
blocks: list[dict] = []
|
|
109
|
+
if audio is not None:
|
|
110
|
+
blocks.append({"type": "audio", "audio": audio})
|
|
111
|
+
if image is not None:
|
|
112
|
+
blocks.append({"type": "image", "image": image})
|
|
113
|
+
blocks.append({"type": "text", "text": prompt})
|
|
114
|
+
return {"role": "user", "content": blocks}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def to_openai_messages(messages: list) -> list[dict]:
|
|
118
|
+
"""Convert internal content-block messages into OpenAI wire format.
|
|
119
|
+
|
|
120
|
+
Plain-string content, assistant ``tool_calls`` and ``role="tool"`` results
|
|
121
|
+
pass through unchanged; ``{"type": "image", "image": ...}`` blocks become
|
|
122
|
+
``image_url`` blocks and ``{"type": "audio", "audio": ...}`` blocks become
|
|
123
|
+
``input_audio`` blocks (base64 WAV/MP3/FLAC -- what ``vllm serve`` reads
|
|
124
|
+
for an audio model).
|
|
125
|
+
"""
|
|
126
|
+
out: list[dict] = []
|
|
127
|
+
for msg in messages:
|
|
128
|
+
content = msg.get("content")
|
|
129
|
+
if not isinstance(content, list):
|
|
130
|
+
out.append(msg)
|
|
131
|
+
continue
|
|
132
|
+
blocks: list[dict] = []
|
|
133
|
+
for block in content:
|
|
134
|
+
if isinstance(block, dict) and block.get("type") == "image":
|
|
135
|
+
blocks.append(
|
|
136
|
+
{"type": "image_url", "image_url": {"url": _to_data_url(block.get("image"))}}
|
|
137
|
+
)
|
|
138
|
+
elif isinstance(block, dict) and block.get("type") == "audio":
|
|
139
|
+
blocks.append(
|
|
140
|
+
{"type": "input_audio", "input_audio": _to_input_audio(block.get("audio"))}
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
blocks.append(block)
|
|
144
|
+
out.append({**msg, "content": blocks})
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _raw_tool_calls(message: Any) -> Optional[list]:
|
|
149
|
+
calls = getattr(message, "tool_calls", None)
|
|
150
|
+
if not calls:
|
|
151
|
+
return None
|
|
152
|
+
return [c.model_dump() if hasattr(c, "model_dump") else dict(c) for c in calls]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def openai_chat_fn(
|
|
156
|
+
*,
|
|
157
|
+
base_url: Optional[str] = None,
|
|
158
|
+
api_key: Optional[str] = None,
|
|
159
|
+
client: Any = None,
|
|
160
|
+
timeout: float = 300.0,
|
|
161
|
+
**sampling: Any,
|
|
162
|
+
) -> Callable[..., ChatTurn]:
|
|
163
|
+
"""Build a tool-aware ``chat_fn(messages, tools, model) -> ChatTurn``.
|
|
164
|
+
|
|
165
|
+
*client* may be injected (tests, custom transports); otherwise a lazy
|
|
166
|
+
``openai.OpenAI(base_url=..., api_key=...)`` is created on first call —
|
|
167
|
+
``api_key`` defaults to ``"EMPTY"``, which is what a local ``vllm serve``
|
|
168
|
+
expects. Extra keyword arguments become sampling parameters on every
|
|
169
|
+
request (``temperature`` defaults to 0).
|
|
170
|
+
"""
|
|
171
|
+
sampling.setdefault("temperature", 0.0)
|
|
172
|
+
state = {"client": client}
|
|
173
|
+
|
|
174
|
+
def _client():
|
|
175
|
+
if state["client"] is None:
|
|
176
|
+
import openai
|
|
177
|
+
|
|
178
|
+
state["client"] = openai.OpenAI(
|
|
179
|
+
base_url=base_url, api_key=api_key or "EMPTY", timeout=timeout
|
|
180
|
+
)
|
|
181
|
+
return state["client"]
|
|
182
|
+
|
|
183
|
+
def _fn(messages: list, tools=None, model: str = "") -> ChatTurn:
|
|
184
|
+
kwargs: dict[str, Any] = dict(sampling)
|
|
185
|
+
if tools:
|
|
186
|
+
kwargs["tools"] = tools
|
|
187
|
+
kwargs.setdefault("tool_choice", "auto")
|
|
188
|
+
resp = _client().chat.completions.create(
|
|
189
|
+
model=model, messages=to_openai_messages(messages), **kwargs
|
|
190
|
+
)
|
|
191
|
+
choice = resp.choices[0]
|
|
192
|
+
usage = getattr(resp, "usage", None)
|
|
193
|
+
return ChatTurn(
|
|
194
|
+
text=choice.message.content or "",
|
|
195
|
+
raw_tool_calls=_raw_tool_calls(choice.message),
|
|
196
|
+
finish_reason=choice.finish_reason,
|
|
197
|
+
usage=(
|
|
198
|
+
{
|
|
199
|
+
"prompt_tokens": int(getattr(usage, "prompt_tokens", 0) or 0),
|
|
200
|
+
"completion_tokens": int(getattr(usage, "completion_tokens", 0) or 0),
|
|
201
|
+
}
|
|
202
|
+
if usage is not None
|
|
203
|
+
else None
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return _fn
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def openai_generate_fn(
|
|
211
|
+
*,
|
|
212
|
+
base_url: Optional[str] = None,
|
|
213
|
+
api_key: Optional[str] = None,
|
|
214
|
+
client: Any = None,
|
|
215
|
+
timeout: float = 300.0,
|
|
216
|
+
**sampling: Any,
|
|
217
|
+
) -> Callable[..., str]:
|
|
218
|
+
"""Build a simple ``generate_fn(prompt, model=...) -> str`` on the same client."""
|
|
219
|
+
chat = openai_chat_fn(
|
|
220
|
+
base_url=base_url, api_key=api_key, client=client, timeout=timeout, **sampling
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _fn(prompt: str, model: str = "", *, image: Any = None, audio: Any = None, **kw) -> str:
|
|
224
|
+
return chat([user_message(prompt, image=image, audio=audio)], tools=None, model=model).text
|
|
225
|
+
|
|
226
|
+
return _fn
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def openai_logprobs_fn(
|
|
230
|
+
*,
|
|
231
|
+
base_url: Optional[str] = None,
|
|
232
|
+
api_key: Optional[str] = None,
|
|
233
|
+
client: Any = None,
|
|
234
|
+
timeout: float = 300.0,
|
|
235
|
+
top_logprobs: int = 5,
|
|
236
|
+
**sampling: Any,
|
|
237
|
+
) -> Callable[..., list]:
|
|
238
|
+
"""Build a ``logprobs_fn(prompt, model=...) -> list[TokenLogprob]``.
|
|
239
|
+
|
|
240
|
+
An OpenAI-compatible server returns per-token logprobs when asked, and
|
|
241
|
+
``vllm serve`` is one. Without this the api backend declares only GENERATE,
|
|
242
|
+
so every analyzer that reads answer-token uncertainty is skipped as
|
|
243
|
+
unsupported -- on the LLM benchmark set that is `calibration` and
|
|
244
|
+
`logprob_entropy`, two of the eight pinned probes, dropped not because the
|
|
245
|
+
server cannot answer but because nobody asked it.
|
|
246
|
+
"""
|
|
247
|
+
sampling.setdefault("temperature", 0.0)
|
|
248
|
+
state = {"client": client}
|
|
249
|
+
|
|
250
|
+
def _client():
|
|
251
|
+
if state["client"] is None:
|
|
252
|
+
import openai
|
|
253
|
+
|
|
254
|
+
state["client"] = openai.OpenAI(
|
|
255
|
+
base_url=base_url, api_key=api_key or "EMPTY", timeout=timeout
|
|
256
|
+
)
|
|
257
|
+
return state["client"]
|
|
258
|
+
|
|
259
|
+
def _fn(prompt: str, model: str = "", *, image: Any = None, audio: Any = None, **kw) -> list:
|
|
260
|
+
kwargs: dict[str, Any] = {**sampling, **kw}
|
|
261
|
+
kwargs["logprobs"] = True
|
|
262
|
+
kwargs["top_logprobs"] = int(kwargs.pop("top_logprobs", top_logprobs))
|
|
263
|
+
resp = _client().chat.completions.create(
|
|
264
|
+
model=model,
|
|
265
|
+
messages=to_openai_messages([user_message(prompt, image=image, audio=audio)]),
|
|
266
|
+
**kwargs,
|
|
267
|
+
)
|
|
268
|
+
logprobs = getattr(resp.choices[0], "logprobs", None)
|
|
269
|
+
content = getattr(logprobs, "content", None) if logprobs is not None else None
|
|
270
|
+
# A server that accepts `logprobs=True` and returns nothing has told us
|
|
271
|
+
# it does not support them. Returning [] would read as "this answer had
|
|
272
|
+
# no tokens", so let the caller see the empty result for what it is.
|
|
273
|
+
return parse_openai_logprobs(
|
|
274
|
+
[item.model_dump() if hasattr(item, "model_dump") else item for item in (content or [])]
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return _fn
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def openai_runtime(
|
|
281
|
+
*,
|
|
282
|
+
base_url: Optional[str] = None,
|
|
283
|
+
api_key: Optional[str] = None,
|
|
284
|
+
client: Any = None,
|
|
285
|
+
timeout: float = 300.0,
|
|
286
|
+
with_logprobs: bool = True,
|
|
287
|
+
**sampling: Any,
|
|
288
|
+
) -> RuntimeConfig:
|
|
289
|
+
"""A ready :class:`RuntimeConfig` for ``compose(key, "api", runtime=...)``.
|
|
290
|
+
|
|
291
|
+
``with_logprobs`` wires the logprobs path, which is what makes the backend
|
|
292
|
+
claim :attr:`Capability.LOGPROBS`. Pass False for an endpoint that rejects
|
|
293
|
+
the parameter -- claiming a capability the server does not have turns a
|
|
294
|
+
skipped analyzer into a failing one.
|
|
295
|
+
"""
|
|
296
|
+
kw = dict(base_url=base_url, api_key=api_key, client=client, timeout=timeout, **sampling)
|
|
297
|
+
return RuntimeConfig(
|
|
298
|
+
chat_fn=openai_chat_fn(**kw),
|
|
299
|
+
generate_fn=openai_generate_fn(**kw),
|
|
300
|
+
logprobs_fn=openai_logprobs_fn(**kw) if with_logprobs else None,
|
|
301
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""vLLM offline backend — in-process, high-throughput generation + logprobs.
|
|
2
|
+
|
|
3
|
+
The *distinct* role of vLLM (NOT the same as ``api``): an in-process engine you
|
|
4
|
+
submit batches to, with continuous batching and ``prompt_logprobs`` — the
|
|
5
|
+
throughput sweet spot for perturbation sweeps (RISE / SHAP) and logprob scoring
|
|
6
|
+
on open weights. It is a **black-box** backend: it exposes outputs only
|
|
7
|
+
(``GENERATE`` / ``TOOL_CALLS`` / ``LOGPROBS``) and **never** attention / hidden
|
|
8
|
+
states / gradients — paged/fused kernels don't materialise them. White-box
|
|
9
|
+
capture is ``hf_local``'s job.
|
|
10
|
+
|
|
11
|
+
(A ``vllm serve`` HTTP endpoint, by contrast, is reached via the ``api`` backend.)
|
|
12
|
+
|
|
13
|
+
``vllm`` is imported lazily inside ``load()`` so this module imports without it.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from evalrx.core.capability import Capability, CapabilityError
|
|
21
|
+
from evalrx.core.case import Inputs
|
|
22
|
+
from evalrx.core.model import Model, TokenLogprob, Trace
|
|
23
|
+
from evalrx.core.tool import ChatTurn
|
|
24
|
+
from evalrx.models.backends.base import Backend, RuntimeConfig
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class VLLMOfflineModel(Model):
|
|
28
|
+
"""A locally-loaded vLLM engine driven offline (batched)."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, spec, runtime: RuntimeConfig) -> None:
|
|
31
|
+
self.spec = spec
|
|
32
|
+
self.runtime = runtime
|
|
33
|
+
self._llm = None # lazy vllm.LLM
|
|
34
|
+
caps = {Capability.GENERATE, Capability.LOGPROBS}
|
|
35
|
+
if spec.tool_calling: # conditional, like hf_local: needs a tool chat template
|
|
36
|
+
caps.add(Capability.TOOL_CALLS)
|
|
37
|
+
self.capabilities = frozenset(caps)
|
|
38
|
+
self.modalities = frozenset(spec.modalities) # text / image / audio / video, from the spec
|
|
39
|
+
|
|
40
|
+
# -- lazy load -----------------------------------------------------
|
|
41
|
+
def load(self) -> None:
|
|
42
|
+
from vllm import LLM
|
|
43
|
+
|
|
44
|
+
self._llm = LLM(
|
|
45
|
+
model=self.spec.hf_repo,
|
|
46
|
+
trust_remote_code=self.spec.trust_remote_code,
|
|
47
|
+
dtype=self.runtime.dtype,
|
|
48
|
+
**self.runtime.engine_kwargs,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def _engine(self):
|
|
53
|
+
if self._llm is None:
|
|
54
|
+
self.load()
|
|
55
|
+
return self._llm
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _as_prompt(inputs: Any) -> str:
|
|
59
|
+
return inputs.prompt if isinstance(inputs, Inputs) else str(inputs)
|
|
60
|
+
|
|
61
|
+
def _sampling(self, *, max_tokens: int | None = None, temperature: float = 0.0, logprobs: int | None = None, **_):
|
|
62
|
+
from vllm import SamplingParams
|
|
63
|
+
|
|
64
|
+
return SamplingParams(
|
|
65
|
+
max_tokens=max_tokens or self.runtime.max_new_tokens,
|
|
66
|
+
temperature=temperature,
|
|
67
|
+
logprobs=logprobs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# -- interface -----------------------------------------------------
|
|
71
|
+
def generate(self, inputs: Any, **kwargs) -> str:
|
|
72
|
+
out = self._engine.generate([self._as_prompt(inputs)], self._sampling(**kwargs))
|
|
73
|
+
return out[0].outputs[0].text
|
|
74
|
+
|
|
75
|
+
def chat(self, messages: list, tools=None) -> ChatTurn:
|
|
76
|
+
if Capability.TOOL_CALLS not in self.capabilities:
|
|
77
|
+
raise CapabilityError(analyzer="chat", model=repr(self), missing={Capability.TOOL_CALLS})
|
|
78
|
+
tok = self._engine.get_tokenizer()
|
|
79
|
+
text = tok.apply_chat_template(
|
|
80
|
+
messages, tools=tools, add_generation_prompt=True, tokenize=False, **self.spec.chat_template_kwargs
|
|
81
|
+
)
|
|
82
|
+
out = self._engine.generate([text], self._sampling())
|
|
83
|
+
return ChatTurn(text=out[0].outputs[0].text, raw_tool_calls=None) # codec parses the text
|
|
84
|
+
|
|
85
|
+
def logprobs(self, inputs: Any, top_k: int = 5, **kwargs) -> list[TokenLogprob]:
|
|
86
|
+
out = self._engine.generate([self._as_prompt(inputs)], self._sampling(logprobs=top_k, **kwargs))
|
|
87
|
+
comp = out[0].outputs[0]
|
|
88
|
+
tok = self._engine.get_tokenizer()
|
|
89
|
+
result: list[TokenLogprob] = []
|
|
90
|
+
for tid, lp_dict in zip(comp.token_ids, comp.logprobs or []):
|
|
91
|
+
entry = lp_dict.get(tid)
|
|
92
|
+
chosen = float(getattr(entry, "logprob", entry)) if entry is not None else 0.0
|
|
93
|
+
chosen_tok = getattr(entry, "decoded_token", None) or tok.decode([tid])
|
|
94
|
+
top = {
|
|
95
|
+
(getattr(v, "decoded_token", None) or tok.decode([k])): float(getattr(v, "logprob", v))
|
|
96
|
+
for k, v in lp_dict.items()
|
|
97
|
+
}
|
|
98
|
+
result.append(TokenLogprob(token=chosen_tok, logprob=chosen, top=top))
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def forward(self, inputs: Any, capture: set[Capability], spec=None) -> Trace:
|
|
102
|
+
raise CapabilityError( # vLLM exposes no internals — use hf_local for white-box
|
|
103
|
+
analyzer="forward", model=repr(self), missing=set(capture) or {Capability.HIDDEN_STATES},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
status = "loaded" if self._llm is not None else "lazy"
|
|
108
|
+
return f"VLLMOfflineModel(key={self.spec.key!r}, {status})"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class VLLMOfflineBackend(Backend):
|
|
112
|
+
kind = "vllm_offline"
|
|
113
|
+
capabilities = frozenset({Capability.GENERATE, Capability.TOOL_CALLS, Capability.LOGPROBS})
|
|
114
|
+
|
|
115
|
+
def build(self, spec, runtime: RuntimeConfig) -> VLLMOfflineModel:
|
|
116
|
+
return VLLMOfflineModel(spec, runtime)
|
evalrx/models/base.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Model base classes.
|
|
2
|
+
|
|
3
|
+
The abstract contract lives in :mod:`evalrx.core.model` (:class:`Model`,
|
|
4
|
+
:class:`Trace`). This module re-exports it and adds the agent base.
|
|
5
|
+
Deployment-specific bases live alongside their models:
|
|
6
|
+
- :class:`~evalrx.models.whitebox.base.WhiteboxModel` (local weights)
|
|
7
|
+
- :class:`~evalrx.models.blackbox.base.BlackboxModel` (API-only)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import abstractmethod
|
|
13
|
+
|
|
14
|
+
from evalrx.core.model import Model, Trace
|
|
15
|
+
|
|
16
|
+
__all__ = ["Model", "Trace", "BaseAgent"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseAgent(Model):
|
|
20
|
+
"""Abstract base for agent-mode models that use tools over multiple steps."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def step(self, observation: str, **kwargs) -> str:
|
|
24
|
+
"""Process one agent step and return the next action/response."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Black-box API agent with tool calling.
|
|
2
|
+
|
|
3
|
+
Planned for Stage 2.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from evalrx.core.capability import Capability
|
|
11
|
+
from evalrx.core.model import Trace
|
|
12
|
+
from evalrx.models.base import BaseAgent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BlackboxAgent(BaseAgent):
|
|
16
|
+
"""API-based agent model."""
|
|
17
|
+
|
|
18
|
+
capabilities = frozenset({Capability.GENERATE})
|
|
19
|
+
|
|
20
|
+
def __init__(self, model_id: str, api_key: str | None = None) -> None:
|
|
21
|
+
self.model_id = model_id
|
|
22
|
+
self.api_key = api_key
|
|
23
|
+
|
|
24
|
+
def generate(self, inputs: Any, **kwargs) -> str:
|
|
25
|
+
raise NotImplementedError("BlackboxAgent is planned for Stage 2.")
|
|
26
|
+
|
|
27
|
+
def forward(self, inputs: Any, capture: set[Capability]) -> Trace:
|
|
28
|
+
raise NotImplementedError("Black-box models expose no internals.")
|
|
29
|
+
|
|
30
|
+
def step(self, observation: str, **kwargs) -> str:
|
|
31
|
+
raise NotImplementedError("BlackboxAgent is planned for Stage 2.")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Base class for black-box (API-only) models.
|
|
2
|
+
|
|
3
|
+
Black-box models have no parameter access, so they provide only output-level
|
|
4
|
+
capabilities (``GENERATE``, sometimes ``LOGITS``). Capability matching means
|
|
5
|
+
attention/saliency/probing analyzers are simply *not offered* for them — the
|
|
6
|
+
agent sees an empty (or small) compatible-analyzer set and never tries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from evalrx.core.capability import Capability
|
|
14
|
+
from evalrx.core.model import Model, Trace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BlackboxModel(Model):
|
|
18
|
+
"""Base for API-based models (no local weights)."""
|
|
19
|
+
|
|
20
|
+
capabilities = frozenset({Capability.GENERATE})
|
|
21
|
+
|
|
22
|
+
def __init__(self, model_id: str, api_key: str | None = None) -> None:
|
|
23
|
+
self.model_id = model_id
|
|
24
|
+
self.api_key = api_key
|
|
25
|
+
|
|
26
|
+
def forward(self, inputs: Any, capture: set[Capability]) -> Trace:
|
|
27
|
+
raise NotImplementedError(
|
|
28
|
+
"Black-box models expose no internals; only generate() is available."
|
|
29
|
+
)
|