Skip to content

Add agent scope observation and advice tools - #4

Merged
Scaxlibur merged 5 commits into
Scaxlibur:Devfrom
Epslion404:split/agent-scope-observe-advise
Oct 5, 2026
Merged

Scaxlibur merged 5 commits into
Scaxlibur:Devfrom
Epslion404:split/agent-scope-observe-advise

Conversation

@Epslion404

@Epslion404 Epslion404 commented Jul 28, 2026 •

Copy link
Copy Markdown
Collaborator

Adds scope observation and advice through MCP without waveform reads, plus an explicit CLI waveform-report path. Sequential channel reads declare that they are not from one acquisition and omit phase, delay, correlation, and intersection results. Advice is returned for the operator to execute explicitly.

The CLI validates advice targets before loading configuration or constructing instrument services. Generated focus commands use the current CHANNEL=V_PER_DIV syntax and --hide-others flag. Expectation summaries retain unavailable channels and report incomplete acceptance instead of pass. The synchronized analysis helper jointly fits a constant and fundamental sine/cosine terms so DC offsets do not bias phase in non-integer-cycle windows.

Validation includes fake-service tests proving invalid targets cause no instrument I/O, parsing generated recommendations through the real CLI, missing-channel acceptance tests, and a 4.5-cycle / 1 kHz / 90-degree synthetic pair with a 5 V offset. Focused and related integration tests: 258 passed, 3 subtests passed. Ruff, scoped strict documentation audit, generated Reference drift check, and strict MkDocs build pass. No real instruments were accessed.

The full Windows Python 3.12 suite with CI extras and PYTHONUTF8=1 produced 2569 passed, 4 skipped, 221 subtests passed, and one failure in test_windows_job_rejects_allocation. That failure reproduced on unchanged master (44506c4): OpenBLAS exited while allocating memory under the worker limit before Python could report MemoryError. With OPENBLAS_NUM_THREADS=1, the master test passed and the feature branch's entire test_analysis_execution.py module passed (17 passed, 3 skipped). No source changes were made for this environment condition.

@Scaxlibur Scaxlibur left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这组功能方向合理,但当前实现同时存在仪器状态边界和测量正确性问题。尤其是 fetch_waveform=true 会实际改变示波器状态,却仍被暴露为只读工具;多通道关系、expectation 和相位结果也可能给出具有误导性的结论。因此建议先修复以下问题,再合并本 PR。

fetched_waveforms: dict[int, WaveformData],
) -> dict[str, Any]:
service.require_high_impedance(channel, allow_50ohm=allow_50ohm)
waveform = service.fetch_waveform(channel=channel)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] scope.observe(fetch_waveform=true) 最终会调用 ScopeService.fetch_waveform()。对于默认使用 dmax 的 DS1000Z/DS1104Z,该路径会发送 :STOP,同时可能启用通道并修改 waveform source/mode/format,而且当前不会恢复原采集状态。因此一次被描述为“只读”的调用可能让正在运行的示波器停下来。

这里却同时返回 read_only=true,/tools 描述也声称不会改变仪器状态,instrument_state_effects 还漏掉了 acquisition stop。建议将 waveform fetch 拆成明确标记为 mutating 的工具并要求显式确认;如果仍保留在 scope.observe,则必须修正工具元数据、文档和完整状态影响说明,并设计失败路径下可证明的状态恢复。

expectations=normalized_expectations,
expectation_results=expectation_results,
)
for observed_channel in observed_channels

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 当前每个通道分别调用 fetch_waveform(),而每次调用还会单独打开 instrument session。示波器处于运行状态时,CH1 和 CH2 可能来自不同 acquisition,但代码仍会计算相位、延迟、相关性和交点,这些结果不能被当作同步测量。

此外,MCP 使用 ThreadingHTTPServer,多个请求还可能同时争用同一台仪器的全局 waveform source。建议在一个持久 session 内冻结或执行一次 acquisition 后批量读取全部通道,并按 instrument resource 序列化访问。若无法证明波形来自同一次 acquisition,应返回 warning,并跳过需要同步性的关系分析。

elif "warn" in statuses:
status = "warn"
else:
status = "pass"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 当 expectation 字段无效、拼写错误或越界时,当前解析逻辑会静默忽略该字段;如果最终没有生成任何 check,这里仍会返回 status="pass"。例如 {"frequency_hz": "typo"}、{"vpp_v": -1} 或拼错的 {"frequncy_hz": 1000} 都会得到 pass, checks=[]。

MCP 发布的 JSON Schema 并未在服务端真正执行,不能依赖客户端自动遵守。建议在运行时完整校验字段名、类型、有限性和取值范围,遇到无效输入直接返回 ConfigError;如果没有任何可执行检查,状态至少应为 skipped 或 error,不能是 pass。

}
pearson = float(np.mean(left * right))
correlation = np.correlate(right, left, mode="full") / left.size
index = int(np.argmax(np.abs(correlation)))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 当前按互相关绝对值选择峰值,随后只根据 lag 计算相位,因此会丢失相关性的正负号。对于 right = -left 的同频正弦,实际结果为 correlation -1、lag 0,最终报告相位 0°,但正确结果应为 180°。

建议明确相位正负约定,并在相关峰为负时处理额外的 180° 相移,或者改用频域基波相位差。测试也应断言具体角度,而不只是检查结果“不是 None”,至少覆盖 0°、±90° 和 180°。

Comment thread src/wavebench/data/expectations.py Outdated
diffs = np.diff(centered)
if diffs.size < 3:
return None
signs = np.sign(diffs)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 这里直接根据相邻样本差分的符号变化寻找局部极值,对真实示波器噪声和量化误差非常敏感。验证中,30% symmetry、1 Vpp 的三角波加入 1 mV 噪声后被估计为约 5.56%,加入 5 mV 后变成约 50%。目前的理想无噪声测试无法暴露这个问题。

建议先进行受控平滑或滞回处理,并利用期望频率限制相邻周期/极值间距;也可以改为按完整周期做鲁棒斜率或峰谷拟合。请增加包含噪声、量化台阶和轻微过冲的测试样本,避免 expectation 在真实波形上随机 pass/fail。

def _summary_frequency(summary: dict[str, Any] | None) -> float | None:
if summary is None:
return None
value = summary.get("frequency_estimate_hz")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] _summary_frequency() 当前只检查频率是否为正,没有检查 quality_warnings。因此即使 summary 已标记 low_cycle_count,该低置信估计仍会优先于用户提供的 expectation,并被用于生成 scope focus --time-range 建议。

建议复用 relationships 中已有的 trusted-frequency 判断:发现 low_cycle_count 等置信度告警时,优先回退到 expectation;没有可靠 expectation 时则不要生成基于该频率的时基建议,并在结果中明确说明原因。

Nept Epslion added 4 commits September 28, 2026 13:15
Address the PR #4 review by separating the mutating waveform path from the
read-only MCP surface:

- `scope.observe` / `scope.advise` no longer read waveforms; both stay
  strictly read-only and are described as such in the tool metadata
- add an explicit `scope observe --fetch-waveform` CLI path (experimental)
  for waveform summaries, expectation checks and relationships
- skip correlation, intersections and phase when the channels are not from
  one acquisition, and say so instead of reporting noisy numbers
- validate expectation fields strictly (name, type, finiteness, range) and
  report `skipped` when no check is executable
- derive phase from the fundamental instead of the cross-correlation peak lag
- make triangle symmetry estimation robust to noise, quantization and overshoot
- withhold timebase advice when the measured frequency is low confidence
- list the current MCP tools with their read-only boundary and point at
  `GET /tools` as the canonical source
- mark the new CLI command and MCP tools as experimental (implemented on the
  development line, not yet part of a release)
- record `scope observe` side effects, the `--expect` TOML example and the
  fact that cross-acquisition timing relationships are not reported
@Epslion404
Epslion404 force-pushed the split/agent-scope-observe-advise branch from 3d57a60 to e212170 Compare September 28, 2026 06:56
@Epslion404

Copy link
Copy Markdown
Collaborator Author

感谢复审。这个 PR 已按意见重做,并 rebase 到当前 master(44506c4)。方向是:MCP 保持纯只读,把"会改变仪器状态"的波形读取移到显式 CLI 命令,让副作用出现在调用点上。

下面按你 6 条 inline 意见逐条说明。因为做了 rebase + 重写,原有 inline comment 会被 GitHub 标成 outdated,以本评论为准。

1. [P1] scope.observe(fetch_waveform=true) 实际改变仪器状态却暴露为只读工具

scope.observe / scope.advise 现在完全不读波形:入参里没有 fetch_waveform,也不再接受 expectations;/tools 返回的每个工具都是 read_only=true、mutates_instrument=false、instrument_state_effects=[]。

读取波形改到显式命令 wavebench scope observe --fetch-waveform。该命令的 instrument_state_effects 逐条列出完整影响,不再只有一句 "waveform transfer source/mode/format may be changed":

  • a running acquisition may be stopped
  • waveform transfer source/mode/format/points may be changed
  • some drivers may enable the requested analog channel display before fetching
  • the previous acquisition run state is not restored

docs/reference/cli.md 与 docs/how-to/serve-mcp.md 同步了这些边界;两个页面和新增 MCP 工具都标为实验性(开发线已实现、尚未随正式版本发布)。

2. [P1] 每通道各自开 session,却计算相位/延迟/相关性/交点

确认了根因:ScopeService.fetch_waveform() 每次调用都会自己开一个 session(_scope_session()),CH1/CH2 可能来自不同 acquisition;MCP 的 ThreadingHTTPServer 还会让并发请求争用同一个全局 waveform source。

处理:

  • 显式读取波形时,结果里带 waveform_source.same_acquisition=false 和原因,并给出 warning。
  • 跨通道只保留与同步无关的量(频率比、Vpp 比、均值差、RMS 比);correlation 和 intersections 不再给出数值,直接返回 {"status": "skipped", "reason": "not_same_acquisition"},phase_degrees_at_left_frequency 为 null。
  • agent_hints 指向 wavebench scope capture --synchronized(驱动可证明的单次采集路径)作为需要同步结论时的做法。
  • MCP 不再读波形,ThreadingHTTPServer 的 waveform source 并发争用随之消失。

3. [P1] expectation 非法字段被静默忽略、可能返回 pass, checks=[]

新增 validate_expectation(),在服务端完整校验,不再依赖客户端遵守 JSON Schema:

  • 字段名白名单:未知字段(含拼错)直接报错,例如 frequncy_hz → ConfigError: unknown expectation field(s): frequncy_hz
  • 类型:非数字 / bool 一律拒绝
  • 有限性:nan、inf 拒绝
  • 取值范围:frequency_hz > 0、vpp_v > 0、duty_cycle ∈ [0,1]、duty_percent ∈ [0,100]、symmetry_percent ∈ [0,100],各 tolerance >= 0
  • 同义字段互斥:duty_cycle/duty_percent、mean_v/offset_v 不能同时出现

没有任何可执行检查时返回 status="skipped" 并带 message="expectation contains no checkable metric",不再返回 pass, checks=[]。

校验发生在 load_config() 之前、更早于任何仪器会话:测试用不存在的 config 路径验证过,ScopeService 根本没有被构造,零仪器写入。

4. [P2] 相位丢失符号(right=-left 应为 180°)

改用基波频域相位差(在估计频率上对每个通道做单点 DFT),不再用互相关峰 lag。两个原因:相关峰 lag 在少周期窗口下有约 5° 系统偏差;按 |corr| 选峰会吃掉反相的 180°。

约定已写进注释和文档:phase_degrees_at_left_frequency 表示 right 相对 left 的相位滞后,取值 [0, 360)。测试断言具体角度:0°/90°/180°/270°(±0.5°),并单独覆盖 right = -left → 180°。实测基波法在这些角度上都是精确值。

5. [P2] 三角波对称度对噪声敏感

