Expected Signature for a Custom SkillSpector Analyzer: Implementation Guide

A custom SkillSpector analyzer must inherit from LLMAnalyzerBase and implement the constructor signature def __init__(self, base_prompt: str, model: str) -> None, calling super().__init__(base_prompt, model) to initialize the LLM backend and token budget.

The NVIDIA SkillSpector framework provides a pluggable architecture for LLM-powered code analysis. All analyzers in the system extend a common base class that standardizes how prompts are built and how model responses are parsed. Understanding the expected signature and extension points ensures your custom analyzer integrates seamlessly with SkillSpector's graph-execution engine.

Base Class Constructor Signature

Every custom analyzer subclasses LLMAnalyzerBase defined in src/skillspector/llm_analyzer_base.py. The constructor at lines 39-42 expects exactly two parameters:

def __init__(self, base_prompt: str, model: str) -> None

base_prompt is the static system prompt that describes analysis criteria to the LLM.
model is the string identifier for the LLM (e.g., "gpt-4o", "claude-3-opus-20240229") that skillspector.llm_utils.get_chat_model resolves.

When invoked, the parent constructor:

  • Stores both parameters as instance attributes
  • Calculates the model-specific input-token budget
  • Initializes the underlying LangChain chat model

Your custom analyzer must invoke super().__init__(base_prompt, model) to ensure these initialization steps execute.

Optional Extension Points

Beyond the required constructor, the base class exposes three primary hooks for customization:

Customizing Response Schemas

The class attribute response_schema defines the Pydantic model used to structure the LLM's output. By default, this is set to LLMAnalysisResult (lines 22-24). Override this attribute to enforce a different JSON structure:

from pydantic import BaseModel, Field

class SecurityFinding(BaseModel):
    severity: str
    line_number: int
    description: str

class SecurityAnalyzer(LLMAnalyzerBase):
    response_schema = SecurityFinding

Overriding Prompt Construction

The build_prompt(self, batch, **kwargs) method (documented in the class docstring at lines 6-13) assembles the final prompt sent to the LLM. Override this to customize how file chunks or metadata are injected into the base_prompt:

def build_prompt(self, batch, **kwargs):
    # Custom logic to format batch data

    custom_context = f"Analyzing {len(batch)} files..."
    return f"{self.base_prompt}\n{custom_context}\n{batch.content}"

Parsing LLM Responses

The parse_response(self, response, batch) method (lines 30-33) converts the structured LLM output into Finding objects (defined in src/skillspector/models.py). Override this when using a custom response_schema or when you need non-standard extraction logic:

def parse_response(self, response, batch):
    # response is an instance of self.response_schema

    findings = []
    for item in response.issues:
        findings.append(Finding(
            message=item.description,
            line=item.line_number
        ))
    return findings

Complete Implementation Examples

Minimal Custom Analyzer

The following skeleton demonstrates the minimal required implementation using the default schema:

from skillspector.llm_analyzer_base import LLMAnalyzerBase

class SimpleStaticAnalyzer(LLMAnalyzerBase):
    def __init__(self, model: str):
        prompt = (
            "Detect any use of unsafe system calls (e.g., exec, system, popen) "
            "and report them with line numbers."
        )
        super().__init__(base_prompt=prompt, model=model)

Advanced Analyzer with Custom Schema

This example implements a custom response schema and overrides the parser:

from pydantic import BaseModel, Field
from skillspector.llm_analyzer_base import LLMAnalyzerBase, LLMAnalysisResult
from skillspector.models import Finding

class MyResult(BaseModel):
    issues: list[dict] = Field(default_factory=list)

class MyAdvancedAnalyzer(LLMAnalyzerBase):
    response_schema = MyResult

    def __init__(self, model: str):
        super().__init__(base_prompt="Find all TODO comments.", model=model)

    def parse_response(self, response, batch):
        findings = []
        for item in response.issues:
            findings.append(
                Finding(
                    message=item.get("text", "TODO found"),
                    file_path=batch.file_path,
                    line=item.get("line", 0)
                )
            )
        return findings

Integration with the Execution Engine

Once implemented, your custom analyzer plugs directly into SkillSpector's batch processing system. The base class provides get_batches() to prepare file chunks and both synchronous and asynchronous execution methods:


# Synchronous execution

analyzer = SimpleStaticAnalyzer(model="gpt-4o")
batches = analyzer.get_batches(
    file_paths=["src/my_module.py"],
    file_cache={"src/my_module.py": open("src/my_module.py").read()},
)
results = analyzer.run_batches(batches)

# Asynchronous execution

import asyncio

async def run():
    analyzer = SimpleStaticAnalyzer(model="gpt-4o")
    batches = analyzer.get_batches(
        file_paths=["src/my_module.py"],
        file_cache={"src/my_module.py": open("src/my_module.py").read()},
    )
    results = await analyzer.arun_batches(batches)
    return results

asyncio.run(run())

Summary

  • Custom SkillSpector analyzers must inherit from LLMAnalyzerBase in src/skillspector/llm_analyzer_base.py and implement the signature __init__(self, base_prompt: str, model: str).
  • Initialization requires calling super().__init__(base_prompt, model) to configure the LangChain backend and token budgets.
  • Customization happens through three optional overrides: response_schema (class attribute), build_prompt(), and parse_response().
  • Execution uses inherited methods run_batches() or arun_batches() after preparing data with get_batches().

Frequently Asked Questions

What happens if I don't call the parent __init__ in my custom analyzer?

The analyzer will fail at runtime because it won't have the _chat_model attribute or the _token_budget calculated. According to the source code in src/skillspector/llm_analyzer_base.py lines 39-42, the parent constructor initializes the LangChain model via get_chat_model() and determines the maximum tokens available for the prompt. Without these, build_prompt() and run_batches() will raise AttributeError exceptions.

Can I change the constructor signature to accept additional parameters?

Yes, but you must preserve the base_prompt and model parameters and pass them to super().__init__(). You can add custom parameters like temperature or api_key before the super() call, provided you handle the storage of those values yourself. The execution engine only checks that the instance is an LLMAnalyzerBase subclass; it does not inspect your specific constructor signature.

Where is the Finding class defined that parse_response should return?

The Finding dataclass is defined in src/skillspector/models.py. This is the standard return type expected by SkillSpector's reporting nodes. When overriding parse_response, ensure your method returns a list of Finding objects (or instances of a compatible dataclass) so that downstream nodes in the analysis graph can process the results correctly.

Does the model parameter accept any string, or only specific values?

The model string must be resolvable by skillspector.llm_utils.get_chat_model. This utility function maps common identifiers like "gpt-4o" or "claude-3-opus" to their respective LangChain chat model classes. If you provide an unsupported identifier, the parent constructor will raise a ValueError during initialization when attempting to instantiate the chat model.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →