Class: Brute::Pipeline

Inherits:
Object
  • Object
show all
Defined in:
lib/brute/pipeline.rb

Overview

Rack-style middleware pipeline for LLM calls.

Each middleware wraps the next, forming an onion model:

Tracing → Retry → DoomLoop → Reasoning → [LLM Call] → Reasoning → DoomLoop → Retry → Tracing

The innermost “app” is the actual LLM call. Each middleware can:

- Modify the env (context, params) BEFORE the call   (pre-processing)
- Modify or inspect the response AFTER the call       (post-processing)
- Short-circuit (return without calling inner app)
- Retry (call inner app multiple times)

## The env hash

{
  provider:          LLM::Provider,    # the LLM provider
  model:             String|nil,       # model override
  input:             <prompt/results>, # what to pass to LLM
  tools:             [Tool, ...],      # tool classes
  messages:          [LLM::Message],   # conversation history (Brute-owned)
  stream:            AgentStream|nil,  # streaming bridge
  params:            {},               # extra LLM call params
  metadata:          {},               # shared scratchpad for middleware state
  callbacks:         {},               # :on_content, :on_tool_call_start, :on_tool_result
  tool_results:      Array|nil,        # tool results from previous iteration
  streaming:         Boolean,          # whether streaming is active
  should_exit:       Hash|nil,         # exit signal from middleware
  pending_functions: [LLM::Function],  # tool calls from last LLM response
}

## The response

The return value of call(env) is the LLM::Message from context.talk().

## Building a pipeline

pipeline = Brute::Pipeline.new do
  use Brute::Middleware::Tracing, logger: logger
  use Brute::Middleware::Retry, max_attempts: 3
  use Brute::Middleware::SessionPersistence, session: session
  run Brute::Middleware::LLMCall.new
end

response = pipeline.call(env)

Instance Method Summary collapse

Constructor Details

#initialize(&block) ⇒ Pipeline

Returns a new instance of Pipeline.



53
54
55
56
57
# File 'lib/brute/pipeline.rb', line 53

def initialize(&block)
  @middlewares = []
  @app = nil
  instance_eval(&block) if block
end

Instance Method Details

#buildObject

Build the chain without calling it. Useful for inspection or caching.



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/brute/pipeline.rb', line 78

def build
  raise "Pipeline has no terminal app — call `run` first" unless @app

  @middlewares.reverse.inject(@app) do |inner, (klass, args, kwargs, block)|
    if block
      klass.new(inner, *args, **kwargs, &block)
    else
      klass.new(inner, *args, **kwargs)
    end
  end
end

#call(env) ⇒ Object

Build the full middleware chain and call it.



73
74
75
# File 'lib/brute/pipeline.rb', line 73

def call(env)
  build.call(env)
end

#run(app) ⇒ Object

Set the terminal app (innermost handler).



67
68
69
70
# File 'lib/brute/pipeline.rb', line 67

def run(app)
  @app = app
  self
end

#use(klass, *args, **kwargs, &block) ⇒ Object

Register a middleware class. The class must implement ‘initialize(app, *args, **kwargs)` and `call(env)`.



61
62
63
64
# File 'lib/brute/pipeline.rb', line 61

def use(klass, *args, **kwargs, &block)
  @middlewares << [klass, args, kwargs, block]
  self
end