按你给的三个方向都做了:滑动平均抑制噪声 → 滞回(默认 2% Vpp)确认极值反转 → 用期望频率约束周期长度 → 再用相邻两段原始数据拟合直线的交点,把滞回和滑窗造成的极值位置偏差还原回真实折点。

复现你的例子(10% 对称度、2 Vpp 三角波):加 1 mV 噪声原先估成 5.56%,现在测得 10.000;加 5 mV 噪声原先约 50%,现在 9.999。测试覆盖 10%/50%/90% × {1 mV 噪声+1 mV 量化台阶, 5 mV 噪声+轻微过冲},外加一个远窄于半周期的毛刺被期望频率过滤掉的用例。

6. [P2] _summary_frequency() 未过滤低置信度

低置信度的实测频率不再驱动时基建议:出现 low_cycle_count 一类质量告警时,优先回退到调用方给出的期望频率;没有期望频率就不生成时基建议,改为返回 timebase_advice_withheld 建议项并写明原因(只保留与频率无关的垂直档位建议)。

只读的 scope.advise 现在只基于状态快照 + expected_frequencies_hz;基于实测频率的建议只出现在 scope observe --fetch-waveform 的结果里,并且同样走这个门槛。

边界

这个 PR 仍然只动主仓库的通用控制面(CLI + MCP 只读边界 + 型号无关的波形分析):新增行里没有型号名、SCPI、通道数或范围表;型号相关的波形读取仍在插件 descriptor(scope_extensions.waveform_binary_profile);没有引入新的 capability 需求,因此不需要插件仓配套改动。

其它变更

  • rebase 到 44506c4。
  • 去掉了 TODO.md(个人路线图,不属于本 PR 范围),以及对已归档的 HTTP MCP 文档的修改(该页已归档为 docs/archive/http-mcp-guide-pre-migration.md,不应再作为当前事实源)。

验证(无硬件)

ruff check .                                   # All checks passed
audit_docs.py                                  # 0 errors / 11 warnings(与 origin/master 基线逐条一致)
scripts/generate_docs.py --check               # generated Reference is current
pytest -q tests/test_agent_observe.py tests/test_agent_advise.py \
  tests/test_waveform_expectations.py tests/test_waveform_relationships.py \
  tests/test_mcp_http.py tests/test_scope_observe_cli.py tests/test_cli.py
# 185 passed, 3 subtests passed

全量 pytest -q:2495 passed / 25 skipped;10 failed 中 8 个在纯净 origin/master worktree 上同样失败(Windows/scipy/subprocess 编码环境问题),剩下 2 个(test_release_artifacts)由 worktree 内遗留的 .tmp-pytest 触发 hatchling 打 sdist 失败,清理后通过。

本轮没有做任何硬件写入,也没有重新跑硬件闭环;不主张新的硬件验收结论。

@Epslion404 Epslion404 added the enhancement New feature or request label Sep 28, 2026
@Epslion404 Epslion404 self-assigned this Sep 28, 2026
@Scaxlibur

Copy link
Copy Markdown
Owner

