Skip to content

feat: agentic context tools + three-layer e2e#1

Merged
Weaxs merged 6 commits into
mainfrom
feat/agent-context-tools-and-e2e
Jul 2, 2026
Merged

feat: agentic context tools + three-layer e2e#1
Weaxs merged 6 commits into
mainfrom
feat/agent-context-tools-and-e2e

Conversation

@Weaxs

@Weaxs Weaxs commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • 8 new agent-context tools: diagnose_data_sources, get_market_capabilities, render_stock_report/render_market_report, build_watchlist_context, analyze_position_context, check_alert_rules, parse_stock_list — focused on helping the host agent make decisions, not fetching more raw data
  • Host parity: all three hosts (Pi / Hermes / OpenClaw) now register the same 39 tools; integration tests assert three-way name parity
  • Three-layer e2e: host↔python subprocess, real provider network (akshare/yfinance), and LLM tool_use (DeepSeek v4 flash) — CI wires each layer to its own job

What changed

New tools (agent context, not data)

Tool Purpose
diagnose_data_sources Probe which providers work per market — self-explains "why can't I fetch data"
get_market_capabilities Return supported/unsupported tools per market — prevents fabricated HK chip data
render_stock_report / render_market_report Structured JSON → markdown via existing j2 templates
build_watchlist_context Per-symbol score/trend/anomalies + suggested next_tools
analyze_position_context Stateless pnl/risk/advice from cost + quantity + stops
check_alert_rules Stateless rule evaluation (no scheduler, no state)
parse_stock_list Extract symbols from freeform text / CSV / markdown
run_backtest (extended) diagnostics field: trade quality, failure reason, regime fit, parameter suggestions

Host integration (39 tools, 3 hosts)

  • pi/index.ts — 8 pi.registerTool calls
  • hermes/tools.py + schemas.py + __init__.py + plugin.yaml
  • openclaw/index.ts + openclaw.plugin.json
  • typings/openclaw/index.d.ts — shim Type.Array / Type.Boolean

Diagnostics correctness

  • Finnhub probes the finnhub package (not requests)
  • Longbridge probes longport.openapi
  • finnhub-python added as opt-in dep

Distribution packaging

  • pyproject.toml: jinja2>=3.1.0 as hard dep; force-include templates/ and schemas/
  • package.json: templates/ added to files
  • tools/requirements.txt: jinja2, finnhub-python

Three-layer e2e

Layer 1 — host↔python subprocess (default CI, no network)

  • tests/e2e/test_host_to_python.py — 4 tools × Hermes handler + subprocess CLI
  • tests/e2e/test_pi_e2e.ts — real Python subprocess through Pi's executor
  • tests/e2e/test_openclaw_e2e.ts — same, through OpenClaw's default executor

Layer 2 — real provider network (@pytest.mark.integration_network)

  • tests/e2e/test_provider_network.py — akshare + yfinance + exchange-calendars
  • Fails loud on upstream outage — that's intentional

Layer 3 — LLM tool_use (@pytest.mark.integration_llm, needs DEEPSEEK_API_KEY)

  • tests/e2e/test_hermes_llm.py, test_pi_llm.ts
  • Uses DeepSeek's OpenAI-compatible endpoint; model via DEEPSEEK_MODEL env (default deepseek-v4-flash)
  • Skipped when secret is missing / on fork PRs

CI job graph

  • test: default unit tests (-m "not integration_network and not integration_llm") — 423 passed
  • integration: registration checks (Hermes/Pi/OpenClaw) + Layer 1 e2e
  • e2e-network: Layer 2, always on push/PR
  • e2e-llm: Layer 3, gated on secrets.DEEPSEEK_API_KEY
  • build: still gated on [lint, test, integration]

Test plan

  • pytest (default): 423 passed, 6 deselected (network/LLM), ~2s
  • pytest -m integration_network: 4 passed against real akshare/yfinance
  • npx tsx tests/e2e/test_pi_e2e.ts: 4 tools OK
  • npx tsx tests/e2e/test_openclaw_e2e.ts: 4 tools OK
  • pytest -m integration_llm with DEEPSEEK_API_KEY: 2 passed (verified locally, 2min)
  • npx tsx tests/e2e/test_pi_llm.ts with DEEPSEEK_API_KEY: 2 passed (verified locally)
  • npx tsc --noEmit: clean
  • ruff check + ruff format --check: clean

Notes

  • integration_llm job on CI will fail hard if the secret is set but the key is wrong — this is the intended tradeoff for "no fake green"
  • Three-way parity check runs on every PR (hermes: 39, pi: 39, openclaw: 39)

Adds 8 new tools focused on agent decision-making context (not raw data
fetching), completes Hermes and OpenClaw integration for the new tools,
strengthens data-source diagnostics, and introduces three layers of
end-to-end tests plus CI wiring for them.

## New tools (agent context, not data)
- diagnose_data_sources: probe which providers work per market
- get_market_capabilities: return supported/unsupported tools per market
- render_stock_report / render_market_report: j2 templates → markdown
- build_watchlist_context: score/trend/anomalies + next_tools per symbol
- analyze_position_context: stateless pnl/risk given cost + quantity
- check_alert_rules: stateless rule evaluation
- parse_stock_list: extract symbols from freeform text
- backtest run: extended diagnostics field (trade_count_quality,
  main_failure_reason, regime_fit, suggested_parameter_changes)

## Host integration
- pi/index.ts, hermes/{tools,schemas,__init__,plugin.yaml},
  openclaw/{index.ts,openclaw.plugin.json} all register the same 39 tools
- All hosts round-trip identically; integration tests assert three-way parity
- typings/openclaw: shim Array/Boolean for Type surface

## Diagnostics correctness (P2)
- Finnhub now probes the finnhub package (not requests)
- Longbridge probes longport.openapi
- finnhub-python added to tools/requirements.txt

## Distribution (P1c)
- pyproject: jinja2 as hard dep; force-include templates/ and schemas/
- package.json.files: include templates/
- tools/requirements.txt: add jinja2, finnhub-python

## Three-layer e2e
- Layer 1 (host↔python subprocess): 4 non-network tools × 3 hosts
  - tests/e2e/test_host_to_python.py (pytest, covers Hermes handlers)
  - tests/e2e/test_pi_e2e.ts, test_openclaw_e2e.ts (real subprocess)
- Layer 2 (real provider network): akshare + yfinance + calendar
  - tests/e2e/test_provider_network.py, @pytest.mark.integration_network
- Layer 3 (LLM tool_use): DeepSeek → tool_calls → handler → answer
  - tests/e2e/test_hermes_llm.py, test_pi_llm.ts
  - @pytest.mark.integration_llm; opt-in via DEEPSEEK_API_KEY
  - Model overridable via DEEPSEEK_MODEL (default deepseek-v4-flash)

## Pytest markers + defaults
- pyproject: register integration_network and integration_llm markers
- addopts excludes both by default — unit tests never touch network/LLM

## CI (.github/workflows/ci.yml)
- integration job runs Layer 1 e2e (3 hosts) after registration checks
- e2e-network job: Layer 2, always on push/PR
- e2e-llm job: Layer 3, skipped on forks, needs DEEPSEEK_API_KEY secret

## Unit tests
- 8 new test files for the 8 new tools (~75 tests)
- Layer 1 e2e: 8 more (pytest) + 8 (tsx)
- Layer 2 e2e: 4 (marked)
- Layer 3 e2e: 2 pytest + 2 tsx (marked)
- Total: 423 passed, 6 deselected by default markers (1.8s)

## Notes
- integration_network hits real providers and may fail loud on upstream
  outages — that's intentional; skip is worse than red
- integration_llm consumes API tokens; only runs where secret is set

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several new financial analysis tools and templates across the pi, hermes, and openclaw platforms, alongside comprehensive end-to-end integration tests. The review feedback highlights critical stability and robustness issues, including potential runtime crashes from unhandled None values in backtest diagnostics, missing default parameter objects in TypeScript destructuring, and case-sensitivity bugs in argparse CLI choices. Additionally, the reviewer identifies performance concerns with dynamic module imports, a dangerous silent failure mode in alert rule checks, and type-safety issues in Jinja2 macros and equity curve parsing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tools/backtest.py Outdated
Comment thread pi/index.ts
Comment thread pi/index.ts
Comment thread tools/alert_rules.py
Comment thread tools/backtest.py
Comment thread tools/capabilities.py Outdated
Comment thread tools/diagnostics.py Outdated
Comment thread tools/diagnostics.py
Comment thread tools/import_parser.py
Comment thread templates/_macros.j2
Weaxs added 5 commits July 2, 2026 01:33
Previous prompt leaked the answer (contained "600519" and "AAPL"), so DeepSeek
often skipped the tool and answered directly. tool_calls=[] on CI → red.

New tests give the LLM only natural-language company names, never the code.
The only way to satisfy the ask is to call parse_stock_list:

- test_parse_stock_list_english: "Nvidia and Apple" → NVDA + AAPL
  Fully offline (US ticker regex only), stable across environments.

- test_parse_stock_list_chinese: "贵州茅台和宁德时代" → 600519 + 300750
  Requires akshare for name resolution. Hard-asserts the LLM called the tool
  (main e2e contract), soft-asserts specific codes only when akshare actually
  resolved them — decouples from upstream data outages.

Same rewrite applied to tests/e2e/test_pi_llm.ts.
## Applied from Gemini code review
- backtest.py: guard against `None` for exit.stop_loss / exit.take_profit
  before `float()` — real crash path if strategy YAML has explicit nulls
- alert_rules.py: surface data-fetch failures in `meta.warnings` instead
  of silently returning "not triggered". An alerting system that fails
  silently is worse than one that fails loudly
- capabilities.py + diagnostics.py: `argparse` --market now case-insensitive
  via `type=str.upper` — matches the get_capabilities() runtime behavior
- diagnostics.py: probe with `importlib.util.find_spec` instead of
  `importlib.import_module`. Avoids executing top-level code of heavy
  packages (akshare, yfinance) during a "cheap capability probe"
- _macros.j2: `change_pct` also skips non-number values, not just None

## Baostock stdout pollution (real bug surfaced by e2e-network)
- stock_data.py::_kline_baostock: baostock.login()/logout() print status
  lines to stdout, corrupting the tool's JSON output. Silence via
  `contextlib.redirect_stdout(io.StringIO())`

## Rejected from Gemini review
- pi/index.ts execute() `= {}` default: pi-agent contract already
  guarantees args is Record<string, unknown>, never undefined
- backtest.py isinstance(list, tuple): equity_curve is built by
  simulate() as list-of-lists; adding tuple support is speculative
- import_parser.py noise dict expansion: not motivated by any observed
  false positive; YAGNI

## Test updates
- test_diagnostics.py: mock `importlib.util.find_spec` to match new probe
- All 423 unit tests still pass
deepseek-v4-flash intermittently returns content=null (empty string in our
loop) after tool_use — reproduced in CI as `final=""` while tool_calls
was correctly populated with parse_stock_list. That's model-side variability,
not a broken plugin contract. Previous assertions on `final` were flaky.

Rewrite the agent loop to also capture each tool's parsed JSON result, and
rewrite all three test assertions to target the tool_use contract:

  1. LLM chose the right tool
  2. LLM sent args that carried the natural-language input
  3. Our tool successfully computed the expected result

Concretely:
- test_parse_stock_list_english: assert NVDA + AAPL appear in the tool's
  extracted items across all parse_stock_list calls (not in the LLM's final)
- test_parse_stock_list_chinese: assert LLM called the tool; soft-check codes
  only when akshare actually resolves them (upstream availability decoupled)
- test_capabilities_flow: assert the LLM queried HK and the tool marked
  get_capital_flow as unsupported (not that the LLM's final said "no")

Same rewrite for tests/e2e/test_pi_llm.ts.

What we lose: verification that the LLM writes a coherent final answer.
What we keep: the actual e2e contract we care about — host↔python↔LLM
tool_use plumbing works end to end.
Runs ruff --fix + ruff format on every PR. autofix-ci's GitHub App pushes
the fixup commit back to the PR branch (bypassing main's branch protection,
which only guards main itself).

Scope:
- pull_request only. push isn't wired because main is protected (bot can't
  direct-push) and feature branches without a PR don't need autofix
- Currently runs ruff on tools/, skills/, tests/, hermes/. TypeScript
  formatter (prettier) not enabled — no prettier config in the repo yet
Switch from hand-rolled `git log` release notes to GitHub's built-in
`--generate-notes`. Categorization is driven by PR labels via
`.github/release.yml`.

What changes in v0.1.4+ release pages:
- Commits grouped by section (New Features / Bug Fixes / Tests & E2E /
  CI / Docs / Chore / Other) based on PR labels
- Each entry links to its PR and @mentions the author
- Footer includes a Full Changelog compare link (auto)
- Install commands (npm / pip / OpenClaw) appended below

Historical commits landing outside PRs fall into "Other Changes" —
acceptable, and no longer a concern since main is now branch-protected
and everything must go through a PR.
@Weaxs Weaxs merged commit 3f89707 into main Jul 2, 2026
8 checks passed
@Weaxs Weaxs added the type: feat New feature or capability label Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat New feature or capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant