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,279 @@
|
|
|
1
|
+
"""Gemini API model — black-box GENERATE + native tool-calling via Google's Gen AI SDK.
|
|
2
|
+
|
|
3
|
+
Install the optional dependency::
|
|
4
|
+
|
|
5
|
+
pip install evalrx[gemini]
|
|
6
|
+
|
|
7
|
+
Two roles:
|
|
8
|
+
|
|
9
|
+
* judge — ``GeminiModel(api_key=...)`` as before (``generate()``), and
|
|
10
|
+
* **agent base** — ``chat(messages, tools=...)`` speaks the agent loop's
|
|
11
|
+
protocol with Gemini's NATIVE function calling (not an OpenAI-compat
|
|
12
|
+
bridge, whose tool-call shims are historically quirky). Images in
|
|
13
|
+
content-block messages are sent as inline PNG parts, so multimodal
|
|
14
|
+
tool loops (zoom crops fed back) work like on the other backends.
|
|
15
|
+
|
|
16
|
+
The loop threads history in OpenAI-ish form (assistant ``tool_calls`` /
|
|
17
|
+
``role="tool"`` results, via :class:`~evalrx.models.toolcodec.OpenAIToolCodec`
|
|
18
|
+
— ``tool_call_style = "native"`` routes it); this module converts that history
|
|
19
|
+
to ``google-genai`` contents per call.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
from evalrx.core.capability import Capability
|
|
29
|
+
from evalrx.core.case import Inputs
|
|
30
|
+
from evalrx.core.tool import ChatTurn
|
|
31
|
+
from evalrx.models.blackbox.base import BlackboxModel, Trace
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _png_bytes(image: Any) -> bytes:
|
|
35
|
+
"""Encode a PIL image (or open a path) as PNG bytes."""
|
|
36
|
+
import io
|
|
37
|
+
|
|
38
|
+
if not (hasattr(image, "save") and hasattr(image, "mode")):
|
|
39
|
+
from PIL import Image
|
|
40
|
+
|
|
41
|
+
image = Image.open(image).convert("RGB")
|
|
42
|
+
buf = io.BytesIO()
|
|
43
|
+
image.save(buf, format="PNG")
|
|
44
|
+
return buf.getvalue()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_AUDIO_MIME = {"wav": "audio/wav", "mp3": "audio/mp3", "flac": "audio/flac",
|
|
48
|
+
"ogg": "audio/ogg", "m4a": "audio/mp4"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _audio_bytes(audio: Any) -> "tuple[bytes, str]":
|
|
52
|
+
"""``(bytes, mime_type)`` for an audio path / raw bytes / waveform.
|
|
53
|
+
|
|
54
|
+
Reuses the endpoint path's encoder (a path keeps its own format; bytes are
|
|
55
|
+
taken as WAV; a waveform is written as 16-bit PCM WAV), so the two API
|
|
56
|
+
backends send the same bytes for the same case.
|
|
57
|
+
"""
|
|
58
|
+
import base64
|
|
59
|
+
|
|
60
|
+
from evalrx.models.backends.openai_compat import _to_input_audio
|
|
61
|
+
|
|
62
|
+
encoded = _to_input_audio(audio)
|
|
63
|
+
return base64.b64decode(encoded["data"]), _AUDIO_MIME.get(encoded["format"], "audio/wav")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_genai_contents(messages: list, types: Any) -> "tuple[Optional[str], list]":
|
|
67
|
+
"""Convert loop messages (OpenAI-ish history + content blocks) into
|
|
68
|
+
``(system_instruction, genai contents)``.
|
|
69
|
+
|
|
70
|
+
``role="tool"`` results become ``function_response`` parts; the function
|
|
71
|
+
name comes from the preceding assistant turn's tool call (the loop is
|
|
72
|
+
single-call-per-turn, so pairing by order is exact).
|
|
73
|
+
"""
|
|
74
|
+
system: Optional[str] = None
|
|
75
|
+
contents: list = []
|
|
76
|
+
last_call_name = ""
|
|
77
|
+
for msg in messages:
|
|
78
|
+
role = msg.get("role")
|
|
79
|
+
content = msg.get("content")
|
|
80
|
+
if role == "system":
|
|
81
|
+
system = content if isinstance(content, str) else str(content)
|
|
82
|
+
continue
|
|
83
|
+
if role == "tool":
|
|
84
|
+
contents.append(
|
|
85
|
+
types.Content(
|
|
86
|
+
role="user",
|
|
87
|
+
parts=[
|
|
88
|
+
types.Part(
|
|
89
|
+
function_response=types.FunctionResponse(
|
|
90
|
+
name=last_call_name or "tool",
|
|
91
|
+
response={"result": content},
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
continue
|
|
98
|
+
if role == "assistant":
|
|
99
|
+
parts = []
|
|
100
|
+
if isinstance(content, str) and content:
|
|
101
|
+
parts.append(types.Part(text=content))
|
|
102
|
+
for tc in msg.get("tool_calls") or []:
|
|
103
|
+
fn = tc.get("function", tc)
|
|
104
|
+
args = fn.get("arguments", {})
|
|
105
|
+
if isinstance(args, str):
|
|
106
|
+
try:
|
|
107
|
+
args = json.loads(args) if args.strip() else {}
|
|
108
|
+
except json.JSONDecodeError:
|
|
109
|
+
args = {"_raw": args}
|
|
110
|
+
last_call_name = fn.get("name", "")
|
|
111
|
+
parts.append(
|
|
112
|
+
types.Part(
|
|
113
|
+
function_call=types.FunctionCall(name=last_call_name, args=args)
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
if parts:
|
|
117
|
+
contents.append(types.Content(role="model", parts=parts))
|
|
118
|
+
continue
|
|
119
|
+
# user message: plain text or content blocks (text / image)
|
|
120
|
+
parts = []
|
|
121
|
+
if isinstance(content, list):
|
|
122
|
+
for block in content:
|
|
123
|
+
if isinstance(block, dict) and block.get("type") == "image":
|
|
124
|
+
parts.append(
|
|
125
|
+
types.Part.from_bytes(
|
|
126
|
+
data=_png_bytes(block.get("image")), mime_type="image/png"
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
elif isinstance(block, dict) and block.get("type") == "audio":
|
|
130
|
+
data, mime = _audio_bytes(block.get("audio"))
|
|
131
|
+
parts.append(types.Part.from_bytes(data=data, mime_type=mime))
|
|
132
|
+
elif isinstance(block, dict) and block.get("type") == "text":
|
|
133
|
+
parts.append(types.Part(text=block.get("text", "")))
|
|
134
|
+
else:
|
|
135
|
+
parts.append(types.Part(text=content if isinstance(content, str) else str(content)))
|
|
136
|
+
contents.append(types.Content(role="user", parts=parts))
|
|
137
|
+
return system, contents
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _to_genai_tools(tools: Optional[list], types: Any) -> Optional[list]:
|
|
141
|
+
"""OpenAI tool schemas -> genai Tool with FunctionDeclarations.
|
|
142
|
+
|
|
143
|
+
Prefers ``parameters_json_schema`` (raw JSON schema passthrough, newer
|
|
144
|
+
SDKs); falls back to ``parameters`` coercion on older ones.
|
|
145
|
+
"""
|
|
146
|
+
if not tools:
|
|
147
|
+
return None
|
|
148
|
+
decls = []
|
|
149
|
+
for t in tools:
|
|
150
|
+
fn = t.get("function", t)
|
|
151
|
+
try:
|
|
152
|
+
decls.append(
|
|
153
|
+
types.FunctionDeclaration(
|
|
154
|
+
name=fn.get("name", ""),
|
|
155
|
+
description=fn.get("description", ""),
|
|
156
|
+
parameters_json_schema=fn.get("parameters"),
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
except Exception: # older SDK without parameters_json_schema
|
|
160
|
+
decls.append(
|
|
161
|
+
types.FunctionDeclaration(
|
|
162
|
+
name=fn.get("name", ""),
|
|
163
|
+
description=fn.get("description", ""),
|
|
164
|
+
parameters=fn.get("parameters"),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return [types.Tool(function_declarations=decls)]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class GeminiModel(BlackboxModel):
|
|
171
|
+
"""Black-box wrapper around the Google Gemini API.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
model_id: Gemini model name (default: ``"gemini-2.5-flash"``).
|
|
175
|
+
api_key: API key. Falls back to the ``GEMINI_API_KEY`` environment
|
|
176
|
+
variable when ``None``.
|
|
177
|
+
client: An injected ``genai.Client``-shaped object (tests, custom
|
|
178
|
+
transports); lazily created from *api_key* when ``None``.
|
|
179
|
+
|
|
180
|
+
Requires ``pip install google-genai`` (``evalrx[gemini]``).
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
capabilities = frozenset({Capability.GENERATE, Capability.TOOL_CALLS})
|
|
184
|
+
modalities = frozenset({"text", "image"})
|
|
185
|
+
tool_call_style = "native" # codec_for -> OpenAIToolCodec (structured calls)
|
|
186
|
+
|
|
187
|
+
def __init__(
|
|
188
|
+
self,
|
|
189
|
+
model_id: str = "gemini-2.5-flash",
|
|
190
|
+
api_key: str | None = None,
|
|
191
|
+
client: Any = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
super().__init__(
|
|
194
|
+
model_id=model_id,
|
|
195
|
+
api_key=api_key or os.getenv("GEMINI_API_KEY"),
|
|
196
|
+
)
|
|
197
|
+
self._client = client
|
|
198
|
+
|
|
199
|
+
# -- plumbing ------------------------------------------------------
|
|
200
|
+
def _genai(self):
|
|
201
|
+
try:
|
|
202
|
+
from google import genai
|
|
203
|
+
from google.genai import types
|
|
204
|
+
except ImportError as exc:
|
|
205
|
+
raise ImportError(
|
|
206
|
+
"GeminiModel requires the google-genai package. "
|
|
207
|
+
"Install it with: pip install 'evalrx[gemini]'"
|
|
208
|
+
) from exc
|
|
209
|
+
if self._client is None:
|
|
210
|
+
if not self.api_key:
|
|
211
|
+
raise ValueError(
|
|
212
|
+
"No Gemini API key found. Pass api_key= or set GEMINI_API_KEY."
|
|
213
|
+
)
|
|
214
|
+
self._client = genai.Client(api_key=self.api_key)
|
|
215
|
+
return self._client, types
|
|
216
|
+
|
|
217
|
+
# -- interface -----------------------------------------------------
|
|
218
|
+
def generate(self, inputs: Any, **kwargs) -> str:
|
|
219
|
+
client, types = self._genai()
|
|
220
|
+
image = getattr(inputs, "image", None) if isinstance(inputs, Inputs) else None
|
|
221
|
+
prompt = inputs.prompt if isinstance(inputs, Inputs) else str(inputs)
|
|
222
|
+
contents: list = []
|
|
223
|
+
if image is not None:
|
|
224
|
+
contents.append(types.Part.from_bytes(data=_png_bytes(image), mime_type="image/png"))
|
|
225
|
+
contents.append(prompt)
|
|
226
|
+
response = client.models.generate_content(
|
|
227
|
+
model=self.model_id, contents=contents, **kwargs
|
|
228
|
+
)
|
|
229
|
+
return response.text or ""
|
|
230
|
+
|
|
231
|
+
def chat(self, messages: list, tools=None) -> ChatTurn:
|
|
232
|
+
"""One tool-aware turn via native Gemini function calling."""
|
|
233
|
+
client, types = self._genai()
|
|
234
|
+
system, contents = _to_genai_contents(messages, types)
|
|
235
|
+
config = types.GenerateContentConfig(
|
|
236
|
+
system_instruction=system,
|
|
237
|
+
tools=_to_genai_tools(tools, types),
|
|
238
|
+
temperature=0.0, # diagnosed runs should be as deterministic as the API allows
|
|
239
|
+
)
|
|
240
|
+
response = client.models.generate_content(
|
|
241
|
+
model=self.model_id, contents=contents, config=config
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
texts: list[str] = []
|
|
245
|
+
raw_calls: list[dict] = []
|
|
246
|
+
candidates = getattr(response, "candidates", None) or []
|
|
247
|
+
parts = getattr(candidates[0].content, "parts", None) or [] if candidates else []
|
|
248
|
+
for part in parts:
|
|
249
|
+
if getattr(part, "text", None):
|
|
250
|
+
texts.append(part.text)
|
|
251
|
+
fc = getattr(part, "function_call", None)
|
|
252
|
+
if fc is not None:
|
|
253
|
+
raw_calls.append(
|
|
254
|
+
{
|
|
255
|
+
"id": getattr(fc, "id", None),
|
|
256
|
+
"function": {
|
|
257
|
+
"name": fc.name,
|
|
258
|
+
"arguments": json.dumps(dict(fc.args or {})),
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
um = getattr(response, "usage_metadata", None)
|
|
263
|
+
usage = (
|
|
264
|
+
{
|
|
265
|
+
"prompt_tokens": int(getattr(um, "prompt_token_count", 0) or 0),
|
|
266
|
+
"completion_tokens": int(getattr(um, "candidates_token_count", 0) or 0),
|
|
267
|
+
}
|
|
268
|
+
if um is not None
|
|
269
|
+
else None
|
|
270
|
+
)
|
|
271
|
+
return ChatTurn(text="".join(texts), raw_tool_calls=raw_calls or None, usage=usage)
|
|
272
|
+
|
|
273
|
+
def forward(self, inputs: Any, capture: set[Capability], spec: Any = None) -> Trace: # type: ignore[override]
|
|
274
|
+
raise NotImplementedError(
|
|
275
|
+
"GeminiModel is a black-box model; only generate()/chat() are available."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def __repr__(self) -> str:
|
|
279
|
+
return f"GeminiModel(model_id={self.model_id!r})"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Black-box LLM via API (OpenAI, Anthropic, Google, etc.).
|
|
2
|
+
|
|
3
|
+
Planned for Stage 2.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from evalrx.models.blackbox.base import BlackboxModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BlackboxLLM(BlackboxModel):
|
|
14
|
+
"""API-based LLM — no local weights, output-level capabilities only."""
|
|
15
|
+
|
|
16
|
+
def generate(self, inputs: Any, **kwargs) -> str:
|
|
17
|
+
raise NotImplementedError("BlackboxLLM is planned for Stage 2.")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Black-box VLM via API (GPT-4V, Gemini Vision, etc.).
|
|
2
|
+
|
|
3
|
+
Planned for Stage 2.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from evalrx.models.blackbox.base import BlackboxModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BlackboxVLM(BlackboxModel):
|
|
14
|
+
"""API-based vision-language model."""
|
|
15
|
+
|
|
16
|
+
def generate(self, inputs: Any, **kwargs) -> str:
|
|
17
|
+
raise NotImplementedError("BlackboxVLM is planned for Stage 2.")
|
evalrx/models/compose.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""compose() — combine a ModelSpec with a Backend into a Model.
|
|
2
|
+
|
|
3
|
+
This is where the orthogonal decomposition becomes a single object:
|
|
4
|
+
|
|
5
|
+
handle = compose(spec, "hf_local", want={Capability.ATTENTION})
|
|
6
|
+
|
|
7
|
+
Capability negotiation happens HERE (before any weights load): if you ask for
|
|
8
|
+
``ATTENTION`` against the ``api`` backend, you get a clear :class:`CapabilityError`
|
|
9
|
+
up front rather than a crash deep inside a hook. ``api_only`` specs (closed
|
|
10
|
+
weights) refuse every backend except ``api``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Iterable
|
|
16
|
+
|
|
17
|
+
from evalrx.core.capability import Capability, CapabilityError
|
|
18
|
+
from evalrx.models.backends import BACKENDS, Backend, RuntimeConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def negotiate(handle, want: Iterable[Capability], model_id: str, where: str):
|
|
22
|
+
"""Raise ``CapabilityError`` if *handle* cannot provide every wanted capability.
|
|
23
|
+
|
|
24
|
+
Shared by :func:`compose` (spec/registry path) and :func:`evalrx.wrap`
|
|
25
|
+
(live-model path) so both negotiate capabilities identically.
|
|
26
|
+
"""
|
|
27
|
+
missing = set(want) - set(handle.capabilities)
|
|
28
|
+
if missing:
|
|
29
|
+
raise CapabilityError(analyzer=where, model=model_id, missing=missing)
|
|
30
|
+
return handle
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def compose(
|
|
34
|
+
spec,
|
|
35
|
+
backend: "str | Backend" = "hf_local",
|
|
36
|
+
runtime: RuntimeConfig | None = None,
|
|
37
|
+
want: Iterable[Capability] = (),
|
|
38
|
+
):
|
|
39
|
+
"""Build a Model for *spec* on *backend*, negotiating capabilities first.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
spec: a :class:`~evalrx.core.spec.ModelSpec` (or a registry key str).
|
|
43
|
+
backend: backend name (``"api"`` / ``"hf_local"`` / ``"vllm_offline"``) or instance.
|
|
44
|
+
runtime: :class:`RuntimeConfig`; defaults applied if omitted.
|
|
45
|
+
want: capabilities the caller needs; raises if the backend can't provide them.
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(spec, str):
|
|
48
|
+
from evalrx.specs import get_spec
|
|
49
|
+
|
|
50
|
+
spec = get_spec(spec)
|
|
51
|
+
if isinstance(backend, str):
|
|
52
|
+
if backend not in BACKENDS:
|
|
53
|
+
raise KeyError(f"Unknown backend {backend!r}. Known: {sorted(BACKENDS)}")
|
|
54
|
+
backend = BACKENDS[backend]()
|
|
55
|
+
runtime = runtime or RuntimeConfig()
|
|
56
|
+
|
|
57
|
+
if spec.api_only and backend.kind != "api":
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"{spec.key!r} is api-only (closed weights); cannot use backend {backend.kind!r}."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Build first (lazy — no weights load), then negotiate against the ACTUAL
|
|
63
|
+
# handle capabilities. This is precise for conditional caps like TOOL_CALLS,
|
|
64
|
+
# which depend on the model (chat template), not just the backend.
|
|
65
|
+
handle = backend.build(spec, runtime)
|
|
66
|
+
return negotiate(handle, want, model_id=spec.key, where=f"request@{backend.kind}")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Infer a :class:`~evalrx.core.spec.ModelSpec` from a live, already-loaded model.
|
|
2
|
+
|
|
3
|
+
The curated path (``evalrx.load("qwen...")``) looks a spec up by registry key.
|
|
4
|
+
The public on-ramp (``evalrx.wrap(model, tokenizer)``) has no key — the user
|
|
5
|
+
brings their own loaded HF model — so we *infer* the same ``ModelSpec`` fields from
|
|
6
|
+
the live ``model.config`` and tokenizer instead. Both paths then feed the identical
|
|
7
|
+
:class:`~evalrx.models.backends.hf_local.HFLocalModel`, so every analyzer sees one
|
|
8
|
+
contract.
|
|
9
|
+
|
|
10
|
+
This module is torch-free: it only reads attributes off objects the caller already
|
|
11
|
+
constructed.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from evalrx.core.spec import AttnSemantics, ModelSpec
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_vlm(config: Any) -> bool:
|
|
22
|
+
"""Best-effort detection of a vision-language config.
|
|
23
|
+
|
|
24
|
+
A text decoder-only config has no separate vision tower; VLM configs carry a
|
|
25
|
+
nested ``vision_config`` (Qwen-VL, LLaVA, GLM-V, …) or expose a distinct text
|
|
26
|
+
sub-config via ``get_text_config()``.
|
|
27
|
+
"""
|
|
28
|
+
if getattr(config, "vision_config", None) is not None:
|
|
29
|
+
return True
|
|
30
|
+
if hasattr(config, "get_text_config"):
|
|
31
|
+
try:
|
|
32
|
+
text_cfg = config.get_text_config()
|
|
33
|
+
except Exception: # pragma: no cover - defensive
|
|
34
|
+
text_cfg = None
|
|
35
|
+
if text_cfg is not None and text_cfg is not config:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _chat_template(tokenizer: Any) -> str:
|
|
41
|
+
"""Return the tokenizer's chat template string (``""`` if none)."""
|
|
42
|
+
tok = getattr(tokenizer, "tokenizer", tokenizer) # processor -> inner tokenizer
|
|
43
|
+
return getattr(tok, "chat_template", None) or ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def infer_spec(model: Any, tokenizer: Any) -> ModelSpec:
|
|
47
|
+
"""Build a :class:`ModelSpec` describing an already-loaded HF causal LM.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
model: an instantiated ``transformers`` decoder-only model.
|
|
51
|
+
tokenizer: its tokenizer (or processor).
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A ``ModelSpec`` carrying only the fields ``HFLocalModel`` reads at analysis
|
|
55
|
+
time (identity, ``tool_calling``, ``attn_semantics``). ``hf_repo`` is left
|
|
56
|
+
empty because the weights are already in memory — ``HFLocalModel`` never
|
|
57
|
+
calls ``load()`` on a wrapped model.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
NotImplementedError: if *model* looks like a VLM. ``wrap()`` supports text
|
|
61
|
+
decoder-only models in this pass; VLM forward-capture (image tokens +
|
|
62
|
+
TokenTypeMap) is Stage 2 — consistent with ``HFLocalModel.forward``.
|
|
63
|
+
"""
|
|
64
|
+
config = getattr(model, "config", None)
|
|
65
|
+
if config is None:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"wrap() expects a transformers model with a .config; "
|
|
68
|
+
f"got {type(model).__name__} with none."
|
|
69
|
+
)
|
|
70
|
+
if _is_vlm(config):
|
|
71
|
+
raise NotImplementedError(
|
|
72
|
+
"wrap() supports text decoder-only models; VLM internals capture "
|
|
73
|
+
"(image tokens + TokenTypeMap) is Stage 2."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
model_type = getattr(config, "model_type", None) or "unknown"
|
|
77
|
+
key = getattr(config, "_name_or_path", "") or model_type or "wrapped-model"
|
|
78
|
+
tool_calling = "tools" in _chat_template(tokenizer)
|
|
79
|
+
|
|
80
|
+
return ModelSpec(
|
|
81
|
+
key=key,
|
|
82
|
+
family=model_type,
|
|
83
|
+
model_type=model_type,
|
|
84
|
+
hf_repo="", # already in memory — never reloaded
|
|
85
|
+
tool_calling=tool_calling,
|
|
86
|
+
eager_required_for_attn=True,
|
|
87
|
+
attn_semantics=AttnSemantics.STANDARD,
|
|
88
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Architecture-aware executors for published model-health repairs.
|
|
2
|
+
|
|
3
|
+
These modules are deliberately small and explicit. A method is exposed here
|
|
4
|
+
only when its model access contract can be checked by the backend; an example
|
|
5
|
+
or benchmark runner must not silently relabel a prompt trick as the paper
|
|
6
|
+
method.
|
|
7
|
+
"""
|
|
8
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Hugging Face adapter for AAD's released per-token contrastive sampler.
|
|
2
|
+
|
|
3
|
+
AAD (Hsu et al. 2025, arXiv:2506.07233, "Reducing Object Hallucination in
|
|
4
|
+
Large Audio-Language Models via Audio-Aware Decoding") contrasts the
|
|
5
|
+
with-audio next-token distribution against the SAME prompt with the audio
|
|
6
|
+
waveform replaced by silence (zeros), at every decode step:
|
|
7
|
+
|
|
8
|
+
logits' = (1 + alpha) * logit(y_t | audio, text, y<t)
|
|
9
|
+
- alpha * logit(y_t | silence, text, y<t)
|
|
10
|
+
|
|
11
|
+
No adaptive-plausibility cutoff -- unlike VCD, the released formula (and this
|
|
12
|
+
adapter, to stay faithful to it) does not mask sub-threshold candidates.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AADLogitsProcessor:
|
|
21
|
+
"""Contrast clean (real-audio) scores with a cached silent-audio pass.
|
|
22
|
+
|
|
23
|
+
Mirrors VCDLogitsProcessor's KV-cache reuse (the released reference
|
|
24
|
+
implementation reruns the full growing sequence every step; caching the
|
|
25
|
+
silent branch's own past_key_values gets the identical formula without
|
|
26
|
+
that O(n^2) cost).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, model: Any, silent_inputs: dict[str, Any], *, alpha: float) -> None:
|
|
30
|
+
self.model = model
|
|
31
|
+
self.silent_inputs = silent_inputs
|
|
32
|
+
self.alpha = float(alpha)
|
|
33
|
+
self._silent_output: Any = None
|
|
34
|
+
|
|
35
|
+
def __call__(self, input_ids: Any, scores: Any) -> Any:
|
|
36
|
+
import torch
|
|
37
|
+
|
|
38
|
+
with torch.no_grad():
|
|
39
|
+
if self._silent_output is None:
|
|
40
|
+
self._silent_output = self.model(
|
|
41
|
+
**self.silent_inputs,
|
|
42
|
+
use_cache=True,
|
|
43
|
+
return_dict=True,
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
self._silent_output = self.model(
|
|
47
|
+
input_ids=input_ids[:, -1:],
|
|
48
|
+
past_key_values=self._silent_output.past_key_values,
|
|
49
|
+
use_cache=True,
|
|
50
|
+
return_dict=True,
|
|
51
|
+
)
|
|
52
|
+
silent_scores = self._silent_output.logits[:, -1, :]
|
|
53
|
+
return (1.0 + self.alpha) * scores - self.alpha * silent_scores
|