当前版本已将 MCP 观察与显式波形采集分开,并跳过逐通道采集之间的相位、延迟等时序分析。这些边界处理合理。复审 e212170 后,仍有以下四项问题,建议修复后再合并。

  1. [P1] CLI 建议参数需要在仪器 I/O 前校验

    _run_scope_observe() 先调用 scope_waveform_report_payload(),之后才在建议计算中校验 target_cycles 和 target_vertical_divisions。

    已复现:scope observe --fetch-waveform --target-cycles 0 会先调用 fetch_waveform(),然后返回参数错误;--target-vertical-divisions 0 同样如此。仪器可能已经停止采集或改变传输设置,这与文档中「输入错误在打开仪器会话前拒绝」的承诺不符。

    请将这些参数的校验提前,并覆盖非法参数下不创建仪器服务、不执行采集的测试。

  2. [P2] 生成的垂直档位建议不符合现有 CLI 格式

    _command_text() 生成 --vertical-scale 1,但现有 scope focus 要求 CHANNEL=V_PER_DIV,例如 --vertical-scale 1=1。将实际生成的建议交给现有解析器后,会得到 ConfigError。

    请按现有命令格式生成参数,并增加建议命令的解析验证,确保输出可以直接使用。

  3. [P2] 部分通道无法验收时,期望检查汇总仍返回 pass

    当 CH1 检查通过、CH2 波形读取失败,且两个通道都配置了期望值时,CH2 不会进入 expectation_results。最终顶层状态虽然是 partial,但 expectations.status 仍为 pass,汇总中只保留 CH1。

    这会让只读取验收汇总的调用方误判。请对照全部待检查通道汇总结果,保留不可用通道,并避免将不完整验收标为通过。

  4. [P2] 同次采集相位计算受直流偏置影响

    _single_bin_phase() 直接对原始样本计算单点 DFT,在非整数周期窗口下,直流分量会污染相位结果。

    已用合成信号复现:两路 1 kHz 正弦实际相差 90°,窗口为 4.5 周期,右路增加 5 V 直流偏置后,返回约 54.74°,且没有警告。

    请处理直流项和非整数周期窗口的影响,并补充对应测试;可以考虑联合拟合常数、正弦和余弦项。此问题位于同次采集分析函数,当前 CLI 跳过跨采集相位计算的边界仍然有效。

本次验证基于隔离工作树,全量测试结果为 2526 passed、4 skipped、221 subtests passed,变更代码的 Ruff 检查通过。以上问题通过 fake service、实际命令解析器或合成信号复现;未连接真实仪器,不涉及新的硬件验收结论。

@Epslion404

Copy link
Copy Markdown
Collaborator Author

已在 416b28b 修复这次复审指出的四项问题,并更新 PR 说明。

  1. CLI 参数在仪器 I/O 前校验。 _run_scope_observe() 复用服务层的有限正数校验,先校验 target_cycles / target_vertical_divisions,再加载配置或构造仪器服务。回归覆盖 0、负数、NaN、±Inf,以及直接调用时的非法类型;断言配置未加载、服务未创建、没有采集。

  2. 建议命令符合实际 CLI。 垂直档位生成 --vertical-scale CHANNEL=V_PER_DIV,隐藏通道使用 --hide-others。测试将生成的建议交给真实 parser 和 focus request 构造函数,检查通道、档位、时基与隐藏参数。

  3. 验收汇总保留全部待检查通道。 波形、安全检查或期望值计算失败的通道保留为 unavailable;有可用结果但验收不完整时汇总为 partial,全部不可用时为 unavailable。已确认的 fail 优先,未提供期望值时仍为 skipped。测试覆盖 CH1 通过 / CH2 不可用,以及全部通道不可用。

  4. 相位计算联合拟合直流项和基波。 使用常数、cos、sin 的最小二乘拟合,处理非整数周期窗口内的直流泄漏。新增 4.5 周期、1 kHz、实际滞后 90°、右路叠加 5 V DC 的回归;结果在 90° ±0.5° 内,原有 0°、±90°、180° 测试通过。逐通道采集仍跳过时序关系分析。

验证:

  • 聚焦及相关集成测试:258 passed,3 subtests passed。
  • Ruff、定点严格文档检查(0 错误 / 0 警告)、生成 Reference 检查、严格 MkDocs 构建与离线 CLI smoke 均通过。
  • Windows Python 3.12、CI extras、PYTHONUTF8=1 的全量结果:2569 passed,4 skipped,221 subtests passed,1 failed。唯一失败为 test_windows_job_rejects_allocation:OpenBLAS 在 worker 内存限额下分配失败并退出,未产生测试期望的 Python MemoryError。该失败在未修改的 master(44506c4)上同样复现;设置 OPENBLAS_NUM_THREADS=1 后,上游该测试通过,本分支整个相关模块 17 passed,3 skipped。本次未为这个环境条件修改源代码。

没有连接真实仪器,也没有新增硬件验收结论。

@Scaxlibur
Scaxlibur changed the base branch from master to Dev October 5, 2026 06:59
@Scaxlibur
Scaxlibur merged commit 17c0de8 into Scaxlibur:Dev Oct 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants