Skip to content

CLI Module API

fuzzy_infer.cli

FuzzyInfer CLI - Unix-philosophy compliant interface.

Each command does one thing well. Reads from stdin, writes to stdout. JSONL is the universal format.

cli

cli(ctx, verbose)

fuzzy - composable fuzzy inference for Unix.

Source code in fuzzy_infer/cli.py
@click.group()
@click.option('--verbose', '-v', is_flag=True, help='Verbose stderr output')
@click.pass_context
def cli(ctx, verbose):
    """fuzzy - composable fuzzy inference for Unix."""
    ctx.ensure_object(dict)
    ctx.obj['verbose'] = verbose
    if verbose:
        logging.getLogger().setLevel(logging.INFO)

fact

fact(predicate, args, degree)

Output a fact as JSONL.

Source code in fuzzy_infer/cli.py
@cli.command()
@click.argument('predicate')
@click.argument('args', nargs=-1)
@click.option('--degree', '-d', type=float, default=1.0)
def fact(predicate, args, degree):
    """Output a fact as JSONL."""
    item = {
        'type': 'fact',
        'pred': predicate,
        'args': list(args),
        'deg': max(0.0, min(1.0, degree))
    }
    print(json.dumps(item, separators=(',', ':')))

infer

infer(max_iter, incremental, kb)

Run inference on KB (from file or stdin), output to stdout.

Source code in fuzzy_infer/cli.py
@cli.command()
@click.option('--max-iter', '-m', default=100)
@click.option('--incremental', '-i', is_flag=True, help='Only output new facts')
@click.option('--kb', type=click.Path(exists=True), help='KB file path (default: stdin)')
def infer(max_iter, incremental, kb):
    """Run inference on KB (from file or stdin), output to stdout."""
    kb_engine = FuzzyInfer()
    initial_facts = set()

    # Read from file or stdin and load KB
    if kb:
        with open(kb) as f:
            stream = JSONLStream(f)
            for obj in stream.parse():
                if isinstance(obj, Fact):
                    kb_engine.add_fact(obj)
                    initial_facts.add(obj)
                elif isinstance(obj, Rule):
                    kb_engine.add_rule(obj)
    else:
        stream = JSONLStream(sys.stdin)
        for obj in stream.parse():
            if isinstance(obj, Fact):
                kb_engine.add_fact(obj)
                initial_facts.add(obj)
            elif isinstance(obj, Rule):
                kb_engine.add_rule(obj)

    # Run inference
    kb_engine.run(max_iterations=max_iter)

    # Output results
    output_stream = JSONLStream()

    if incremental:
        # Only new facts
        new_facts = []
        for fact in kb_engine.get_facts():
            if fact not in initial_facts:
                item = {
                    'type': 'fact',
                    'pred': fact.predicate,
                    'args': list(fact.args),
                    'deg': fact.degree,
                    '_inferred': True
                }
                new_facts.append(item)
        output_stream.write(iter(new_facts))
    else:
        # All facts
        all_items = []
        for fact in kb_engine.get_facts():
            item = {
                'type': 'fact',
                'pred': fact.predicate,
                'args': list(fact.args),
                'deg': fact.degree
            }
            if fact not in initial_facts:
                item['_inferred'] = True
            all_items.append(item)

        for rule in kb_engine.get_rules():
            # Convert conditions and actions to dicts
            conditions = [c.to_dict() for c in rule.conditions]
            actions = [a.to_dict() for a in rule.actions]

            item = {
                'type': 'rule',
                'name': rule.name,
                'cond': conditions,
                'actions': actions,
                'priority': rule.priority
            }
            all_items.append(item)

        output_stream.write(iter(all_items))

query

query(pattern, min_degree, kb)

Query facts matching pattern from KB (file or stdin).

Source code in fuzzy_infer/cli.py
@cli.command()
@click.argument('pattern')
@click.option('--min-degree', '-m', type=float, help='Minimum degree')
@click.option('--kb', type=click.Path(exists=True), help='KB file path (default: stdin)')
def query(pattern, min_degree, kb):
    """Query facts matching pattern from KB (file or stdin)."""
    kb_engine = FuzzyInfer()

    # Read from file or stdin and load KB
    if kb:
        with open(kb) as f:
            stream = JSONLStream(f)
            for obj in stream.parse():
                if isinstance(obj, Fact):
                    kb_engine.add_fact(obj)
                elif isinstance(obj, Rule):
                    kb_engine.add_rule(obj)
    else:
        stream = JSONLStream(sys.stdin)
        for obj in stream.parse():
            if isinstance(obj, Fact):
                kb_engine.add_fact(obj)
            elif isinstance(obj, Rule):
                kb_engine.add_rule(obj)

    # Parse pattern
    parts = pattern.split()
    predicate = parts[0] if parts else None
    args = parts[1:] if len(parts) > 1 else None

    # Query
    results = kb_engine.query(predicate, args)

    # Filter by degree
    if min_degree is not None:
        results = [f for f in results if f.degree >= min_degree]

    # Output
    output_stream = JSONLStream()
    items = []
    for fact in results:
        item = {
            'type': 'fact',
            'pred': fact.predicate,
            'args': list(fact.args),
            'deg': fact.degree
        }
        items.append(item)

    output_stream.write(iter(items))

filter

filter(predicate, min_degree, max_degree)

Filter facts from stdin based on criteria.

Source code in fuzzy_infer/cli.py
@cli.command()
@click.option('--predicate', '-p', help='Filter by predicate prefix')
@click.option('--min-degree', type=float, help='Minimum degree')
@click.option('--max-degree', type=float, help='Maximum degree')
def filter(predicate, min_degree, max_degree):
    """Filter facts from stdin based on criteria."""
    stream = JSONLStream(sys.stdin)
    output = JSONLStream()

    filtered = []
    for item in stream.read():
        if item.get('type') != 'fact':
            filtered.append(item)  # Pass through non-facts
            continue

        # Apply filters
        if predicate and not item.get('pred', '').startswith(predicate):
            continue

        deg = item.get('deg', 1.0)
        if min_degree is not None and deg < min_degree:
            continue
        if max_degree is not None and deg > max_degree:
            continue

        filtered.append(item)

    output.write(iter(filtered))

diff

diff(file1, file2)

Show difference between two KB files.

Source code in fuzzy_infer/cli.py
@cli.command()
@click.argument('file1', type=click.Path(exists=True))
@click.argument('file2', type=click.Path(exists=True))
def diff(file1, file2):
    """Show difference between two KB files."""
    kb1_facts = set()
    kb2_facts = set()

    # Load first KB
    with open(file1) as f:
        stream = JSONLStream(f)
        for item in stream.read():
            if item.get('type') == 'fact':
                # Create hashable representation
                key = (item['pred'], tuple(item.get('args', [])), item.get('deg', 1.0))
                kb1_facts.add(key)

    # Load second KB
    with open(file2) as f:
        stream = JSONLStream(f)
        for item in stream.read():
            if item.get('type') == 'fact':
                key = (item['pred'], tuple(item.get('args', [])), item.get('deg', 1.0))
                kb2_facts.add(key)

    # Output differences
    output = JSONLStream()

    # Facts only in kb1
    removed = []
    for pred, args, deg in kb1_facts - kb2_facts:
        item = {
            'type': 'fact',
            'pred': pred,
            'args': list(args),
            'deg': deg,
            '_diff': 'removed'
        }
        removed.append(item)

    # Facts only in kb2
    added = []
    for pred, args, deg in kb2_facts - kb1_facts:
        item = {
            'type': 'fact',
            'pred': pred,
            'args': list(args),
            'deg': deg,
            '_diff': 'added'
        }
        added.append(item)

    output.write(iter(removed + added))

validate

validate(strict)

Validate JSONL KB from stdin.

Source code in fuzzy_infer/cli.py
@cli.command()
@click.option('--strict', is_flag=True, help='Strict validation')
def validate(strict):
    """Validate JSONL KB from stdin."""
    stream = JSONLStream(sys.stdin)
    output = JSONLStream()

    valid_items = []
    errors = []
    line_num = 0

    for item in stream.read():
        line_num += 1
        error = None

        # Validate structure
        if 'type' not in item:
            if strict:
                error = f"Line {line_num}: Missing type"
            else:
                item['type'] = 'fact'

        if item.get('type') == 'fact':
            if 'pred' not in item:
                error = f"Line {line_num}: Fact missing predicate"
            elif 'args' not in item:
                error = f"Line {line_num}: Fact missing args"
            else:
                deg = item.get('deg', 1.0)
                if not (0 <= deg <= 1):
                    error = f"Line {line_num}: Invalid degree {deg}"

        elif item.get('type') == 'rule':
            if 'cond' not in item:
                error = f"Line {line_num}: Rule missing conditions"
            elif 'actions' not in item:
                error = f"Line {line_num}: Rule missing actions"

        if error:
            errors.append(error)
            if strict:
                continue

        valid_items.append(item)

    # Output valid items
    output.write(iter(valid_items))

    # Report errors to stderr
    if errors:
        for err in errors:
            logging.error(err)
        if strict:
            sys.exit(1)

    if sys.stderr.isatty():
        logging.info(f"Validated {line_num} items, {len(errors)} errors")

Overview

The fuzzy_infer.cli module provides command-line interface functionality for fuzzy inference operations.

Commands

run

Execute fuzzy inference on JSONL input.

fuzzy-infer run [OPTIONS] [RULES_FILE] [FACTS_FILE]

Arguments: - RULES_FILE: Path to JSONL file containing rules (optional) - FACTS_FILE: Path to JSONL file containing facts (optional, defaults to stdin)

Options: - --max-iterations N: Maximum inference iterations (default: 100) - --output, -o FILE: Write output to file instead of stdout - --format FORMAT: Output format: jsonl (default), json - --verbose, -v: Enable verbose logging - --help: Show help message

Examples:

# Run from stdin
cat facts.jsonl | fuzzy-infer run rules.jsonl

# Run with both files
fuzzy-infer run rules.jsonl facts.jsonl

# Increase iteration limit
fuzzy-infer run --max-iterations 200 rules.jsonl facts.jsonl

# Save output to file
fuzzy-infer run rules.jsonl facts.jsonl -o results.jsonl

query

Query facts matching a predicate.

fuzzy-infer query [OPTIONS] PREDICATE [ARGS...]

Arguments: - PREDICATE: Predicate name to query - ARGS: Optional argument values to match

Options: - --min-degree N: Minimum degree threshold (0.0 to 1.0) - --max-degree N: Maximum degree threshold (0.0 to 1.0) - --format FORMAT: Output format: jsonl (default), json, text - --limit N: Limit number of results - --help: Show help message

Examples:

# Query all mammals
fuzzy-infer query "is-mammal" < knowledge.jsonl

# Query specific animal
fuzzy-infer query "is-mammal" "dog" < knowledge.jsonl

# Filter by degree
fuzzy-infer query "is-bird" --min-degree 0.8 < facts.jsonl

# Limit results
fuzzy-infer query "species" --limit 10 < large_kb.jsonl

# JSON output
fuzzy-infer query "temperature" --format json < sensors.jsonl

fact

Create a fact from command-line arguments.

fuzzy-infer fact [OPTIONS] PREDICATE ARGS... [DEGREE]

Arguments: - PREDICATE: Predicate name - ARGS: One or more argument values - DEGREE: Degree value (optional, defaults to 1.0)

Options: - --format FORMAT: Output format: jsonl (default), json - --help: Show help message

Examples:

# Create fact with default degree
fuzzy-infer fact "is-bird" "robin"
# {"type":"fact","pred":"is-bird","args":["robin"],"deg":1.0}

# Create with specific degree
fuzzy-infer fact "is-bird" "robin" 0.9
# {"type":"fact","pred":"is-bird","args":["robin"],"deg":0.9}

# Multiple arguments
fuzzy-infer fact "temperature" "room-1" "hot" 0.75

# Pipe to inference
fuzzy-infer fact "is-bird" "robin" 0.9 | fuzzy-infer run rules.jsonl

facts

List all facts from input.

fuzzy-infer facts [OPTIONS]

Options: - --predicate PRED: Filter by predicate name - --format FORMAT: Output format: jsonl (default), json, text - --sort-by FIELD: Sort by field: predicate, degree - --help: Show help message

Examples:

# List all facts
fuzzy-infer facts < knowledge.jsonl

# Filter by predicate
fuzzy-infer facts --predicate "is-mammal" < knowledge.jsonl

# JSON array output
fuzzy-infer facts --format json < knowledge.jsonl

# Sort by degree
fuzzy-infer facts --sort-by degree < knowledge.jsonl

# Text format
fuzzy-infer facts --format text < knowledge.jsonl
# is-bird(robin) = 0.9
# is-bird(eagle) = 1.0

validate

Validate JSONL format and content.

fuzzy-infer validate [OPTIONS] [FILE]

Arguments: - FILE: Path to JSONL file (optional, defaults to stdin)

Options: - --strict: Enable strict validation (check degrees, rules, etc.) - --show-errors: Display detailed error messages - --help: Show help message

Examples:

# Validate file
fuzzy-infer validate knowledge.jsonl

# Validate from stdin
cat facts.jsonl | fuzzy-infer validate

# Strict validation
fuzzy-infer validate --strict --show-errors knowledge.jsonl

# Use in pipeline
fuzzy-infer validate facts.jsonl && fuzzy-infer run rules.jsonl facts.jsonl

Environment Variables

Configure CLI behavior with environment variables:

FUZZY_INFER_MAX_ITERATIONS

Maximum inference iterations (default: 100).

export FUZZY_INFER_MAX_ITERATIONS=200
fuzzy-infer run rules.jsonl facts.jsonl

FUZZY_INFER_LOG_LEVEL

Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL.

export FUZZY_INFER_LOG_LEVEL=DEBUG
fuzzy-infer run rules.jsonl facts.jsonl

FUZZY_INFER_OUTPUT_FORMAT

Default output format: jsonl, json, text.

export FUZZY_INFER_OUTPUT_FORMAT=json
fuzzy-infer facts < knowledge.jsonl

Configuration File

Create ~/.fuzzy-infer.json for persistent settings:

{
  "max_iterations": 100,
  "default_degree": 1.0,
  "output_format": "jsonl",
  "log_level": "INFO",
  "strict_validation": false
}

Configuration precedence (highest to lowest): 1. Command-line options 2. Environment variables 3. Configuration file 4. Default values

Exit Codes

  • 0: Success
  • 1: General error
  • 2: Invalid input
  • 3: Validation error
  • 4: Inference error

Logging

Control logging with --verbose flag or environment variable:

# Verbose output
fuzzy-infer --verbose run rules.jsonl facts.jsonl

# Debug logging
FUZZY_INFER_LOG_LEVEL=DEBUG fuzzy-infer run rules.jsonl facts.jsonl

Log format:

[2024-01-15 10:30:45] INFO: Loading rules from rules.jsonl
[2024-01-15 10:30:45] INFO: Loaded 5 rules
[2024-01-15 10:30:45] INFO: Running inference
[2024-01-15 10:30:46] INFO: Inference complete in 3 iterations

Complete Examples

Basic Inference Pipeline

#!/bin/bash
# inference_pipeline.sh

set -euo pipefail

# Configuration
RULES="animal_rules.jsonl"
FACTS="observations.jsonl"
OUTPUT="classifications.jsonl"

# Validate inputs
echo "Validating inputs..."
fuzzy-infer validate "$RULES" || exit 1
fuzzy-infer validate "$FACTS" || exit 1

# Run inference
echo "Running inference..."
fuzzy-infer run "$RULES" "$FACTS" > "$OUTPUT"

# Query results
echo "Querying mammals..."
fuzzy-infer query "is-mammal" < "$OUTPUT" > mammals.jsonl

echo "Querying birds..."
fuzzy-infer query "is-bird" < "$OUTPUT" > birds.jsonl

# Generate summary
echo "Summary:"
echo "  Total facts: $(fuzzy-infer facts < "$OUTPUT" | wc -l)"
echo "  Mammals: $(wc -l < mammals.jsonl)"
echo "  Birds: $(wc -l < birds.jsonl)"

Streaming Sensor Data

#!/bin/bash
# sensor_monitor.sh

# Monitor sensor stream and trigger alerts
tail -f /var/log/sensors.log | \
  ./parse_sensors_to_jsonl.py | \
  fuzzy-infer run sensor_rules.jsonl | \
  fuzzy-infer query "alert" --min-degree 0.8 | \
  while IFS= read -r alert; do
    sensor=$(echo "$alert" | jq -r '.args[0]')
    confidence=$(echo "$alert" | jq -r '.deg')
    echo "[$(date)] ALERT: Sensor $sensor requires attention ($confidence)"
    ./send_notification.sh "$sensor" "$confidence"
  done

Batch Classification

#!/bin/bash
# batch_classify.sh

# Classify all documents in directory
for doc in documents/*.txt; do
  echo "Processing: $doc"

  # Extract features → Classify → Save results
  ./extract_features.py "$doc" | \
    fuzzy-infer run classification_rules.jsonl | \
    fuzzy-infer query "category" | \
    jq --arg file "$doc" \
      '{file: $file, category: .args[1], confidence: .deg}' \
      >> classification_results.jsonl
done

# Aggregate results
echo "Classification complete. Generating summary..."
jq -s 'group_by(.category) | map({category: .[0].category, count: length})' \
  classification_results.jsonl > summary.json

cat summary.json

Integration Examples

With Python

import subprocess
import json
from pathlib import Path

def run_inference(rules_file, facts_file):
    """Run fuzzy-infer CLI from Python."""
    result = subprocess.run(
        ["fuzzy-infer", "run", rules_file, facts_file],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout

# Run inference
output = run_inference("rules.jsonl", "facts.jsonl")

# Parse results
for line in output.strip().split('\n'):
    item = json.loads(line)
    if item['type'] == 'fact':
        print(f"{item['pred']}({item['args']}) = {item['deg']}")

With Shell Script

#!/bin/bash
# run_inference.sh

# Function to run inference with error handling
run_inference() {
    local rules=$1
    local facts=$2
    local output=$3

    if ! fuzzy-infer validate "$rules"; then
        echo "Error: Invalid rules file" >&2
        return 1
    fi

    if ! fuzzy-infer validate "$facts"; then
        echo "Error: Invalid facts file" >&2
        return 1
    fi

    if ! fuzzy-infer run "$rules" "$facts" > "$output" 2>errors.log; then
        echo "Error: Inference failed" >&2
        cat errors.log >&2
        return 1
    fi

    return 0
}

# Usage
run_inference rules.jsonl facts.jsonl results.jsonl

Best Practices

1. Always Validate Inputs

fuzzy-infer validate input.jsonl || exit 1
fuzzy-infer run rules.jsonl input.jsonl

2. Use Pipelines for Complex Workflows

cat data.jsonl | \
  fuzzy-infer run rules1.jsonl | \
  fuzzy-infer run rules2.jsonl | \
  fuzzy-infer query "result"

3. Handle Errors Gracefully

if ! fuzzy-infer run rules.jsonl facts.jsonl > results.jsonl 2>errors.log; then
    echo "Inference failed:"
    cat errors.log
    exit 1
fi

4. Log Important Operations

echo "[$(date)] Starting inference" >> pipeline.log
fuzzy-infer run rules.jsonl facts.jsonl 2>> pipeline.log
echo "[$(date)] Inference complete" >> pipeline.log

5. Use Configuration Files

Create ~/.fuzzy-infer.json to set defaults:

{
  "max_iterations": 200,
  "log_level": "INFO"
}

Summary

  • CLI provides Unix-style interface to FuzzyInfer
  • Commands: run, query, fact, facts, validate
  • Configuration via environment variables or config file
  • JSONL format for streaming and composability
  • Integration with shell scripts and other tools
  • Comprehensive error handling and validation