Skip to content

feat(nodes): add Baidu Qianfan ERNIE LLM node#1006

Open
jimmyzhuu wants to merge 4 commits into
rocketride-org:developfrom
jimmyzhuu:feat/RR-1005-add-baidu-qianfan-llm
Open

feat(nodes): add Baidu Qianfan ERNIE LLM node#1006
jimmyzhuu wants to merge 4 commits into
rocketride-org:developfrom
jimmyzhuu:feat/RR-1005-add-baidu-qianfan-llm

Conversation

@jimmyzhuu
Copy link
Copy Markdown

@jimmyzhuu jimmyzhuu commented May 27, 2026

Summary

This PR adds first-class Baidu Qianfan / ERNIE support as a dedicated RocketRide LLM node.

Instead of requiring users to configure Baidu through the generic OpenAI-compatible node, the new llm_baidu_qianfan node exposes Qianfan-specific defaults, provider branding, model profiles, runtime validation, sync-models support, and targeted tests.

Closes #1005

Motivation

RocketRide already includes dedicated nodes for major LLM providers such as OpenAI, Anthropic, Gemini, Mistral, DeepSeek, Perplexity, xAI, and Qwen. Baidu Qianfan is an important provider for Chinese-language and enterprise AI workflows, and Qianfan now exposes OpenAI-compatible chat-completions endpoints.

A dedicated node improves the user experience in three ways:

  • Users can select Baidu Qianfan directly from the node catalogue.
  • ERNIE model profiles are discoverable without manual base URL setup.
  • The model sync tooling can validate and maintain Qianfan profiles like other cloud providers.

What Changed

New node runtime

  • Added nodes/src/nodes/llm_baidu_qianfan/.
  • Implemented IGlobal, IInstance, and qianfan_client.Chat.
  • Uses langchain_openai.ChatOpenAI against Qianfan's OpenAI-compatible API.
  • Defaults to https://qianfan.baidubce.com/v2.
  • Keeps serverbase configurable so users can switch endpoints, including Baidu's international endpoint when needed.
  • Validates configuration with a 1-token chat-completions probe.
  • Maps common OpenAI SDK exceptions into provider-specific user-facing messages.

Node metadata and profiles

  • Added services.json for llm_baidu_qianfan://.
  • Added default ERNIE profiles:
    • ernie-4.5-turbo-128k
    • ernie-4.5-turbo-32k
    • ernie-5.0-thinking-preview
    • custom
  • Marked the API-confirmed ERNIE profiles with modelSource: "provider".
  • Added a dynamic node test case using the existing mocked LLM response path.
  • Added node README documentation.
  • Added test mock credentials for llm_baidu_qianfan.

Shared UI

  • Added a Baidu node icon asset.
  • Registered baidu-qianfan.svg in the shared UI icon resolver.
  • Kept the Baidu icon out of THEME_DYNAMIC_ICONS because it is a colored brand asset and should not be filtered as a single-color icon.

Model sync tooling

  • Added BaiduQianfanProvider.
  • Registered llm_baidu_qianfan in _PROVIDER_REGISTRY and _SERVICES_JSON_PATHS.
  • Added Qianfan config to sync_models.config.json.
  • Added ERNIE title mapping and token/output-token overrides.
  • Added ROCKETRIDE_BAIDU_QIANFAN_KEY live-test marker.
  • Added live profile-existence coverage for Qianfan.
  • Added focused provider unit tests.
  • Updated tools/SYNC_MODELS.md.

Implementation Notes

  • Qianfan API keys are not forced to start with sk-. The runtime only validates that a non-empty key is configured because Baidu keys can use other formats.
  • The first version intentionally sticks to standard OpenAI-compatible chat completions. Qianfan-specific reasoning controls are not added yet, keeping this PR focused on the base provider integration.
  • custom remains protected from sync-models deprecation.
  • The sync dry run with real Qianfan credentials reports no changes after this PR's checked-in profiles.

Testing

Local focused tests:

  • PYTHONPATH=tools/src pytest tools/test/test_baidu_qianfan_provider.py -v
  • PYTHONPATH=nodes/src uv run --with pytest --with pytest-asyncio --with pytest-timeout --with pytest-xdist --with pytest-mock --with json5 --with requests --with httpx --with openai pytest nodes/test/test_contracts.py -k llm_baidu_qianfan -v
  • python -m py_compile nodes/src/nodes/llm_baidu_qianfan/IGlobal.py nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py tools/src/providers/baidu_qianfan.py
  • git diff --check

Live/API validation:

  • PYTHONPATH=tools/src uv run --with pytest --with json5 --with openai pytest tools/test/test_sync_live.py -k baidu_qianfan -v
    Verified this skips cleanly without credentials.
  • ROCKETRIDE_BAIDU_QIANFAN_KEY=*** PYTHONPATH=tools/src uv run --with pytest --with json5 --with openai pytest tools/test/test_sync_live.py -k baidu_qianfan -v
    Verified this passes with a real Qianfan key.
  • Real Qianfan chat-completions smoke test with ernie-4.5-turbo-128k.
  • ROCKETRIDE_BAIDU_QIANFAN_KEY=*** PYTHONPATH=tools/src uv run --with json5 --with openai python tools/src/sync_models.py --provider llm_baidu_qianfan --model-source provider
    Verified dry run reports no changes.

Local Environment Note

./builder nodes:test-contracts was not runnable in this local environment because the default pnpm is 9.15.9, while this repository requires pnpm >=10.0.0. Direct pytest contract coverage for the new node passed.

Security

  • No API keys or credentials are committed.
  • Live Qianfan credentials were only supplied through environment variables during local verification.

Summary by CodeRabbit

  • New Features

    • Added support for Baidu Qianfan (ERNIE) as a new LLM provider with configurable API key, model selection, and token limits.
    • Support for OpenAI-compatible endpoint with selectable China and International base URLs.
    • Pre-configured ERNIE model profiles available.
  • Documentation

    • Added provider documentation and configuration reference.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Review Change Stack

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This PR adds a complete Baidu Qianfan LLM node to RocketRide, enabling access to ERNIE chat models via Qianfan's OpenAI-compatible API. It includes the node runtime with configuration validation and client lifecycle, service definition with selectable profiles, model sync provider for discovering available ERNIE models, documentation, and comprehensive test coverage.

Changes

Baidu Qianfan LLM Node Integration

Layer / File(s) Summary
Node Runtime: Validation, Client, and Lifecycle
nodes/src/nodes/llm_baidu_qianfan/IGlobal.py, nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py, nodes/src/nodes/llm_baidu_qianfan/IInstance.py, nodes/src/nodes/llm_baidu_qianfan/__init__.py, nodes/src/nodes/llm_baidu_qianfan/requirements.txt
IGlobal validates configuration by instantiating an OpenAI-compatible client and performing a test chat completion, translating auth/rate-limit/network errors into user-facing warnings with JSON payload parsing. Chat adapter configures ChatOpenAI with Qianfan's base URL and API key, customizes retry behavior (non-retryable auth errors, retryable rate-limits), and maps provider exceptions to structured messages. Global lifecycle ensures dependencies, constructs the client, and clears it on shutdown.
Service Definition with Profiles and UI Fields
nodes/src/nodes/llm_baidu_qianfan/services.json
Declares the node as an LLM provider with pipe routing from questions to answers. Defines preconfig model profiles (default ernie-4-5-turbo-128k, plus variants and custom), with per-profile token limits, context windows, and selectable base URL. Configurable fields control model selection and token boundaries. Conditional profile mapping exposes relevant config properties. Includes mock test case validating responses.
Documentation and Public Registration
nodes/src/nodes/llm_baidu_qianfan/README.md, docs/README-nodes.md, tools/SYNC_MODELS.md
Node README describes ERNIE models via Qianfan's OpenAI-compatible API and required fields (model, API key, optional base URL). LLM Providers tables in user and operator docs register the new node and document the ROCKETRIDE_BAIDU_QIANFAN_KEY environment variable.
Model Sync Provider: Handler and Configuration
tools/src/providers/baidu_qianfan.py, tools/src/sync_models.config.json, tools/src/sync_models.py
BaiduQianfanProvider creates OpenAI-compatible clients and fetches available ERNIE models. sync_models.config.json defines provider endpoint, auth, model filtering rules (ernie- prefix, excluding embedding/rerank/image families), protected profile keys, context windows, and per-model token limit overrides. sync_models.py registers the provider and services.json path.
Tests: Infrastructure, Validation, and Live Sync
nodes/test/framework/pipeline.py, nodes/test/test_baidu_qianfan_global_validation.py, tools/test/markers.py, tools/test/test_baidu_qianfan_provider.py, tools/test/test_sync_live.py
Mock credentials support test mode. IGlobal validation tests verify correct error warnings for auth and rate-limit failures via stubbed openai exceptions. Provider registration test confirms handler and services.json path. Provider endpoint test validates OpenAI-compatible client construction with correct base URL. Live sync test (guarded by environment variable marker) verifies configured ERNIE profiles exist in live API.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rocketride-org/rocketride-server#719: Standardized LLMBase as the parent class for IInstance implementations, which this PR uses for llm_baidu_qianfan.IInstance.
  • rocketride-org/rocketride-server#651: Established the model sync infrastructure (sync_models.py, sync_models.config.json, provider handlers) that this PR extends with Baidu Qianfan model discovery.
  • rocketride-org/rocketride-server#967: Adds another OpenAI-compatible LLM node using the same shared test/sync infrastructure (markers, mock credentials, provider pattern) as this PR.

Suggested labels

ci/cd

Suggested reviewers

  • jmaionchi
  • stepmikhaylov
  • Rod-Christensen
  • kwit75

Poem

🐰 A Qianfan of ERNIE dreams,
Where chat completions flow downstream,
With profiles prepped and errors mapped,
And validations firmly tapped. 🔑
From sync to shape, the node takes flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the primary change: adding a Baidu Qianfan ERNIE LLM node. It is concise, specific, and directly reflects the main objective.
Linked Issues check ✅ Passed The PR comprehensively implements all coding requirements from issue #1005: runtime node (IGlobal, IInstance, qianfan_client.Chat), services.json profiles, OpenAI-compatible client integration, validation, sync-models provider support, tests, and documentation.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Baidu Qianfan LLM node as specified in issue #1005. No unrelated modifications detected in configuration, testing, or tooling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added docs Documentation module:nodes Python pipeline nodes module:ui Chat UI and Dropper UI labels May 27, 2026
@github-actions
Copy link
Copy Markdown

No description provided.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py`:
- Around line 69-97: The APIConnectionError checks in is_retryable_error and
map_exception are unreachable because APIConnectionError subclasses APIError;
update both methods (is_retryable_error and map_exception) to check for
APIConnectionError before checking for APIError, preserving the existing return
values/messages for AuthenticationError, RateLimitError, APIConnectionError, and
APIError so connection-specific logic runs as intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7130dc6d-1b61-4157-9c57-faedc6e54c80

📥 Commits

Reviewing files that changed from the base of the PR and between a57a379 and 72734c7.

⛔ Files ignored due to path filters (1)
  • packages/shared-ui/src/assets/nodes/baidu-qianfan.svg is excluded by !**/*.svg
📒 Files selected for processing (17)
  • docs/README-nodes.md
  • nodes/src/nodes/llm_baidu_qianfan/IGlobal.py
  • nodes/src/nodes/llm_baidu_qianfan/IInstance.py
  • nodes/src/nodes/llm_baidu_qianfan/README.md
  • nodes/src/nodes/llm_baidu_qianfan/__init__.py
  • nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py
  • nodes/src/nodes/llm_baidu_qianfan/requirements.txt
  • nodes/src/nodes/llm_baidu_qianfan/services.json
  • nodes/test/framework/pipeline.py
  • packages/shared-ui/src/components/canvas/util/get-icon-path.ts
  • tools/SYNC_MODELS.md
  • tools/src/providers/baidu_qianfan.py
  • tools/src/sync_models.config.json
  • tools/src/sync_models.py
  • tools/test/markers.py
  • tools/test/test_baidu_qianfan_provider.py
  • tools/test/test_sync_live.py

Comment thread nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py
Copy link
Copy Markdown
Collaborator

@asclearuc asclearuc left a comment

Choose a reason for hiding this comment

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

Thanks @jimmyzhuu — this is a clean, complete provider integration (node runtime, ERNIE profiles, model-sync tooling, tests, and icon), and it follows the existing OpenAI-compatible node pattern well.

Before it can merge it needs a rebase onto develop, because #926 changed how node icons work and it rewrote the exact files your PR touches under packages/. Points to pay attention to during the rebase:

  • packages/shared-ui/src/assets/nodes/ no longer exists. Move your baidu-qianfan.svg to nodes/src/nodes/llm_baidu_qianfan/baidu-qianfan.svg (next to services.json). Keep "icon": "baidu-qianfan.svg" in services.json unchanged — only the file location moves.
  • get-icon-path.ts is gone, replaced by Icon.tsx, which auto-discovers nodes/src/nodes/**/*.svg at build time. Drop all your changes to it — there is no manual icon registration anymore.
  • THEME_DYNAMIC_ICONS is gone. Theming is now automatic: a build step rewrites single-color SVGs to currentColor (so they follow the light/dark theme) and passes multicolor logos through unchanged. Your note about keeping Baidu out of the dynamic set no longer applies — instead, make sure the Baidu SVG is genuinely multicolor if you want it to keep its brand blue, otherwise it will be theme-tinted.
  • docs/README-nodes.md was also edited by #926 — expect a conflict there and resolve it.

After rebasing, please run builder shared-ui:test and the icon audit (node scripts/audit-icons.mjs); the audit asserts every services.json icon exists inside its node folder.

There are also a few code findings to address for a clean PR — see the inline comments below. CodeRabbit's notes are worth a pass too.

Comment on lines +82 to +110
except APIStatusError as e:
status = getattr(e, 'status_code', None) or getattr(e, 'status', None)
try:
resp = getattr(e, 'response', None)
data = resp.json() if resp is not None else None
if isinstance(data, dict):
err = data.get('error')
if isinstance(err, dict):
etype = err.get('type') or err.get('code')
emsg = err.get('message') or data.get('message')
else:
etype = data.get('code')
emsg = data.get('message')
message = self._format_error(status, etype, emsg, str(e))
else:
message = self._format_error(status, None, None, str(e))
except Exception:
message = self._format_error(status, None, None, str(e))
warning(message)
return
except AuthenticationError:
warning('Baidu Qianfan API key is invalid or unauthorized.')
return
except RateLimitError:
warning('Baidu Qianfan rate limit exceeded while validating the configuration.')
return
except APIConnectionError:
warning('Could not connect to Baidu Qianfan. Check the configured base URL and network access.')
return
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

these except branches can never run.

except APIStatusError (line 82) is listed before except AuthenticationError (line 102) and except RateLimitError (line 105). In the OpenAI SDK both AuthenticationError (401) and RateLimitError (429) are subclasses of APIStatusError, so the APIStatusError branch catches them first and the two dedicated handlers below are dead code. The friendly messages — "Baidu Qianfan API key is invalid or unauthorized." and the rate-limit message — never appear; a bad key at save time falls through to the generic formatted string instead.

Move the AuthenticationError and RateLimitError handlers above the APIStatusError handler so the specific cases are matched first. (APIConnectionError is fine — it is not a subclass of APIStatusError.) Your qianfan_client.py already orders these correctly; this is the one spot where the order slipped.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 07af0f2: validateConfig() now catches AuthenticationError and RateLimitError before APIStatusError, so the provider-specific API key and rate-limit warnings are reachable. Added focused regression coverage in nodes/test/test_baidu_qianfan_global_validation.py and verified it passes along with py_compile and the llm_baidu_qianfan contract tests.

Comment on lines +140 to +144
"baidu_qianfan.serverbase": {
"type": "string",
"title": "Base URL",
"description": "Qianfan OpenAI-compatible API base URL"
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

surface both endpoints as a dropdown instead of a README copy-paste (and verify the international URL first).

Right now Base URL is a free-text field and the international endpoint is only documented as a raw URL in the README that users hand-copy.

Two problems: it's easy to mistype, and the documented domain https://api.baiduqianfan.ai/v1 is a different domain from the official qianfan.baidubce.com used everywhere else in this PR — the Base URL receives the user's API key, so an unverified domain is a credential-leak risk.

Since the runtime already reads serverbase directly as the URL, make it a labeled enum (no Python change needed), the same way the Qwen node offers regional endpoints:

"baidu_qianfan.serverbase": {
	"type": "string",
	"title": "Base URL",
	"description": "Qianfan OpenAI-compatible API endpoint.",
	"default": "https://qianfan.baidubce.com/v2",
	"enum": [
		["https://qianfan.baidubce.com/v2", "China (default)"],
		["https://api.baiduqianfan.ai/v1", "International"]
	]
},

Then drop the "set the base URL to …" line from the README — the dropdown documents itself.

Before adding the international option, confirm api.baiduqianfan.ai is an official Baidu domain with a source. If you can't verify it, omit that second entry from both the enum and the README — don't ship an unverified endpoint as a selectable option, which is worse than a doc line.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 07af0f2: Base URL is now a labeled enum with China (default) and International options, and the README no longer asks users to copy-paste a raw endpoint. I verified https://api.baiduqianfan.ai/v1 against official Baidu AI Cloud Qianfan documentation before adding it:

@jimmyzhuu jimmyzhuu force-pushed the feat/RR-1005-add-baidu-qianfan-llm branch from 89a5bc7 to 07af0f2 Compare June 2, 2026 00:12
@github-actions github-actions Bot removed the module:ui Chat UI and Dropper UI label Jun 2, 2026
@jimmyzhuu
Copy link
Copy Markdown
Author

@asclearuc Thanks for your detailed review! Rebased this PR onto the latest develop and resolved the #926 icon-system changes:

  • Moved baidu-qianfan.svg next to the Qianfan node under nodes/src/nodes/llm_baidu_qianfan/.
  • Dropped the legacy shared-ui icon resolver/asset changes from the old branch shape.
  • Resolved the docs/README-nodes.md conflict.
  • Addressed the two inline findings: validateConfig() exception ordering now preserves the specific auth/rate-limit warnings, and Base URL is a labeled China/International enum. The international endpoint was verified against official Baidu AI Cloud Qianfan docs before being added.

Verification run locally:

  • PATH=/private/tmp/rocketride-pnpm10:$PATH ./builder shared-ui:test — passed, 19 tests.
  • node scripts/audit-icons.mjs — exit 0; existing unrelated fallback-icon warnings only.
  • PYTHONPATH=nodes/src uv run --with pytest --with pytest-asyncio --with pytest-timeout --with pytest-xdist --with pytest-mock pytest nodes/test/test_baidu_qianfan_global_validation.py -v — passed, 2 tests.
  • PYTHONPATH=nodes/src uv run --with pytest --with pytest-asyncio --with pytest-timeout --with pytest-xdist --with pytest-mock --with json5 --with requests --with httpx --with openai pytest nodes/test/test_contracts.py -k llm_baidu_qianfan -v — passed, 2 tests.
  • PYTHONPATH=tools/src pytest tools/test/test_baidu_qianfan_provider.py -v — passed, 2 tests.
  • PYTHONPATH=tools/src uv run --with pytest --with json5 --with openai pytest tools/test/test_sync_live.py -k baidu_qianfan -v — skipped as expected without ROCKETRIDE_BAIDU_QIANFAN_KEY.
  • python -m py_compile nodes/src/nodes/llm_baidu_qianfan/IGlobal.py nodes/src/nodes/llm_baidu_qianfan/qianfan_client.py tools/src/providers/baidu_qianfan.py — passed.
  • git diff --check origin/develop...HEAD — passed.

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

Labels

docs Documentation module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Baidu Qianfan ERNIE LLM node

2 participants