Class: DSPy::LM

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/dspy/lm.rb,
lib/dspy/lm/usage.rb,
lib/dspy/lm/errors.rb,
lib/dspy/lm/adapter.rb,
lib/dspy/lm/message.rb,
lib/dspy/lm/response.rb,
lib/dspy/lm/cache_manager.rb,
lib/dspy/lm/retry_handler.rb,
lib/dspy/lm/vision_models.rb,
lib/dspy/lm/adapter_factory.rb,
lib/dspy/lm/message_builder.rb,
lib/dspy/lm/strategy_selector.rb,
lib/dspy/lm/adapters/gemini_adapter.rb,
lib/dspy/lm/adapters/ollama_adapter.rb,
lib/dspy/lm/adapters/openai_adapter.rb,
lib/dspy/lm/strategies/base_strategy.rb,
lib/dspy/lm/adapters/anthropic_adapter.rb,
lib/dspy/lm/structured_output_strategy.rb,
lib/dspy/lm/adapters/openai/schema_converter.rb,
lib/dspy/lm/strategies/anthropic_tool_use_strategy.rb,
lib/dspy/lm/strategies/enhanced_prompting_strategy.rb,
lib/dspy/lm/strategies/anthropic_extraction_strategy.rb,
lib/dspy/lm/strategies/openai_structured_output_strategy.rb

Defined Under Namespace

Modules: Adapters, MessageFactory, ResponseMetadataFactory, Strategies, UsageFactory, VisionModels Classes: Adapter, AdapterError, AdapterFactory, AnthropicAdapter, AnthropicResponseMetadata, CacheManager, ConfigurationError, Error, GeminiAdapter, GeminiResponseMetadata, IncompatibleImageFeatureError, Message, MessageBuilder, MissingAPIKeyError, OllamaAdapter, OpenAIAdapter, OpenAIResponseMetadata, OpenAIUsage, Response, ResponseMetadata, RetryHandler, StrategySelector, StructuredOutputStrategy, UnsupportedProviderError, Usage

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_id, api_key: nil, **options) ⇒ LM

Returns a new instance of LM.



32
33
34
35
36
37
38
39
40
41
# File 'lib/dspy/lm.rb', line 32

def initialize(model_id, api_key: nil, **options)
  @model_id = model_id
  @api_key = api_key
  
  # Parse provider and model from model_id
  @provider, @model = parse_model_id(model_id)
  
  # Create appropriate adapter with options
  @adapter = AdapterFactory.create(model_id, api_key: api_key, **options)
end

Instance Attribute Details

#adapterObject (readonly)

Returns the value of attribute adapter.



30
31
32
# File 'lib/dspy/lm.rb', line 30

def adapter
  @adapter
end

#api_keyObject (readonly)

Returns the value of attribute api_key.



30
31
32
# File 'lib/dspy/lm.rb', line 30

def api_key
  @api_key
end

#modelObject (readonly)

Returns the value of attribute model.



30
31
32
# File 'lib/dspy/lm.rb', line 30

def model
  @model
end

#model_idObject (readonly)

Returns the value of attribute model_id.



30
31
32
# File 'lib/dspy/lm.rb', line 30

def model_id
  @model_id
end

#providerObject (readonly)

Returns the value of attribute provider.



30
31
32
# File 'lib/dspy/lm.rb', line 30

def provider
  @provider
end

Class Method Details

.cache_managerObject



146
147
148
# File 'lib/dspy/lm/cache_manager.rb', line 146

def cache_manager
  @cache_manager ||= CacheManager.new
end

Instance Method Details

#chat(inference_module, input_values, &block) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/dspy/lm.rb', line 43

def chat(inference_module, input_values, &block)
  signature_class = inference_module.signature_class
  
  # Build messages from inference module
  messages = build_messages(inference_module, input_values)
  
  # Execute with instrumentation
  response = instrument_lm_request(messages, signature_class.name) do
    chat_with_strategy(messages, signature_class, &block)
  end
  
  # Parse response (no longer needs separate instrumentation)
  parsed_result = parse_response(response, input_values, signature_class)
  
  parsed_result
end

#execute_raw_chat(messages, &streaming_block) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
# File 'lib/dspy/lm.rb', line 327

def execute_raw_chat(messages, &streaming_block)
  response = instrument_lm_request(messages, 'RawPrompt') do
    # Convert messages to hash format for adapter
    hash_messages = messages_to_hash_array(messages)
    # Direct adapter call, no strategies or JSON parsing
    adapter.chat(messages: hash_messages, signature: nil, &streaming_block)
  end
  
  # Return raw response content, not parsed JSON
  response.content
end

#messages_to_hash_array(messages) ⇒ Object

Convert Message objects to hash array for adapters



380
381
382
383
384
385
386
387
388
# File 'lib/dspy/lm.rb', line 380

def messages_to_hash_array(messages)
  messages.map do |msg|
    if msg.is_a?(Message)
      msg.to_h
    else
      msg
    end
  end
end

#normalize_messages(messages) ⇒ Object

Convert messages to normalized Message objects



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/dspy/lm.rb', line 340

def normalize_messages(messages)
  # Validate array format first
  unless messages.is_a?(Array)
    raise ArgumentError, "messages must be an array"
  end
  
  return messages if messages.all? { |m| m.is_a?(Message) }
  
  # Convert hash messages to Message objects
  normalized = []
  messages.each_with_index do |msg, index|
    if msg.is_a?(Message)
      normalized << msg
    elsif msg.is_a?(Hash)
      # Validate hash has required fields
      unless msg.key?(:role) && msg.key?(:content)
        raise ArgumentError, "Message at index #{index} must have :role and :content"
      end
      
      # Validate role
      valid_roles = %w[system user assistant]
      unless valid_roles.include?(msg[:role])
        raise ArgumentError, "Invalid role at index #{index}: #{msg[:role]}. Must be one of: #{valid_roles.join(', ')}"
      end
      
      # Create Message object
      message = MessageFactory.create(msg)
      if message.nil?
        raise ArgumentError, "Failed to create Message from hash at index #{index}"
      end
      normalized << message
    else
      raise ArgumentError, "Message at index #{index} must be a Message object or hash with :role and :content"
    end
  end
  
  normalized
end

#raw_chat(messages = nil, &block) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/dspy/lm.rb', line 60

def raw_chat(messages = nil, &block)
  # Support both array format and builder DSL
  if block_given? && messages.nil?
    # DSL mode - block is for building messages
    builder = MessageBuilder.new
    yield builder
    messages = builder.messages
    streaming_block = nil
  else
    # Array mode - block is for streaming
    messages ||= []
    streaming_block = block
  end
  
  # Normalize and validate messages
  messages = normalize_messages(messages)
  
  # Execute with instrumentation
  execute_raw_chat(messages, &streaming_block)
end

#validate_messages!(messages) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/dspy/lm.rb', line 305

def validate_messages!(messages)
  unless messages.is_a?(Array)
    raise ArgumentError, "messages must be an array"
  end
  
  messages.each_with_index do |message, index|
    # Accept both Message objects and hash format for backward compatibility
    if message.is_a?(Message)
      # Already validated by type system
      next
    elsif message.is_a?(Hash) && message.key?(:role) && message.key?(:content)
      # Legacy hash format - validate role
      valid_roles = %w[system user assistant]
      unless valid_roles.include?(message[:role])
        raise ArgumentError, "Invalid role at index #{index}: #{message[:role]}. Must be one of: #{valid_roles.join(', ')}"
      end
    else
      raise ArgumentError, "Message at index #{index} must be a Message object or hash with :role and :content"
    end
  end
end