Skip to content

Index

agent_cover.instrumentation.raw_strings

Raw String Heuristics.

This module scans your codebase for simple string variables acting as prompts (e.g., f-strings).

🔗 Architectural Relationships

Since raw strings are not objects, they cannot be patched directly. This module acts as a Pattern Generator.

  • Feeds: AgentRegistry (Registers the "Definition" with a generated Regex).
  • Enables: CoverageCallbackHandler (The handler uses the regexes generated here to match runtime text).
  • Configured by: CLI flags (--prompt-prefixes, --prompt-suffixes).

⚠️ Limitations

Global Scope Only: Currently, this scanner uses runtime introspection (vars(module)). Therefore, it only detects global module-level constants. Variables defined inside functions (local scope) are invisible to the scanner and will not be tracked.

⚙️ How it works

  1. Static Scan: It iterates through loaded modules looking for variables like PROMPT_SALES.
  2. Regex Generation: It converts the string content into a robust regex (handling whitespace and f-string placeholders like {user}).
  3. Runtime Match: When the LLM is called, the Callback Handler checks if the input text matches these regexes.

Usage

from agent_cover.instrumentation.raw_strings import (
    set_custom_prefixes,
    scan_raw_string_prompts
)

# 1. Configure custom naming conventions
set_custom_prefixes(["BOT_SAY_", "USER_ASK_"])

# 2. Run the scan
scan_raw_string_prompts()

Customizing Raw String Scanning

If you don't use PromptTemplate objects but store prompts in global string variables, you can tell AgentCover how to find them.

By default, we look for variables starting with PROMPT_ or ending with _TEMPLATE.

Via Python SDK

You can add your own prefixes/suffixes programmatically:

from agent_cover.instrumentation.raw_strings import set_custom_prefixes

set_custom_prefixes(["MY_BOT_MSG_", "AI_INSTRUCTION_"])
# Now variables like MY_BOT_MSG_WELCOME = "..." will be tracked.
Via Pytest CLI

Alternatively, if you are using the pytest plugin, you can configure these without changing code using CLI flags:

  • --prompt-prefixes="MY_BOT_MSG_,AI_INSTRUCTION_"
  • --prompt-suffixes="_TEXT,_MSG"

Functions

scan_raw_string_prompts(registry=None, root_path=None, module_iterator=None, source_reader=None)

Scans for raw string prompts in Python modules and registers them.

It iterates through all loaded modules in sys.modules that reside within root_path. For each string variable matching the configured prefixes/suffixes, it: 1. Calculates a regex pattern for runtime matching. 2. Registers it in the AgentRegistry.

Parameters:

Name Type Description Default
registry Optional[AgentRegistry]

An optional AgentRegistry instance. If not provided, it defaults to the global registry.

None
root_path Optional[str]

An optional root path to start the scan from. If not provided, it defaults to the current working directory.

None
module_iterator Optional[Callable[[], Dict[str, Any]]]

An optional callable for iterating through loaded modules. Defaults to _default_module_iterator.

None
source_reader Optional[Callable[[str], List[str]]]

An optional callable for reading source file contents. Defaults to _default_source_reader.

None
Source code in src/agent_cover/instrumentation/raw_strings/scanner.py
def scan_raw_string_prompts(
    registry: Optional[AgentRegistry] = None,
    root_path: Optional[str] = None,
    module_iterator: Optional[Callable[[], Dict[str, Any]]] = None,
    source_reader: Optional[Callable[[str], List[str]]] = None,
) -> None:
    """Scans for raw string prompts in Python modules and registers them.

    It iterates through all loaded modules in `sys.modules` that reside within
    `root_path`. For each string variable matching the configured prefixes/suffixes,
    it:
    1.  Calculates a regex pattern for runtime matching.
    2.  Registers it in the [`AgentRegistry`][agent_cover.registry.AgentRegistry].

    Args:
        registry: An optional AgentRegistry instance. If not provided, it
            defaults to the global registry.
        root_path: An optional root path to start the scan from. If not
            provided, it defaults to the current working directory.
        module_iterator: An optional callable for iterating through
            loaded modules.  Defaults to _default_module_iterator.
        source_reader: An optional callable for reading source file
            contents.  Defaults to _default_source_reader.
    """
    if registry is None:
        registry = get_registry()
    if root_path is None:
        root_path = os.getcwd()  # Default Prod
    if module_iterator is None:
        module_iterator = _default_module_iterator
    if source_reader is None:
        source_reader = _default_source_reader

    active_prefixes = tuple(DEFAULT_PREFIXES + _custom_prefixes)
    active_suffixes = tuple(DEFAULT_SUFFIXES + _custom_suffixes)

    modules_snapshot = module_iterator()

    for mod_name, mod in modules_snapshot.items():
        if not hasattr(mod, "__file__") or not mod.__file__:
            continue
        mod_file = os.path.abspath(mod.__file__)

        if not mod_file.startswith(root_path) or "site-packages" in mod_file:
            continue

        file_lines = None

        for name, val in list(vars(mod).items()):
            if not isinstance(val, str):
                continue

            match_prefix = name.startswith(active_prefixes)
            match_suffix = name.endswith(active_suffixes)

            if not (match_prefix or match_suffix):
                continue

            if len(val) < 10:
                continue

            if file_lines is None:
                file_lines = source_reader(mod_file)

            line_num = _find_variable_line_number(name, file_lines)
            if line_num == 0:
                continue

            clean_content = val.strip()
            regex_pattern = _create_robust_regex(clean_content)

            raw_id = f"RAW:{mod_file}::{name}"
            canonical_id = registry.get_canonical_id(clean_content, raw_id)

            if canonical_id not in registry.definitions:
                logger.debug(
                    f"[AgentCover][SCAN] Found {name} at line {line_num} in {mod_file}"
                )
                registry.register_definition(
                    key=canonical_id,
                    kind="PROMPT",
                    metadata={
                        "class": "StringConstant",
                        "preview": clean_content[:40],
                        "raw_content": clean_content,
                        "regex_pattern": regex_pattern,
                        "line_number": line_num,
                    },
                )

set_custom_prefixes(prefixes)

Sets custom prefixes for identifying prompt variables.

Parameters:

Name Type Description Default
prefixes list[str]

A list of string prefixes.

required
Source code in src/agent_cover/instrumentation/raw_strings/scanner.py
def set_custom_prefixes(prefixes: list[str]) -> None:
    """Sets custom prefixes for identifying prompt variables.

    Args:
        prefixes: A list of string prefixes.
    """
    global _custom_prefixes
    _custom_prefixes = [p.strip() for p in prefixes if p.strip()]

set_custom_suffixes(suffixes)

Sets custom suffixes for identifying prompt variables.

Parameters:

Name Type Description Default
suffixes list[str]

A list of string suffixes.

required
Source code in src/agent_cover/instrumentation/raw_strings/scanner.py
def set_custom_suffixes(suffixes: list[str]) -> None:
    """Sets custom suffixes for identifying prompt variables.

    Args:
        suffixes: A list of string suffixes.
    """
    global _custom_suffixes
    _custom_suffixes = [s.strip() for s in suffixes if s.strip()]