-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(llm): virtualize provider interface behind a normalized Adapter #1683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
afc9f74
refactor(llm): extract stream content parsing into a named seam
dsapandora 3bb5136
feat(llm): add normalized provider Adapter interface
dsapandora 4cfc8e1
refactor(llm): move stream content parser to the adapter module
dsapandora 502ea6e
feat(llm): add LangChainAdapter
dsapandora 695b53d
feat(llm): add AnthropicAdapter
dsapandora 44f2e67
feat(llm): add OpenAIAdapter
dsapandora 1956036
feat(llm): add drive_adapter Event→callback shim
dsapandora c2c67e4
feat(llm): drive the LangChain streaming path through the adapter
dsapandora 1de1e3d
fix(llm): flatten non-streaming _chat content to a string
dsapandora d779495
feat(llm): route the native Anthropic path through an adapter
dsapandora ce853f9
feat(llm): route the OpenAI Responses path through an adapter
dsapandora 4f943ef
refactor(llm): unify the non-streaming path through the adapter
dsapandora e1ae7d9
feat(llm_anthropic): per-node extended-thinking control with context …
dsapandora 1fd66cd
test(agent_crewai): drive NativeAnthropicAdapter for native stop-thre…
dsapandora 8c488f9
fix(sync-models): make the extendedThinking toggle capabilities-aware
dsapandora ae6df02
refactor(llm): address CodeRabbit — dead adapters, store=False, Respo…
dsapandora 3b61c38
fix(sync-models): scope the extendedThinking toggle to nodes that def…
dsapandora 5daf0cc
docs(llm_anthropic): soften the extendedThinking field description
dsapandora 3f6dc36
fix(llm): keep the non-streaming path on invoke via adapter.collect()
dsapandora 9c1da26
fix(llm): address review — parse_bool, opus-5 toggle, custom, Respons…
dsapandora 8fc1992
fix(llm): close Responses stream + honor Haiku 4.5 extended thinking
dsapandora 96468a4
refactor(llm): reuse LangChainAdapter.collect() in the Responses fall…
dsapandora 989a6e7
refactor(llm): expose collect() reasoning so the fallback keeps its lane
dsapandora fe43637
refactor(llm): read the block vocabulary from flatten_content_blocks
dsapandora 9c318a0
docs(llm_anthropic): describe the extended-thinking toggle as opt-in
dsapandora File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
nodes/test/llm_anthropic/test_extended_thinking_init.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # ============================================================================= | ||
| # MIT License | ||
| # Copyright (c) 2026 Aparavi Software AG | ||
| # ============================================================================= | ||
|
|
||
| """Node-init tests for the llm_anthropic extended-thinking toggle. | ||
|
|
||
| `test_thinking_mode_injected_into_payload_per_call` sets `_thinking_mode_kwargs` | ||
| by hand, so it starts after the interesting part. These tests drive the real | ||
| chain instead: | ||
|
|
||
| config['extendedThinking'] -> parse_bool -> _is_reasoning gate | ||
| -> build_anthropic_thinking_kwargs(model_gate, ...) -> _thinking_mode_kwargs | ||
| -> _native_stream_provider = 'anthropic' | ||
|
|
||
| Four ways that chain can break and nothing else would catch: `parse_bool` | ||
| reverted to `bool`, the `_is_reasoning` gate dropped, the wrong `model_gate` | ||
| passed, or `_native_stream_provider` left unset so the native handler never runs. | ||
| """ | ||
|
|
||
| import importlib.util | ||
| import os | ||
| import sys | ||
| import types | ||
|
|
||
| import pytest | ||
|
|
||
| from ai.common.config import Config | ||
|
|
||
| _HERE = os.path.dirname(os.path.abspath(__file__)) | ||
| _MOD_PATH = os.path.join(_HERE, '..', '..', 'src', 'nodes', 'llm_anthropic', 'anthropic.py') | ||
|
|
||
| _MODEL = 'claude-sonnet-4-6' | ||
|
|
||
|
|
||
| def _load_node_module(): | ||
| """Load anthropic.py standalone, stubbing the langchain_anthropic client.""" | ||
| saved = sys.modules.get('langchain_anthropic') | ||
| stub = types.ModuleType('langchain_anthropic') | ||
| stub.ChatAnthropic = type('ChatAnthropic', (), {'__init__': lambda self, **kw: None}) | ||
| sys.modules['langchain_anthropic'] = stub | ||
| try: | ||
| spec = importlib.util.spec_from_file_location('_llm_anthropic_node', _MOD_PATH) | ||
| mod = importlib.util.module_from_spec(spec) | ||
| spec.loader.exec_module(mod) | ||
| return mod | ||
| finally: | ||
| if saved is None: | ||
| sys.modules.pop('langchain_anthropic', None) | ||
| else: | ||
| sys.modules['langchain_anthropic'] = saved | ||
|
|
||
|
|
||
| def _build_chat(monkeypatch, *, reasoning: bool, toggle=None): | ||
| """Instantiate the node over a controlled config and return it.""" | ||
| config = { | ||
| 'model': _MODEL, | ||
| 'apikey': 'sk-ant-test', | ||
| 'modelTotalTokens': 200000, | ||
| 'modelOutputTokens': 8192, | ||
| 'capabilities': {'reasoning': reasoning}, | ||
| } | ||
| if toggle is not None: | ||
| config['extendedThinking'] = toggle | ||
|
|
||
| # Both the node and ChatBase read their config through this one call. | ||
| monkeypatch.setattr(Config, 'getNodeConfig', staticmethod(lambda *a, **k: dict(config))) | ||
| return _load_node_module().Chat('anthropic', {}, {}) | ||
|
|
||
|
|
||
| def test_toggle_absent_leaves_thinking_off(monkeypatch): | ||
| chat = _build_chat(monkeypatch, reasoning=True) | ||
|
|
||
| assert chat._thinking_mode_kwargs == {} | ||
| assert chat._native_stream_provider == '' | ||
|
|
||
|
|
||
| def test_toggle_on_reasoning_model_arms_the_native_handler(monkeypatch): | ||
| chat = _build_chat(monkeypatch, reasoning=True, toggle=True) | ||
|
|
||
| assert chat._thinking_mode_kwargs != {} | ||
| assert chat._native_stream_provider == 'anthropic' | ||
|
|
||
|
|
||
| def test_toggle_on_non_reasoning_model_stays_off(monkeypatch): | ||
| # The capability gate wins: the toggle alone must not request thinking. | ||
| chat = _build_chat(monkeypatch, reasoning=False, toggle=True) | ||
|
|
||
| assert chat._thinking_mode_kwargs == {} | ||
| assert chat._native_stream_provider == '' | ||
|
|
||
|
|
||
| @pytest.mark.parametrize('falsy', ['false', 'False', '0', '']) | ||
| def test_string_false_from_the_form_stays_off(monkeypatch, falsy): | ||
| # The case that fails the moment parse_bool is swapped back for bool(): | ||
| # a non-empty string like 'false' is truthy to bool(). | ||
| chat = _build_chat(monkeypatch, reasoning=True, toggle=falsy) | ||
|
|
||
| assert chat._thinking_mode_kwargs == {} | ||
| assert chat._native_stream_provider == '' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.