Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 8 additions & 23 deletions nodes/src/nodes/accessibility_describe/accessibility_vision.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@

# Spatial format prompt modifiers
SPATIAL_PROMPTS = {
'clock': '\n\nUse clock positions for spatial references (12 o\'clock = straight ahead).',
'clock': "\n\nUse clock positions for spatial references (12 o'clock = straight ahead).",
Copy link
Copy Markdown
Contributor

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:

#!/bin/bash
# Check if pyproject.toml contains ruff quote-style configuration
rg -A5 '\[tool\.ruff\]' pyproject.toml

Repository: rocketride-org/rocketride-server

Length of output: 210


String literal uses double quotes instead of required single quotes.

Line 70 should use single quotes per the coding guideline for nodes/**/*.py. While the string contains a single quote character (o'clock), it should use single quotes with escaping rather than double quotes:

'clock': 'Use clock positions for spatial references (12 o\'clock = straight ahead).',

Additionally, the ruff configuration in pyproject.toml is missing quote-style = 'single', which would enforce this convention automatically. Add this to the [tool.ruff] section to align with project standards.

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

In `@nodes/src/nodes/accessibility_describe/accessibility_vision.py` at line 70,
Replace the double-quoted string value for the 'clock' key in the
accessibility_vision dictionary with a single-quoted string and escape the
internal single quote (i.e., change the value for the 'clock' key in
nodes/src/nodes/accessibility_describe/accessibility_vision.py), and also update
the project's tooling by adding quote-style = 'single' to the [tool.ruff]
section of pyproject.toml so Ruff enforces single-quote strings across
nodes/**/*.py.

'relative': '\n\nUse relative directions (left, right, ahead, behind) for spatial references.',
'both': '\n\nUse both clock positions and relative directions for spatial references.',
}
Expand Down Expand Up @@ -98,31 +98,18 @@ def __init__(self, provider: str, connConfig: dict[str, Any], bag: dict[str, Any
spatial_format = config.get('accessibility.spatialFormat', 'clock')

# Build system prompt with config modifiers
self._system_prompt = (
config.get('accessibility.systemPrompt')
or config.get('systemPrompt')
or DEFAULT_SYSTEM_PROMPT
)
self._system_prompt = config.get('accessibility.systemPrompt') or config.get('systemPrompt') or DEFAULT_SYSTEM_PROMPT
self._system_prompt += HAZARD_PROMPTS.get(hazard_priority, '')
self._system_prompt += SPATIAL_PROMPTS.get(spatial_format, '')

self._prompt = (
config.get('accessibility.prompt')
or config.get('prompt')
or DEFAULT_PROMPT
)
self._prompt = config.get('accessibility.prompt') or config.get('prompt') or DEFAULT_PROMPT

if not api_key:
raise ValueError(
'Missing Google AI API key. Get one at https://aistudio.google.com/apikey'
)
raise ValueError('Missing Google AI API key. Get one at https://aistudio.google.com/apikey')

# Validate the API key format
if api_key.startswith('sk-'):
raise ValueError(
'Invalid API key format. This appears to be an OpenAI key. '
'Please provide a Google AI API key.'
)
raise ValueError('Invalid API key format. This appears to be an OpenAI key. Please provide a Google AI API key.')

try:
self._client = genai.Client(api_key=api_key)
Expand Down Expand Up @@ -157,7 +144,7 @@ def _format_user_error(self, error_msg: str) -> str:
if any(phrase in error_lower for phrase in ['invalid input', 'bad request', '400']):
return 'Invalid input. Please check your image format and prompt.'
if any(phrase in error_lower for phrase in ['model not found', 'unavailable', 'not supported']):
return f'Model \'{self._model}\' is currently unavailable. Please try a different model.'
return f"Model '{self._model}' is currently unavailable. Please try a different model."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quote style violates coding guideline.

This f-string was reformatted to use double quotes, but the coding guideline requires single quotes for regular string literals. While the string contains single quotes (making double quotes convenient to avoid escaping), the project standard requires consistent use of single quotes.

As per coding guidelines: nodes/**/*.py must use single quotes for regular string literals.

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

In `@nodes/src/nodes/accessibility_describe/accessibility_vision.py` at line 147,
Change the returned string to use single-quoted string literals per the
nodes/**/*.py guideline; update the return in accessibility_vision.py (the line
that builds the message using self._model) to use single quotes for the outer
string and escape the inner single quotes around the model placeholder so the
message remains identical (i.e., keep the same text including the quotes around
the model but switch outer quotes to single and escape the inner ones).

if any(phrase in error_lower for phrase in ['timeout', 'timed out']):
return 'Request timed out. Please try again.'
if any(phrase in error_lower for phrase in ['content policy', 'safety', 'blocked']):
Expand Down Expand Up @@ -199,9 +186,7 @@ def chat(self, question: Question) -> Answer:
mime_type = header.split(':')[1].split(';')[0]
image_bytes = base64.b64decode(b64_data)
except (ValueError, IndexError, base64.binascii.Error) as e:
raise ValueError(
'Malformed image data URL. Expected format: data:<mime>;base64,<data>'
) from e
raise ValueError('Malformed image data URL. Expected format: data:<mime>;base64,<data>') from e

# Build request contents once (deterministic, no need to rebuild per retry)
contents = [
Expand Down Expand Up @@ -233,7 +218,7 @@ def chat(self, question: Question) -> Answer:
except Exception as e:
last_error = e
if attempt < max_retries and self._shouldRetry(e):
delay = base_delay * (2 ** attempt)
delay = base_delay * (2**attempt)
time.sleep(delay)
continue
break
Expand Down
7 changes: 1 addition & 6 deletions nodes/src/nodes/agent_crewai/crewai.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,7 @@ def _invoke_tool(tool_name: str, input: Any = None, kwargs: Optional[Dict[str, A
agent_obj = Agent(
role='Assistant',
goal='Solve the user request using available tools when helpful.',
backstory=(
'You are an agent node in a tool-invocation hierarchy. '
'You may call tools wired to you via the host tools interface. '
'When a tool is needed, call it; otherwise respond directly. '
'Follow any additional instructions exactly.'
),
backstory=('You are an agent node in a tool-invocation hierarchy. You may call tools wired to you via the host tools interface. When a tool is needed, call it; otherwise respond directly. Follow any additional instructions exactly.'),
tools=tools_for_agent,
llm=llm,
verbose=False,
Expand Down
1 change: 0 additions & 1 deletion nodes/src/nodes/agent_langchain/IInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,3 @@ def invoke(self, param: Any) -> Any: # noqa: ANN401
if isinstance(op, str) and op.startswith('tool.'):
return self.IGlobal.agent.handle_invoke(self, param)
return super().invoke(param)

3 changes: 1 addition & 2 deletions nodes/src/nodes/agent_langchain/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _llm_type(self) -> str:
def _identifying_params(self) -> Dict[str, Any]:
return {'framework': 'rocketride', 'adapter': 'tool_calling_json'}

def bind_tools(self, tools: Any, **kwargs: Any) -> "RocketRideToolCallingChatModel":
def bind_tools(self, tools: Any, **kwargs: Any) -> 'RocketRideToolCallingChatModel':
try:
self._bound_tools = _normalize_bound_tools(tools)
except Exception:
Expand Down Expand Up @@ -448,4 +448,3 @@ def _safe_str(v: Any) -> str:
return '' if v is None else str(v)
except Exception:
return ''

1 change: 1 addition & 0 deletions nodes/src/nodes/agent_rocketride/IGlobal.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def beginGlobal(self) -> None:
config handling is needed here.
"""
from .rocketride_agent import RocketRideDriver

self.agent = RocketRideDriver(self)

def endGlobal(self) -> None:
Expand Down
30 changes: 18 additions & 12 deletions nodes/src/nodes/agent_rocketride/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
# Structural summary (_describe)
# ---------------------------------------------------------------------------


def _describe(value: Any, depth: int = 0) -> str:
"""Return a compact structural summary of *value* for LLM context.

Expand Down Expand Up @@ -118,9 +119,7 @@ def _describe(value: Any, depth: int = 0) -> str:
if isinstance(first, dict):
# Collect field names from up to 5 rows to handle sparse rows
# where early rows may be missing fields that appear later.
keys = list(dict.fromkeys(
k for row in value[:5] if isinstance(row, dict) for k in row
))
keys = list(dict.fromkeys(k for row in value[:5] if isinstance(row, dict) for k in row))
lines = [f'{n} items, fields: {keys}']
# Show first 2 rows as sample data so the LLM can see real values
for i, row in enumerate(value[:2]):
Expand Down Expand Up @@ -152,6 +151,7 @@ def _describe_dict(d: dict, depth: int) -> str:
# Template resolution
# ---------------------------------------------------------------------------


def _memory_get(key: str, host: AgentHost) -> Any:
"""Fetch a raw value from the memory store.

Expand Down Expand Up @@ -298,6 +298,7 @@ def resolve_answer_refs(answer: str, host: AgentHost) -> str:
# Wave result storage
# ---------------------------------------------------------------------------


def _auto_key(wave_name: str, idx: int) -> str:
"""Generate a memory key scoped to a wave and call index.

Expand Down Expand Up @@ -335,6 +336,7 @@ def _store_and_preview(tool: str, key: str, result: Any, host: AgentHost) -> Dic
# Wave executor
# ---------------------------------------------------------------------------


def _execute_wave_calls(
wave: List[Dict[str, Any]],
*,
Expand All @@ -356,10 +358,7 @@ def _execute_wave_calls(

# Tag each call with its auto-generated memory key before parallelism so
# the key assignment is deterministic and order-preserving.
tagged: List[Dict[str, Any]] = [
{**call, '_key': _auto_key(wave_name, i)}
for i, call in enumerate(wave)
]
tagged: List[Dict[str, Any]] = [{**call, '_key': _auto_key(wave_name, i)} for i, call in enumerate(wave)]

def _run_one(call: Dict[str, Any]) -> Dict[str, Any]:
"""Execute a single tool call and return a result dict."""
Expand Down Expand Up @@ -411,8 +410,11 @@ def _run_one(call: Dict[str, Any]) -> Dict[str, Any]:
total_items = len(value)
preview = json.dumps(value[:_PEEK_MAX_ARRAY_ITEMS], ensure_ascii=False)
return {
'tool': tool, 'key': key, 'path': path,
'preview': preview, 'truncated': True,
'tool': tool,
'key': key,
'path': path,
'preview': preview,
'truncated': True,
'returned_items': _PEEK_MAX_ARRAY_ITEMS,
'total_items': total_items,
}
Expand All @@ -426,10 +428,14 @@ def _run_one(call: Dict[str, Any]) -> Dict[str, Any]:
value = json.dumps(value, ensure_ascii=False, indent=2)
offset = int(args.get('offset', 0))
length = int(args.get('length', _PEEK_DEFAULT_LENGTH))
chunk = value[offset:offset + length]
chunk = value[offset : offset + length]
return {
'tool': tool, 'key': key, 'preview': chunk,
'offset': offset, 'length': len(chunk), 'total_chars': len(value),
'tool': tool,
'key': key,
'preview': chunk,
'offset': offset,
'length': len(chunk),
'total_chars': len(value),
}

# Regular tool — route through the host's tool pipeline which
Expand Down
1 change: 1 addition & 0 deletions nodes/src/nodes/agent_rocketride/rocketride_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
# Can be overridden via the ``max_waves`` node configuration field.
_DEFAULT_MAX_WAVES = 10


class RocketRideDriver(AgentBase):
"""
RocketRide Wave framework driver.
Expand Down
13 changes: 4 additions & 9 deletions nodes/src/nodes/anonymize/IInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,10 @@

class IInstance(IInstanceBase):
IGlobal: IGlobal # Reference to a global context providing recognizer functionality

# Default PII labels for zero-shot NER when no classifications provided
DEFAULT_PII_LABELS = [
'person', 'name', 'email', 'phone number', 'address',
'social security number', 'credit card number', 'date of birth',
'organization', 'company', 'location', 'ip address',
'bank account', 'passport number', 'driver license'
]

DEFAULT_PII_LABELS = ['person', 'name', 'email', 'phone number', 'address', 'social security number', 'credit card number', 'date of birth', 'organization', 'company', 'location', 'ip address', 'bank account', 'passport number', 'driver license']
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid mutable class-level defaults for DEFAULT_PII_LABELS.

Line 32 uses a class-level list, which is shared and mutable across all instances (RUF012). Use an immutable tuple to prevent accidental mutation.

Proposed fix
-    DEFAULT_PII_LABELS = ['person', 'name', 'email', 'phone number', 'address', 'social security number', 'credit card number', 'date of birth', 'organization', 'company', 'location', 'ip address', 'bank account', 'passport number', 'driver license']
+    DEFAULT_PII_LABELS = (
+        'person', 'name', 'email', 'phone number', 'address', 'social security number',
+        'credit card number', 'date of birth', 'organization', 'company', 'location',
+        'ip address', 'bank account', 'passport number', 'driver license',
+    )
As per coding guidelines, `nodes/**/*.py` should use Ruff for linting/formatting.
📝 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
DEFAULT_PII_LABELS = ['person', 'name', 'email', 'phone number', 'address', 'social security number', 'credit card number', 'date of birth', 'organization', 'company', 'location', 'ip address', 'bank account', 'passport number', 'driver license']
DEFAULT_PII_LABELS = (
'person', 'name', 'email', 'phone number', 'address', 'social security number',
'credit card number', 'date of birth', 'organization', 'company', 'location',
'ip address', 'bank account', 'passport number', 'driver license',
)
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 32-32: Mutable default value for class attribute

(RUF012)

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

In `@nodes/src/nodes/anonymize/IInstance.py` at line 32, DEFAULT_PII_LABELS is
defined as a mutable class-level list on IInstance which can be accidentally
mutated across instances; change it to an immutable tuple (e.g.,
DEFAULT_PII_LABELS = ('person', 'name', ...)) to prevent shared-state bugs and
update any uses expecting a list (convert to list locally if needed) and re-run
Ruff formatting/linting for the nodes package.


#
# Current object context properties
#
Expand All @@ -59,7 +54,7 @@ def closing(self):
if not self.has_classifications:
# No classifications received - anonymize with default PII labels
self.target_object_text = self.IGlobal.recognizer.process(self.target_object_text, self.DEFAULT_PII_LABELS)

# Resume the writeText lane
self.instance.writeText(self.target_object_text)

Expand Down
1 change: 1 addition & 0 deletions nodes/src/nodes/anonymize/anonymize.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# SOFTWARE.
# =============================================================================


def anonymize(text: str, matches, anonymize_char: str = '*') -> str:
"""Replace specified segments with a sequence of anonymization characters.

Expand Down
25 changes: 11 additions & 14 deletions nodes/src/nodes/anonymize/glinerRecognizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class GliNERRecognizer:
def __init__(self, provider: str, connConfig: Dict[str, Any], bag: Dict[str, Any]):
"""
Initialize the GLiNER Recognizer.

Uses ai.common.models.GLiNER which automatically routes to model server
if --modelserver flag is present, otherwise runs locally.
"""
Expand All @@ -50,7 +50,7 @@ def __init__(self, provider: str, connConfig: Dict[str, Any], bag: Dict[str, Any
enginePath = expand('%execPath%')
rule_file_path = os.path.join(enginePath, 'nucleuz', 'rulePack.dat')
self.ruleParser = RuleParser(rule_file_path)

# Use ai.common.models.GLiNER - auto-detects local vs model server mode
self.model = GLiNER(self.model_name)

Expand Down Expand Up @@ -182,34 +182,34 @@ def process_chunk(chunk_idx):
def process(self, text: str, labels: list, existing_matches: list = None) -> str:
"""
Core anonymization method - detects entities using GLiNER and masks them.

Args:
text: The text to anonymize
labels: Entity labels to detect
existing_matches: Optional list of (offset, length) tuples from classifications

Returns:
Anonymized text with detected entities replaced by anonymize_char
"""
if not text:
return text

# Run NER prediction
ner_results = self.predict(text, labels)
ner_matches = self.convert_ner_results_to_matches(ner_results)

debug(f'Anonymize: Detected {len(ner_results)} entities')

# Combine with existing matches (from classifications)
all_matches = list(existing_matches or []) + ner_matches

if not all_matches:
debug('Anonymize: No entities to mask')
return text

# Sort by offset and apply masking
all_matches_sorted = sorted(all_matches, key=lambda x: x[0])

return _anonymize(text, all_matches_sorted, self.anonymize_char)

def handleClassifications(self, classifications: dict, target_object_text: str, classificationPolicy: any, classificationRules: any):
Expand All @@ -234,9 +234,6 @@ def handleClassifications(self, classifications: dict, target_object_text: str,
labels = self.ruleParser.get_rules_names(unique_id_refs) + rules

# Extract existing matches from classifications (offset, length tuples)
existing_matches = list(
(m['offset'], m['length'])
for m in ((m.get('location', {}).get('inChars') or m) for m in text_matches)
)
existing_matches = list((m['offset'], m['length']) for m in ((m.get('location', {}).get('inChars') or m) for m in text_matches))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Replace the unnecessary generator with a direct list comprehension.

Line 237 materializes list((...) for ...), which triggers Ruff C400 and adds avoidable overhead.

Proposed fix
-        existing_matches = list((m['offset'], m['length']) for m in ((m.get('location', {}).get('inChars') or m) for m in text_matches))
+        existing_matches = [
+            (m['offset'], m['length'])
+            for m in ((m.get('location', {}).get('inChars') or m) for m in text_matches)
+        ]
As per coding guidelines, `nodes/**/*.py` should use Ruff for linting/formatting.
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 237-237: Unnecessary generator (rewrite as a list comprehension)

Rewrite as a list comprehension

(C400)

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

In `@nodes/src/nodes/anonymize/glinerRecognizer.py` at line 237, Replace the
nested generator expression used to build existing_matches with a direct list
comprehension to avoid Ruff C400 and extra overhead: change the current
expression that uses list((m['offset'], m['length']) for m in
((m.get('location', {}).get('inChars') or m) for m in text_matches)) to a
comprehension like [(m['offset'], m['length']) for tm in text_matches for m in
(tm.get('location', {}).get('inChars') or tm)] so the same elements are produced
without the intermediate generator; update the assignment to existing_matches
accordingly.


return self.process(target_object_text, labels, existing_matches)
5 changes: 1 addition & 4 deletions nodes/src/nodes/audio_transcribe/IGlobal.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ def transcribe(self, audio: Any) -> List[SimpleNamespace]:
)

segments = result.get('$segments') or []
return [
SimpleNamespace(text=s.get('text', ''), start=s.get('start', 0.0), end=s.get('end', 0.0))
for s in segments
]
return [SimpleNamespace(text=s.get('text', ''), start=s.get('start', 0.0), end=s.get('end', 0.0)) for s in segments]

def _audio_to_pcm_bytes(self, audio: Any) -> bytes:
"""Convert audio (bytes or float32 numpy) to PCM int16 bytes (16 kHz mono)."""
Expand Down
1 change: 0 additions & 1 deletion nodes/src/nodes/autopipe/IGlobal.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ def isSet(key: str):
providerStore, configStore = Config.getMultiProviderConfig('store', autopipeConfig)
pushLocal(getFilter('vector_1', providerStore, configStore))


pass

# Now, add all the filters we figured out
Expand Down
4 changes: 1 addition & 3 deletions nodes/src/nodes/chart_chartjs/IInstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ def invoke(self, param: Any) -> Any: # noqa: ANN401
if driver._llm_invoke is None:
llm_invoker = _make_llm_invoker(self.instance)
if llm_invoker is None:
raise RuntimeError(
'Chart generator requires an LLM node connected to the pipeline.'
)
raise RuntimeError('Chart generator requires an LLM node connected to the pipeline.')
driver.set_llm_invoker(llm_invoker)

return driver.handle_invoke(param)
Loading
Loading