Skip to content

[API Nodes] add CAMB AI nodes#13037

Open
neilruaro-camb wants to merge 1 commit intoComfy-Org:masterfrom
neilruaro-camb:camb-ai-api-nodes
Open

[API Nodes] add CAMB AI nodes#13037
neilruaro-camb wants to merge 1 commit intoComfy-Org:masterfrom
neilruaro-camb:camb-ai-api-nodes

Conversation

@neilruaro-camb
Copy link
Copy Markdown

@neilruaro-camb neilruaro-camb commented Mar 18, 2026

Summary

Hey! We're the team at CAMB AI - we'd love to be added into the ComfyUI ecosystem. CAMB AI is a localization and audio intelligence platform already trusted and used by organizations like the Premier League, Australian Open, NASCAR, LaLiga, Serie A, Ligue 1, and many more.

This PR adds a set of CAMB AI API nodes to ComfyUI:

  • CAMB AI Voice Selector — Select a voice by ID for TTS
  • CAMB AI Voice Clone — Create a custom cloned voice from an audio sample
  • CAMB AI Text to Speech — Convert text to speech using CAMB AI TTS models (Mars Flash, Mars Pro, Mars Instruct)
  • CAMB AI Translation — Translate text between 16 supported languages
  • CAMB AI Transcription — Transcribe audio to text with speaker diarization
  • CAMB AI Text to Sound — Generate sound effects or music from a text description

Files

  • comfy_api_nodes/apis/cambai.py — Pydantic request/response models
  • comfy_api_nodes/nodes_cambai.py — Node implementations using sync_op, poll_op, and sync_op_raw utilities

Thanks for considering this — happy to make any changes needed!

API Node PR Checklist

Scope

  • Is API Node Change

Pricing & Billing

  • Need pricing update
  • No pricing update

If Need pricing update:

  • Metronome rate cards updated
  • Auto‑billing tests updated and passing

QA

  • QA done
  • QA not required

Comms

  • Informed Kosinkadink

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Adds a new CAMB AI integration module containing seven API-enabled nodes and an extension class. The nodes include voice selection and cloning, text-to-speech conversion, translation, transcription, and text-to-sound generation. Each node implements a schema definition and execute method with appropriate input/output transformations. The module includes helper functions for endpoint construction, language mappings, and API communication via polling mechanisms. The extension exposes the node list through a factory entrypoint function. Total additions: 467 lines.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '[API Nodes] add CAMB AI nodes' directly and clearly describes the main change: adding CAMB AI API nodes to the codebase.
Description check ✅ Passed The pull request description clearly relates to the changeset, detailing the CAMB AI integration with specific nodes being added that match the file changes.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@comfy_api_nodes/nodes_cambai.py`:
- Around line 32-38: The _cambai_endpoint helper currently reads CAMBAI_API_KEY
and defaults to an empty string, causing silent auth failures; change
_cambai_endpoint to explicitly check os.environ.get("CAMBAI_API_KEY") and if
missing/empty raise a clear exception (e.g., RuntimeError) before constructing
the ApiEndpoint so no request is emitted with an empty "x-api-key" header;
update the function to reference CAMBAI_API_KEY and ApiEndpoint and ensure
callers relying on _cambai_endpoint will surface a clear configuration error
instead of sending requests with an empty key.
- Around line 294-296: The current code silently returns IO.NodeOutput("") when
result.texts is missing or empty; change this so empty translation results are
treated as failures: check result and result.texts after the API call (same
place that currently uses result.texts[0]) and if texts is empty or the API
indicates an error, surface the failure by throwing/returning an error (e.g.,
raise an exception or return an IO.NodeOutput containing a clear error
state/message) that includes relevant details from the API response (status,
error fields, or the raw result) instead of returning an empty string.
- Around line 362-369: Before constructing CambAIDialogueItem instances,
validate that result_raw is a dict and contains a "transcript" key that is a
list of dict-like items: check result_raw from sync_op_raw(...) and verify
isinstance(result_raw, dict) and isinstance(result_raw.get("transcript"), list).
If the shape is invalid, handle gracefully (e.g., log the raw response and
return/raise a clear error) instead of letting Pydantic raise opaque errors;
then iterate over transcript and only attempt CambAIDialogueItem(**item) for
items that are dicts (skip or report non-dict entries) before building dialogues
and joining item.text into text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd8eb757-9ea6-473b-a951-059d226dfe67

📥 Commits

Reviewing files that changed from the base of the PR and between b941913 and 6a24746.

⛔ Files ignored due to path filters (1)
  • comfy_api_nodes/apis/cambai.py is excluded by !comfy_api_nodes/apis/**
📒 Files selected for processing (1)
  • comfy_api_nodes/nodes_cambai.py

Comment on lines +32 to +38
def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint:
api_key = os.environ.get("CAMBAI_API_KEY", "")
return ApiEndpoint(
path=f"{CAMBAI_API_BASE}/{route}",
method=method,
headers={"x-api-key": api_key},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast when CAMBAI_API_KEY is missing.

Line 33 defaults to an empty key, and Line 37 still sends it. That guarantees avoidable auth failures across all CAMB API nodes instead of surfacing a clear local configuration error.

Suggested fix
 def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint:
-    api_key = os.environ.get("CAMBAI_API_KEY", "")
+    api_key = os.environ.get("CAMBAI_API_KEY")
+    if not api_key:
+        raise ValueError("Missing CAMBAI_API_KEY environment variable.")
     return ApiEndpoint(
         path=f"{CAMBAI_API_BASE}/{route}",
         method=method,
         headers={"x-api-key": api_key},
     )

As per coding guidelines: comfy_api_nodes/**: ... Proper error handling for API failures (timeouts, rate limits, auth errors).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint:
api_key = os.environ.get("CAMBAI_API_KEY", "")
return ApiEndpoint(
path=f"{CAMBAI_API_BASE}/{route}",
method=method,
headers={"x-api-key": api_key},
)
def _cambai_endpoint(route: str, method: str = "GET") -> ApiEndpoint:
api_key = os.environ.get("CAMBAI_API_KEY")
if not api_key:
raise ValueError("Missing CAMBAI_API_KEY environment variable.")
return ApiEndpoint(
path=f"{CAMBAI_API_BASE}/{route}",
method=method,
headers={"x-api-key": api_key},
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_api_nodes/nodes_cambai.py` around lines 32 - 38, The _cambai_endpoint
helper currently reads CAMBAI_API_KEY and defaults to an empty string, causing
silent auth failures; change _cambai_endpoint to explicitly check
os.environ.get("CAMBAI_API_KEY") and if missing/empty raise a clear exception
(e.g., RuntimeError) before constructing the ApiEndpoint so no request is
emitted with an empty "x-api-key" header; update the function to reference
CAMBAI_API_KEY and ApiEndpoint and ensure callers relying on _cambai_endpoint
will surface a clear configuration error instead of sending requests with an
empty key.

Comment on lines +294 to +296
if result.texts and len(result.texts) > 0:
return IO.NodeOutput(result.texts[0])
return IO.NodeOutput("")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t treat empty translation results as success.

Line 294-Line 296 silently returns "" when the API payload is missing/empty, which masks upstream failures and makes debugging harder.

Suggested fix
-        if result.texts and len(result.texts) > 0:
-            return IO.NodeOutput(result.texts[0])
-        return IO.NodeOutput("")
+        if not result.texts:
+            raise ValueError("CAMB AI translation returned no texts.")
+        return IO.NodeOutput(result.texts[0])

As per coding guidelines: comfy_api_nodes/**: ... Proper error handling for API failures (timeouts, rate limits, auth errors).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_api_nodes/nodes_cambai.py` around lines 294 - 296, The current code
silently returns IO.NodeOutput("") when result.texts is missing or empty; change
this so empty translation results are treated as failures: check result and
result.texts after the API call (same place that currently uses result.texts[0])
and if texts is empty or the API indicates an error, surface the failure by
throwing/returning an error (e.g., raise an exception or return an IO.NodeOutput
containing a clear error state/message) that includes relevant details from the
API response (status, error fields, or the raw result) instead of returning an
empty string.

Comment on lines +362 to +369
result_raw = await sync_op_raw(
cls,
_cambai_endpoint(f"transcription-result/{poll_result.run_id}"),
)

transcript = result_raw.get("transcript", [])
dialogues = [CambAIDialogueItem(**item) for item in transcript]
text = " ".join(item.text for item in dialogues)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if we have git context to determine if this code is newly introduced
cd comfy_api_nodes && git diff HEAD -- nodes_cambai.py | head -100

Repository: Comfy-Org/ComfyUI

Length of output: 43


🏁 Script executed:

# Read the file around lines 362-369 to understand the context
cat -n comfy_api_nodes/nodes_cambai.py | sed -n '350,380p'

Repository: Comfy-Org/ComfyUI

Length of output: 1354


🏁 Script executed:

# Search for CambAIDialogueItem definition to understand the Pydantic model
rg "class CambAIDialogueItem" comfy_api_nodes/

Repository: Comfy-Org/ComfyUI

Length of output: 128


🏁 Script executed:

# Search for sync_op_raw to understand what it returns
rg "def sync_op_raw|async def sync_op_raw" comfy_api_nodes/

Repository: Comfy-Org/ComfyUI

Length of output: 114


🏁 Script executed:

# Read CambAIDialogueItem definition
cat -n comfy_api_nodes/apis/cambai.py | grep -A 10 "class CambAIDialogueItem"

Repository: Comfy-Org/ComfyUI

Length of output: 533


🏁 Script executed:

# Read sync_op_raw definition
cat -n comfy_api_nodes/util/client.py | grep -A 20 "async def sync_op_raw"

Repository: Comfy-Org/ComfyUI

Length of output: 1048


🏁 Script executed:

# Check git status and log to understand if this file was recently modified
cd comfy_api_nodes && git log --oneline -n 20 -- nodes_cambai.py

Repository: Comfy-Org/ComfyUI

Length of output: 98


🏁 Script executed:

# Check the full diff for nodes_cambai.py to see what changed
cd comfy_api_nodes && git diff HEAD~1 HEAD -- nodes_cambai.py | head -200

Repository: Comfy-Org/ComfyUI

Length of output: 89


🏁 Script executed:

# Get the full sync_op_raw implementation including return type
cat -n comfy_api_nodes/util/client.py | sed -n '185,250p'

Repository: Comfy-Org/ComfyUI

Length of output: 3121


🏁 Script executed:

# Check the git log to see this is indeed the first/initial commit
git log --all --oneline | head -5

Repository: Comfy-Org/ComfyUI

Length of output: 384


🏁 Script executed:

# Check if there's any documentation or type hints for sync_op_raw return value
rg "sync_op_raw" comfy_api_nodes/ -A 5 -B 2 | head -80

Repository: Comfy-Org/ComfyUI

Length of output: 4625


🏁 Script executed:

# Check for similar patterns in other CAMB AI or API node code
rg "sync_op_raw" comfy_api_nodes/nodes_*.py -A 5 | head -150

Repository: Comfy-Org/ComfyUI

Length of output: 6895


🏁 Script executed:

# Check if there are other examples of response validation in API nodes
rg "isinstance.*dict" comfy_api_nodes/nodes_*.py -B 2 -A 2 | head -100

Repository: Comfy-Org/ComfyUI

Length of output: 1872


Add validation for transcription response structure before parsing.

The code assumes result_raw is a dict with a "transcript" key containing a list of dicts parseable as CambAIDialogueItem. Malformed API responses will fail with opaque Pydantic validation errors. Add explicit shape checks before constructing the model instances.

Suggested validation
         result_raw = await sync_op_raw(
             cls,
             _cambai_endpoint(f"transcription-result/{poll_result.run_id}"),
         )
 
-        transcript = result_raw.get("transcript", [])
-        dialogues = [CambAIDialogueItem(**item) for item in transcript]
+        if not isinstance(result_raw, dict):
+            raise ValueError("Unexpected CAMB AI transcription response format.")
+        transcript = result_raw.get("transcript")
+        if not isinstance(transcript, list):
+            raise ValueError("Missing or invalid 'transcript' in CAMB AI transcription response.")
+        dialogues = [CambAIDialogueItem(**item) for item in transcript if isinstance(item, dict)]
         text = " ".join(item.text for item in dialogues)
         return IO.NodeOutput(text)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result_raw = await sync_op_raw(
cls,
_cambai_endpoint(f"transcription-result/{poll_result.run_id}"),
)
transcript = result_raw.get("transcript", [])
dialogues = [CambAIDialogueItem(**item) for item in transcript]
text = " ".join(item.text for item in dialogues)
result_raw = await sync_op_raw(
cls,
_cambai_endpoint(f"transcription-result/{poll_result.run_id}"),
)
if not isinstance(result_raw, dict):
raise ValueError("Unexpected CAMB AI transcription response format.")
transcript = result_raw.get("transcript")
if not isinstance(transcript, list):
raise ValueError("Missing or invalid 'transcript' in CAMB AI transcription response.")
dialogues = [CambAIDialogueItem(**item) for item in transcript if isinstance(item, dict)]
text = " ".join(item.text for item in dialogues)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_api_nodes/nodes_cambai.py` around lines 362 - 369, Before constructing
CambAIDialogueItem instances, validate that result_raw is a dict and contains a
"transcript" key that is a list of dict-like items: check result_raw from
sync_op_raw(...) and verify isinstance(result_raw, dict) and
isinstance(result_raw.get("transcript"), list). If the shape is invalid, handle
gracefully (e.g., log the raw response and return/raise a clear error) instead
of letting Pydantic raise opaque errors; then iterate over transcript and only
attempt CambAIDialogueItem(**item) for items that are dicts (skip or report
non-dict entries) before building dialogues and joining item.text into text.

@neilruaro-camb
Copy link
Copy Markdown
Author

Note: The CAMB AI nodes currently use direct API calls with an environment variable for the API key (CAMBAI_API_KEY).

To support ComfyUI Cloud, we'd need a /proxy/cambai/ route set up on the api.comfy.org proxy - happy to coordinate on getting that configured so we can align with the same auth pattern used by Runway, Bria, Grok, etc.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant