Repository navigation
fix(agent): preserve provider state and signed tool-loop prefixes - #41095
hpiclaranet wants to merge 6 commits into
Conversation
api/core/rag/extractor/unstructured/unstructured_epub_extractor.py imports pypandoc directly, but it was only satisfied transitively through graphon's PyPI wheel (unstructured[epub] extra). Declare it directly, with the bundled pypandoc-binary so the Docker image does not need a system pandoc install.
…agent iterations FunctionCallAgentRunner rebuilt each assistant turn from the flattened text plus tool calls only, so provider round-trip state exposed by plugins via AssistantPromptMessage.opaque_body (e.g. signed/encrypted Claude thinking blocks) was lost between LLM iterations, causing provider 400 errors on the next tool-use round. Capture the complete snapshot from the LLM result (streaming chunks: last non-None wins; blocking result: message.opaque_body) and attach it to the assistant message stored in _current_thoughts so the next iteration's prompt carries it back to the provider. Also point the graphon dependency at the cherry-picked opaque_body change (rev-pinned, v0.7.0-based, zero drift) until the release containing langgenius/graphon#261 is published to PyPI; the pin then becomes graphon==0.7.1. Part of langgenius#41092; plugin-side counterpart: langgenius/dify-official-plugins#3715; core entity change: langgenius/graphon#261.
|
Heads-up to maintainers — this draft needs two things from the core side:
One heads-up for that eventual pin bump: graphon Tracking: #41092 · graphon #260/#261 · plugin side dify-official-plugins#3715. |
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-09-24 10:21:19.903552615 +0000
+++ /tmp/pyrefly_pr.txt 2026-09-24 10:21:10.267517606 +0000
@@ -2375,89 +2375,135 @@
ERROR `in` is not supported between `Literal['Question: What is Python?']` and `None` [not-iterable]
--> tests/unit_tests/core/agent/test_cot_completion_agent_runner.py:180:16
ERROR `Literal[0]` is not assignable to attribute `prompt_price` with type `Decimal` [bad-assignment]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:42:26
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:47:26
ERROR `Literal[0]` is not assignable to attribute `completion_price` with type `Decimal` [bad-assignment]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:43:30
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:48:30
ERROR `Literal[0]` is not assignable to attribute `total_price` with type `Decimal` [bad-assignment]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:44:25
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:49:25
ERROR Missing argument `session` in function `core.agent.base_agent_runner.BaseAgentRunner.__init__` [missing-argument]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:144:37
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:150:37
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:176:5
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:182:5
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:187:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:193:9
ERROR Argument `DummyChunk` is not assignable to parameter `llm_result_chunk` with type `LLMResultChunk` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner.check_tool_calls` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:199:40
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:205:40
ERROR Argument `DummyResult` is not assignable to parameter `llm_result` with type `LLMResult` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner.check_blocking_tool_calls` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:204:49
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:210:49
ERROR Argument `DummyChunk` is not assignable to parameter `llm_result_chunk` with type `LLMResultChunk` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner.extract_tool_calls` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:220:43
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:226:43
ERROR Argument `DummyChunk` is not assignable to parameter `llm_result_chunk` with type `LLMResultChunk` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner.extract_tool_calls` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:231:43
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:237:43
ERROR Argument `DummyResult` is not assignable to parameter `llm_result` with type `LLMResult` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner.extract_blocking_tool_calls` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:242:52
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:248:52
ERROR Argument `None` is not assignable to parameter `query` with type `str` in function `core.agent.fc_agent_runner.FunctionCallAgentRunner._organize_user_query` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:278:46
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:284:46
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:353:58
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:311:58
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:401:19
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:359:19
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:434:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:407:9
+ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:444:9
+ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:451:25
+ERROR Object of class `PromptMessage` has no attribute `opaque_body` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:461:16
+ERROR Object of class `FunctionType` has no attribute `assert_not_called` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:465:9
+ERROR `Literal['context_size']` is not assignable to dict key type `ModelPropertyKey` [bad-assignment]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:468:62
+ERROR `list[SimpleNamespace]` is not assignable to attribute `parameter_rules` with type `list[ParameterRule]` [bad-assignment]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:469:60
+ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:472:9
+ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:484:9
+ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:489:29
+ERROR Object of class `FunctionType` has no attribute `call_count` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:491:16
+ERROR Object of class `PromptMessage` has no attribute `opaque_body` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:492:16
+ERROR Object of class `FunctionType` has no attribute `call_args` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:494:16
+ERROR `Literal['context_size']` is not assignable to dict key type `ModelPropertyKey` [bad-assignment]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:509:62
+ERROR `list[SimpleNamespace]` is not assignable to attribute `parameter_rules` with type `list[ParameterRule]` [bad-assignment]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:510:60
+ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:514:9
+ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:522:13
+ERROR Object of class `AIModel` has no attribute `get_num_tokens` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:536:9
+ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:561:9
+ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:562:25
+ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:581:9
+ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:583:29
+ERROR Object of class `FunctionType` has no attribute `call_count` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:585:16
+ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:592:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:436:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:594:35
ERROR Object of class `FunctionType` has no attribute `call_args` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:438:33
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:596:33
ERROR Object of class `FunctionType` has no attribute `call_args` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:439:33
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:597:33
ERROR Object of class `FunctionType` has no attribute `call_args` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:440:16
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:598:16
ERROR Object of class `FunctionType` has no attribute `assert_called` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:445:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:603:9
ERROR Object of class `FunctionType` has no attribute `call_args_list` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:447:23
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:605:23
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:453:19
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:611:19
ERROR Argument `list[TextPromptMessageContent]` is not assignable to parameter `content` with type `str | None` in function `DummyMessage.__init__` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:470:57
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:628:57
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:476:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:634:9
ERROR Argument `list[TextPromptMessageContent]` is not assignable to parameter `content` with type `str | None` in function `DummyMessage.__init__` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:492:57
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:650:57
ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:500:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:658:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:502:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:660:35
ERROR Argument `list[TextPromptMessageContent]` is not assignable to parameter `content` with type `str | None` in function `DummyMessage.__init__` [bad-argument-type]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:508:46
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:666:46
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:511:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:669:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:513:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:671:35
ERROR Object of class `FunctionType` has no attribute `call_args` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:515:16
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:673:16
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:531:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:689:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:542:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:700:35
ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:558:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:716:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:560:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:718:35
ERROR Object of class `FunctionType` has no attribute `side_effect` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:575:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:733:9
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:580:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:738:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:589:35
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:747:35
ERROR Object of class `FunctionType` has no attribute `call_args_list` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:595:25
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:753:25
ERROR Object of class `NoneType` has no attribute `max_iteration` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:599:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:757:9
ERROR Object of class `FunctionType` has no attribute `return_value` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:611:9
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:769:9
ERROR Object of class `FunctionCallAgentRunner` has no attribute `session` [missing-attribute]
- --> tests/unit_tests/core/agent/test_fc_agent_runner.py:614:29
+ --> tests/unit_tests/core/agent/test_fc_agent_runner.py:772:29
ERROR Missing argument `description` in function `core.agent.plugin_entities.AgentStrategyEntity.__init__` [missing-argument]
--> tests/unit_tests/core/agent/test_plugin_entities.py:281:32
ERROR Invalid key for TypedDict `AppParametersDict`, got `Unknown` [bad-typed-dict-key]
|
Pyrefly Type Coverage
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #41095 +/- ##
==========================================
+ Coverage 87.96% 88.06% +0.10%
==========================================
Files 5267 5128 -139
Lines 324459 321848 -2611
Branches 65729 64690 -1039
==========================================
- Hits 285398 283428 -1970
+ Misses 33695 33048 -647
- Partials 5366 5372 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thank you for your contribution and the effort you’ve put into this PR. It currently has merge conflicts with |
Summary
Part of #41092: preserve provider replay state during one Function Calling Agent execution.
The runner previously reconstructed replies from visible text and tool calls, then changed the history window, image inputs, or tool schemas before the next request.
It now carries the last non-None
opaque_bodysnapshot in both streaming and blocking modes, chooses the initial history once, freezes tool schemas, and appends each assistant/tool turn without changing the signed prefix.The iteration limit no longer clears tools; an exhausted context budget stops before invocation without reducing output tokens below the model's configured thinking budget.
Configured or schema-default output budgets below 16 tokens remain valid; the fallback applies only when no budget is available.
The host regression exercises the real
PluginModelClient, daemon-response Pydantic decoding,RequestInvokeLLM, runtime normalization, and backwards response reconstruction for Anthropic/OpenAI payloads in both modes.HTTP transport is mocked at the daemon boundary; this is not a deployed end-to-end API test.
The change retains the original PR author's commits and extends the same PR.
Dependencies and release gate
This remains a draft.
Dify's Graphon 0.7 runtime API is incompatible with Graphon main/0.8, so the review build pins the tested 0.7 backport in
api/pyproject.tomlandapi/uv.lock.The mainline fix is Graphon #261.
Replace the temporary source override with an actual containing release before merging for production.
The Agent strategy plugin additionally requires SDK #390 and daemon #828.
Provider counterparts are Anthropic #3919 and OpenAI #3918.
The final daemon image and minimum supported Dify version must refer to actual published fixes; no future release version is assumed here.
Cross-session history reconstruction and third-party provider adapters are outside this in-memory tool-loop fix.
Validation
uv: 116 targeted tests passed across the FC runner and host transport/model boundaries.make type-check-core(Pyrefly plus Mypy across 1868 source files), lock validation, andgit diff --checkpassed.a637fc4e72997948c615529565cb3a9d53a3be6b, Main CI and Docker builds passed, including API unit/controller/integration/coverage, Python Style, VDB, browser E2E, and API/Web/local-sandbox images.Screenshots
Not applicable: backend behavior only.
Checklist