Skip to content

feat(dsl,nodes): support agent_strategy plugin invocation#135

Closed
BenjaminX wants to merge 5 commits into
langgenius:mainfrom
BenjaminX:feat/dsl-slim-invoke-agent-strategy
Closed

feat(dsl,nodes): support agent_strategy plugin invocation#135
BenjaminX wants to merge 5 commits into
langgenius:mainfrom
BenjaminX:feat/dsl-slim-invoke-agent-strategy

Conversation

@BenjaminX
Copy link
Copy Markdown

@BenjaminX BenjaminX commented May 12, 2026

Summary

Implements the two integration surfaces flagged in RFC #102 so external integrators can drive Dify chatflows containing agent nodes through graphon.dsl.loads.

  • PR-Adsl/slim learns the invoke_agent_strategy slim action via a new SlimAgentStrategyClient. Reuses ToolRuntimeMessage as the wire DTO because the Dify Plugin SDK defines AgentInvokeMessage(InvokeMessage): pass — wire format is identical, the tool-side decoder is shared.
  • PR-B — Adds AgentNode, AgentNodeRuntimeProtocol, and SlimAgentNodeRuntime; routes BuiltinNodeTypes.AGENT through SlimDslNodeFactory. Mirrors the structure of ToolNode / SlimToolNodeRuntime.
  • Example — Adds examples/chatflow_dsl_runner, a canonical downstream-integration pattern using graphon.dsl.loads against a real Dify Studio chatflow export. Two fixtures bundled: one runs out of the box, one documents a known limitation (see below).
  • Chore — Bumps the pinned openai plugin in examples/slim_llm from 0.3.8 to 0.4.0 to align with the new chatflow example.

What's new

  • src/graphon/dsl/slim/agent.pySlimAgentStrategyClient, SlimAgentStrategyError, AgentRuntimeMessage alias, SlimActionInvoker DI seam.
  • src/graphon/dsl/agent_runtime.pySlimAgentNodeRuntime (AgentNodeRuntimeProtocol), mirrors SlimToolNodeRuntime.
  • src/graphon/dsl/_provider.py — extracted shared canonical_vendor helper (deduped from node_factory.py).
  • src/graphon/nodes/agent/{agent_node.py, entities.py, exc.py}AgentNode, AgentNodeData (typed-wrapper agent_parameters), AgentParameterValue, AgentNodeError.
  • src/graphon/nodes/runtime.py — new AgentNodeRuntimeProtocol.
  • src/graphon/dsl/{node_factory.py, importer.py} — route BuiltinNodeTypes.AGENT; add to _SUPPORTED_DEFAULT_FACTORY_NODES.
  • examples/chatflow_dsl_runner/main.py (CLI with inspect() / loads() / event streaming), chatflow_dsl_simple.yml (runnable), chatflow_dsl_agent.yml (reference fixture, see limitations), README.md, credentials.example.json.

Out of scope (deliberate)

tenant_id / user_id are intentionally not propagated through the slim runtime. When an agent strategy plugin does self.session.model.llm.invoke(...), the daemon currently performs backwards-invocation against the Dify Server inner API at :5001, which requires a tenant context. Teaching graphon to forward tenant_id / user_id would leak Dify's business-side identity model into a tenant-agnostic execution engine.

The correct long-term fix is the daemon redesign outlined in the RFC #102 "long-term direction" — let plugin code perform nested invocations without depending on Dify Server's inner API. Until that lands, chatflow_dsl_agent.yml ships as a structural reference (this is what a real Dify Studio export of a chatflow with an agent looks like) but cannot run end-to-end from a graphon-only runner. chatflow_dsl_simple.yml is the runnable out-of-box fixture.

This trade-off is documented in examples/chatflow_dsl_runner/README.md#architecture--limitations.

Test plan

  • pytest tests/ — 348 passed (28 new tests across test_slim_agent.py, test_agent_node.py, test_slim_agent_node_runtime.py, test_node_factory_agent.py).
  • make tc — ruff format + check + ty all green.
  • Local end-to-end: python3 examples/chatflow_dsl_runner/main.py chatflow_dsl_simple.yml "Say hi in 5 words." streams a real LLM response from langgenius/openai:0.4.0.
  • CI run on this PR. Unit tests use a fake action_invoker injection seam — no e2e slim-binary tests on CI by design.

Open questions for reviewers

  1. PR scope — RFC RFC: Slim runtime support for agent_strategy and tool plugin invocation #102 originally split the work into PR-A (slim) and PR-B (node). They're bundled here because PR-B is hard to verify without PR-A and the example is the smallest end-to-end witness we have. Happy to split into stacked PRs if preferred — the three commits (984a416 slim+node, 23855bd example, f439196 slim_llm bump) are natural boundaries.
  2. AgentRuntimeMessage = ToolRuntimeMessage alias — keeps a semantic name so future agent-only message variants can diverge, while structurally reusing the tool DTO and decoder. Alternative is to inline ToolRuntimeMessage at the agent call sites. The reasoning is in a comment at src/graphon/dsl/slim/agent.py:15.
  3. AgentNode.version() → "1" — matches the start / llm / tool convention. Distinct from tool_node_version: "2" in AgentNodeData, which is the Dify plugin protocol version forwarded verbatim to the strategy plugin.
  4. chatflow_dsl_agent.yml placement — kept under examples/ with a README disclaimer rather than tests/fixtures/ because it doubles as documentation for the architecture limitation. Open to relocation if examples/ is meant to be strictly run-out-of-box.
  5. slim_llm openai bump (f439196) — kept as a separate commit; trivially droppable from this PR if maintainers prefer it as a follow-up chore.

References

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label May 12, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

All contributors on this pull request have signed the CLA.
Posted by the CLA Assistant Lite bot.

@dosubot dosubot Bot added the enhancement New feature or request label May 12, 2026
@BenjaminX
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@WH-2099 WH-2099 self-requested a review May 23, 2026 10:01
@WH-2099 WH-2099 self-assigned this May 23, 2026
BenjaminX and others added 3 commits May 23, 2026 18:22
Add a first-class workflow node for Dify ``agent_strategy`` plugins
plus the slim-backed runtime adapter that drives them. This unlocks
Dify Studio chatflows that contain ``type: agent`` nodes —
``graphon.dsl.loads`` now accepts and executes them end-to-end.

Three layers, all shipped together so the change is atomic:

1. **Slim layer** (``src/graphon/dsl/slim/agent.py``):
   - ``SlimAgentStrategyClient`` — thin invoker scoped by
     ``(plugin_id, agent_strategy_provider, agent_strategy)``. Mirrors
     ``SlimLLM`` and ``SlimToolNodeRuntime``: ``action_invoker``
     injection for tests, ``SlimClient`` for subprocess. Errors from
     ``action_invoker`` propagate unchanged (caller-owned); errors
     from the SlimClient path are wrapped into ``SlimAgentStrategyError``
     (library-owned boundary).
   - ``AgentRuntimeMessage`` — PEP 695 alias of ``ToolRuntimeMessage``
     because the Dify Plugin SDK defines
     ``AgentInvokeMessage(InvokeMessage): pass`` without adding fields.
     The alias keeps call sites semantically clear and reserves room for
     divergence later.
   - ``SlimActionInvoker`` — DI seam.
   - ``_decode_agent_message`` — dedicated decoder for the agent message
     subset (text / link / json / log / variable / retriever_resources).
     Independent from the tool runtime's decoder so the agent path is
     not coupled to tool-specific variants (file / blob / image).

2. **Node layer** (``src/graphon/nodes/agent/``):
   - ``AgentNodeData`` mirrors the Dify Studio v1.7+ export shape — the
     ``agent_strategy_provider_name`` / ``agent_strategy_name`` /
     ``plugin_unique_identifier`` triple plus the ``agent_parameters``
     typed-wrapper bag.
   - ``AgentParameterValue`` is the ``{type, value}`` wrapper Studio
     emits for every parameter (constant / variable / mixed),
     type-checked at validation time.
   - ``AgentNode._run`` is a streaming generator that resolves
     typed-wrapper parameters against the variable pool, forwards them
     through the injected runtime, translates each runtime message into
     the matching graph event — text/link → ``StreamChunkEvent``
     selected by ``[node_id, "text"]``, log → ``AgentLogEvent``, json
     / variable → accumulated outputs — and emits one terminal
     ``StreamCompletedEvent``.
   - ``AgentNodeError`` is the node-layer boundary error type.
   - ``AgentNodeRuntimeProtocol`` (in ``nodes/runtime.py``) decouples
     the node from any specific runtime.

3. **DSL wiring** (``src/graphon/dsl/``):
   - ``SlimAgentNodeRuntime`` (``agent_runtime.py``) implements
     ``AgentNodeRuntimeProtocol`` by assembling a fresh
     ``SlimAgentStrategyClient`` per invocation. Stateless adapter that
     mirrors how ``SlimDslNodeFactory`` constructs LLM and tool slim
     clients per node. ``_provider_slug`` extracts the trailing segment
     of ``agent_strategy_provider_name`` (e.g. "langgenius/agent/agent"
     → "agent") to match the slim payload contract.
   - ``SlimDslNodeFactory.create_node`` routes ``BuiltinNodeTypes.AGENT``
     to ``AgentNode`` with a slim-backed runtime built from the
     factory's existing ``slim_client_config``.
   - ``_SUPPORTED_DEFAULT_FACTORY_NODES`` in the importer now includes
     ``BuiltinNodeTypes.AGENT``.

Tests: 28 new cases across three files cover the slim adapter
(``tests/dsl/test_slim_agent.py``: 12 cases — basic / mixed-type
decoding, payload contract, meta propagation, lazy partial consumption,
both error-path contracts, alias identity), the node behavior
(``tests/nodes/agent/test_agent_node.py``: 10 cases — text streaming,
log → ``AgentLogEvent``, json / variable accumulation, all three
typed-wrapper resolution modes, runtime-error wrap), the slim runtime
adapter (``tests/dsl/test_slim_agent_node_runtime.py``: 4 cases — slug
extraction, payload forwarding, decoded message contract, per-node
client identity), and factory routing
(``tests/dsl/test_node_factory_agent.py``: 2 cases).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eaming

Standard downstream-integration pattern for executing Dify Studio
exported chatflow / workflow DSLs through ``graphon.dsl.loads``.

What ``main.py`` demonstrates end-to-end:

- Static DSL inspection via ``graphon.dsl.inspect`` before execution,
  printing document kind, plugin dependencies, and load status. Aborts
  early with a readable diagnostic for non-loadable plans (unsupported
  node types, config-only ``app.mode`` like ``chat`` / ``completion`` /
  ``agent-chat``, unresolvable plugin dependencies).
- Execution via ``graphon.dsl.loads`` — the canonical 4-line integration
  surface; everything else in main.py is decoration.
- Live event consumption: ``NodeRunStreamChunkEvent`` writes chunks to
  stdout as they arrive, ``NodeRunStartedEvent`` shows per-node lifecycle,
  ``NodeRunAgentLogEvent`` exposes agent strategy inner steps,
  ``GraphRunSucceededEvent`` collects the final ``outputs["answer"]``.
- Credential isolation: keys live only in ``credentials.json``
  (gitignored via the repo-wide ``examples/*/credentials.json`` rule).
  No ambient environment variables are consulted for secrets.
- Slim binary auto-discovery: ``SLIM_BINARY_PATH`` env var wins, then a
  local ``./slim`` file in the example directory, otherwise rely on
  ``PATH``.

Includes ``credentials.example.json`` matching the upstream
``examples/slim_llm`` convention so credential plumbing is consistent
across demos. Multi-vendor template lists both ``tongyi`` (DashScope)
and ``openai`` (with custom ``openai_api_base`` + ``api_protocol``
fields exposed by the ``langgenius/openai`` plugin).

README is a standard usage reference for downstream integrators:
observed event types, the 4-line integration core, supported /
unsupported node types and modes, troubleshooting table.

Also extends ``.gitignore`` to cover ``.scratch/`` (local design /
review notes) and ``examples/*/credentials.json`` (per-example
secrets, never committed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous pin ``langgenius/openai:0.3.8@<digest>`` was rejected by
the live marketplace with ``plugin package not found`` (500). The
upstream ``examples/slim_llm`` demo therefore did not run end-to-end on
a fresh ``.slim/plugins`` cache. Updating both the demo's
``graph.yml`` and ``settings.py`` to the current marketplace head
``0.4.0`` (digest ``beafb5a726eda839a1839f61a0456ae7e068c98624c53f59b07be9a71fbf72da``)
restores marketplace download. No behavioral change in the demo itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@WH-2099 WH-2099 force-pushed the feat/dsl-slim-invoke-agent-strategy branch from f4a924f to 6f028a5 Compare May 23, 2026 10:22
@WH-2099
Copy link
Copy Markdown
Member

WH-2099 commented May 23, 2026

Hi @BenjaminX, thank you very much for the thoughtful work here and for the detailed RFC/prototype. I am sorry for the delayed conclusion.

After aligning our DSL import roadmap in #154, we have decided not to support the agent node in Graphon in the short term. The main reason is that the runtime boundary for Dify agent execution is still changing: agent strategies may require nested model/tool invocation and control-plane context that we do not want to model in Graphon right now.

Because of that, we are going to close this PR and the related RFC issue for now. This is not a reflection on the quality of your contribution; the implementation and tests gave us useful reference points, and we appreciate the time you put into it.

To share a little more context: internally, we are about to start a larger refactor of Dify's agent node/runtime design. We expect that work to change the right integration surface for agent execution, so landing AgentNode support now would likely create churn for both maintainers and downstream users.

Apologies again for the back-and-forth, and thank you for pushing this area forward. Once the agent refactor direction is clearer, we would be happy to revisit the support path with a more stable boundary.

@WH-2099 WH-2099 closed this May 23, 2026
@BenjaminX
Copy link
Copy Markdown
Author

Hi @BenjaminX, thank you very much for the thoughtful work here and for the detailed RFC/prototype. I am sorry for the delayed conclusion.

After aligning our DSL import roadmap in #154, we have decided not to support the agent node in Graphon in the short term. The main reason is that the runtime boundary for Dify agent execution is still changing: agent strategies may require nested model/tool invocation and control-plane context that we do not want to model in Graphon right now.

Because of that, we are going to close this PR and the related RFC issue for now. This is not a reflection on the quality of your contribution; the implementation and tests gave us useful reference points, and we appreciate the time you put into it.

To share a little more context: internally, we are about to start a larger refactor of Dify's agent node/runtime design. We expect that work to change the right integration surface for agent execution, so landing AgentNode support now would likely create churn for both maintainers and downstream users.

Apologies again for the back-and-forth, and thank you for pushing this area forward. Once the agent refactor direction is clearer, we would be happy to revisit the support path with a more stable boundary.

I also saw the Dify main repository, which refactored the Agent runtime. Are there any refactoring plans and roadmaps for this new Agent runtime?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants