Class: Ask::Agent::Chat

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/agent/chat.rb

Overview

Thin wrapper around Provider + an internal message array that presents a Chat-like API for ask-agent internal use.

Manages conversation history, resolves the correct provider/model, handles streaming chunk accumulation, and normalises tool call formats between Ask::Provider (Array of Hashes) and ask-agent internal usage (Hash of { id => ToolCallInfo }).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model:, tools: [], temperature: nil, schema: nil, assume_model_exists: false, provider: nil) ⇒ Chat

Returns a new instance of Chat.

Parameters:

  • model (String, #ask)

    model ID or chat-like object

  • tools (Array<Ask::Tool>) (defaults to: [])

    tool instances available to the chat

  • temperature (Float, nil) (defaults to: nil)

    sampling temperature

  • schema (Ask::Schema, Hash, nil) (defaults to: nil)

    structured output schema

  • assume_model_exists (Boolean) (defaults to: false)

    whether to skip model catalog lookup

  • provider (String, Symbol, nil) (defaults to: nil)

    provider slug to use (overrides model catalog)



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/ask/agent/chat.rb', line 43

def initialize(model:, tools: [], temperature: nil, schema: nil,
               assume_model_exists: false, provider: nil, **)
  @model_id = model.respond_to?(:id) ? model.id : model.to_s
  @model_info = resolve_model(@model_id) unless assume_model_exists
  @tools = tools
  @temperature = temperature
  @schema = schema
  @messages = []
  @provider_override = provider
  @provider = nil
end

Instance Attribute Details

#messagesArray<Ask::Message> (readonly)

Returns all messages in the conversation.

Returns:

  • (Array<Ask::Message>)

    all messages in the conversation



35
36
37
# File 'lib/ask/agent/chat.rb', line 35

def messages
  @messages
end

#model_idString (readonly)

Returns model ID (e.g. “gpt-4o”).

Returns:

  • (String)

    model ID (e.g. “gpt-4o”)



29
30
31
# File 'lib/ask/agent/chat.rb', line 29

def model_id
  @model_id
end

Instance Method Details

#add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil) ⇒ Object

Add a message to the conversation history.

Parameters:

  • role (Symbol)

    :system, :user, :assistant, :tool

  • content (String, nil) (defaults to: nil)

    message content

  • tool_call_id (String, nil) (defaults to: nil)

    tool call ID (for tool results)

  • tool_calls (Array<Hash>, nil) (defaults to: nil)

    tool call invocations



114
115
116
117
118
119
120
121
# File 'lib/ask/agent/chat.rb', line 114

def add_message(role:, content: nil, tool_call_id: nil, tool_calls: nil)
  @messages << Ask::Message.new(
    role: role,
    content: content,
    tool_call_id: tool_call_id,
    tool_calls: tool_calls
  )
end

#ask(message = nil) {|ChatChunk| ... } ⇒ ResponseMessage

Send a user message and get a completion response.

Parameters:

  • message (String, nil) (defaults to: nil)

    user message text

Yields:

  • (ChatChunk)

    streaming chunks (only when a block is given)

Returns:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ask/agent/chat.rb', line 60

def ask(message = nil, &block)
  @messages << Ask::Message.new(role: :user, content: message.to_s) if message

  stream = block_given?
  tool_defs = @tools.map { |t| Ask::ToolDef.from_tool(t) }

  # Accumulator for tool calls during streaming (keyed by index)
  calls_acc = {}

  result = provider.chat(          @messages.map(&:to_h),
    model: @model_id,
    tools: tool_defs,
    temperature: @temperature,
    stream: stream,
    schema: @schema&.respond_to?(:to_json_schema) ? @schema.to_json_schema : @schema,
    **(@extra_params || {})
  ) do |raw_chunk|
    next unless block_given?

    # Accumulate tool calls by index during streaming
    accumulate_tool_calls(raw_chunk, calls_acc)

    # Yield adapted chunk with current tool call state
    yield ChatChunk.new(
      content: raw_chunk.content,
      tool_calls: build_current_tool_calls(calls_acc),
      thinking: raw_chunk.respond_to?(:thinking) ? raw_chunk.thinking : nil
    )
  end

  response_msg = if stream
    build_stream_response(result, calls_acc)
  else
    build_response(result)
  end

  # Store assistant response in conversation history
  @messages << Ask::Message.new(
    role: :assistant,
    content: response_msg.content,
    tool_calls: response_msg.tool_calls&.values&.map { |tc|
      { id: tc.id, type: "function", name: tc.name, arguments: tc.arguments }
    }
  )

  response_msg
end

#modelString

Returns model ID (e.g. “gpt-4o”).

Returns:

  • (String)

    model ID (e.g. “gpt-4o”)



31
32
33
# File 'lib/ask/agent/chat.rb', line 31

def model
  @model_id
end

#reset_messages!Object

Clear all messages from the conversation.



155
156
157
# File 'lib/ask/agent/chat.rb', line 155

def reset_messages!
  @messages.clear
end

#with_instructions(prompt) ⇒ self

Set or replace the system prompt.

Parameters:

  • prompt (String)

    system instructions

Returns:

  • (self)


127
128
129
130
131
# File 'lib/ask/agent/chat.rb', line 127

def with_instructions(prompt)
  @messages.reject! { |m| m.role == :system }
  @messages.unshift(Ask::Message.new(role: :system, content: prompt))
  self
end

#with_params(**params) ⇒ self

Set additional parameters for the provider call and return self.

Parameters:

  • params (Hash)

    extra parameters passed to the provider

Returns:

  • (self)


141
142
143
144
# File 'lib/ask/agent/chat.rb', line 141

def with_params(**params)
  @extra_params = (@extra_params || {}).merge(params)
  self
end

#with_schema(schema) ⇒ self

Parameters:

  • params (Hash)

    extra keyword arguments for the provider

Returns:

  • (self)


149
150
151
152
# File 'lib/ask/agent/chat.rb', line 149

def with_schema(schema)
  @schema = schema.respond_to?(:to_json_schema) ? schema.to_json_schema : schema
  self
end