raif 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -4
  3. data/app/assets/builds/raif.css +26 -1
  4. data/app/assets/stylesheets/raif/loader.scss +27 -1
  5. data/app/models/raif/concerns/llm_response_parsing.rb +22 -16
  6. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +56 -0
  7. data/app/models/raif/concerns/llms/{bedrock_claude → bedrock}/message_formatting.rb +4 -4
  8. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
  9. data/app/models/raif/concerns/llms/message_formatting.rb +7 -6
  10. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
  11. data/app/models/raif/concerns/llms/{open_ai → open_ai_completions}/message_formatting.rb +1 -1
  12. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
  13. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
  14. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
  15. data/app/models/raif/conversation.rb +17 -4
  16. data/app/models/raif/conversation_entry.rb +18 -2
  17. data/app/models/raif/embedding_models/{bedrock_titan.rb → bedrock.rb} +2 -2
  18. data/app/models/raif/llm.rb +73 -7
  19. data/app/models/raif/llms/anthropic.rb +56 -36
  20. data/app/models/raif/llms/{bedrock_claude.rb → bedrock.rb} +62 -45
  21. data/app/models/raif/llms/open_ai_base.rb +66 -0
  22. data/app/models/raif/llms/open_ai_completions.rb +100 -0
  23. data/app/models/raif/llms/open_ai_responses.rb +144 -0
  24. data/app/models/raif/llms/open_router.rb +38 -43
  25. data/app/models/raif/model_completion.rb +2 -0
  26. data/app/models/raif/model_tool.rb +4 -0
  27. data/app/models/raif/model_tools/provider_managed/base.rb +9 -0
  28. data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
  29. data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
  30. data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
  31. data/app/models/raif/streaming_responses/anthropic.rb +63 -0
  32. data/app/models/raif/streaming_responses/bedrock.rb +89 -0
  33. data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
  34. data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
  35. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +48 -0
  36. data/app/views/raif/admin/conversations/show.html.erb +1 -1
  37. data/app/views/raif/admin/model_completions/_model_completion.html.erb +7 -0
  38. data/app/views/raif/admin/model_completions/index.html.erb +1 -0
  39. data/app/views/raif/admin/model_completions/show.html.erb +28 -0
  40. data/app/views/raif/conversation_entries/_citations.html.erb +9 -0
  41. data/app/views/raif/conversation_entries/_conversation_entry.html.erb +5 -1
  42. data/app/views/raif/conversation_entries/_message.html.erb +4 -0
  43. data/config/locales/admin.en.yml +2 -0
  44. data/config/locales/en.yml +22 -0
  45. data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
  46. data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +1 -1
  47. data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +1 -1
  48. data/db/migrate/20250424232946_add_created_at_indexes.rb +1 -1
  49. data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +1 -1
  50. data/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
  51. data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
  52. data/db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb +7 -0
  53. data/lib/generators/raif/conversation/templates/conversation.rb.tt +3 -3
  54. data/lib/generators/raif/install/templates/initializer.rb +14 -2
  55. data/lib/raif/configuration.rb +27 -5
  56. data/lib/raif/embedding_model_registry.rb +1 -1
  57. data/lib/raif/engine.rb +25 -9
  58. data/lib/raif/errors/streaming_error.rb +18 -0
  59. data/lib/raif/errors.rb +1 -0
  60. data/lib/raif/llm_registry.rb +157 -47
  61. data/lib/raif/migration_checker.rb +74 -0
  62. data/lib/raif/utils/html_fragment_processor.rb +169 -0
  63. data/lib/raif/utils.rb +1 -0
  64. data/lib/raif/version.rb +1 -1
  65. data/lib/raif.rb +2 -0
  66. metadata +45 -8
  67. data/app/models/raif/llms/open_ai.rb +0 -256
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::StreamingResponses::Anthropic
4
+
5
+ def initialize
6
+ @response_json = { "content" => [], "usage" => {} }
7
+ @finish_reason = nil
8
+ end
9
+
10
+ def process_streaming_event(event_type, event)
11
+ delta = nil
12
+ index = event["index"]
13
+
14
+ case event_type
15
+ when "message_start"
16
+ @response_json = event["message"]
17
+ @response_json["content"] = []
18
+ @response_json["usage"] ||= {}
19
+ when "content_block_start"
20
+ @response_json["content"][index] = event["content_block"]
21
+ if event.dig("content_block", "type") == "tool_use"
22
+ @response_json["content"][index]["input"] = ""
23
+ end
24
+ when "content_block_delta"
25
+ delta_chunk = event["delta"]
26
+ if delta_chunk["type"] == "text_delta"
27
+ delta = delta_chunk["text"]
28
+ @response_json["content"][index]["text"] += delta if delta
29
+ elsif delta_chunk["type"] == "input_json_delta"
30
+ @response_json["content"][index]["input"] += delta_chunk["partial_json"]
31
+ end
32
+ when "content_block_stop"
33
+ content_block = @response_json["content"][index]
34
+ if content_block&.dig("type") == "tool_use"
35
+ begin
36
+ content_block["input"] = JSON.parse(content_block["input"])
37
+ rescue JSON::ParserError
38
+ # If parsing fails, leave as a string
39
+ end
40
+ end
41
+ when "message_delta"
42
+ @finish_reason = event.dig("delta", "stop_reason")
43
+ @response_json["usage"]["output_tokens"] = event.dig("usage", "output_tokens")
44
+ when "message_stop"
45
+ @finish_reason = "stop"
46
+ when "error"
47
+ error_details = event["error"]
48
+ raise Raif::Errors::StreamingError.new(
49
+ message: error_details["message"],
50
+ type: error_details["type"],
51
+ event: event
52
+ )
53
+ end
54
+
55
+ [delta, @finish_reason]
56
+ end
57
+
58
+ def current_response_json
59
+ @response_json["stop_reason"] = @finish_reason
60
+ @response_json
61
+ end
62
+
63
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::StreamingResponses::Bedrock
4
+
5
+ def initialize_new_message
6
+ # Initialize empty AWS response object
7
+ @message = Aws::BedrockRuntime::Types::Message.new(
8
+ role: "assistant",
9
+ content: []
10
+ )
11
+
12
+ @output = Aws::BedrockRuntime::Types::ConverseOutput::Message.new(message: @message)
13
+
14
+ @usage = Aws::BedrockRuntime::Types::TokenUsage.new(
15
+ input_tokens: 0,
16
+ output_tokens: 0,
17
+ total_tokens: 0
18
+ )
19
+
20
+ @response = Aws::BedrockRuntime::Types::ConverseResponse.new(
21
+ output: @output,
22
+ usage: @usage,
23
+ stop_reason: nil
24
+ )
25
+ end
26
+
27
+ def process_streaming_event(event_type, event)
28
+ delta = nil
29
+
30
+ case event.event_type
31
+ when :message_start
32
+ initialize_new_message
33
+ when :content_block_start
34
+ index = event.content_block_index
35
+
36
+ if event.start.is_a?(Aws::BedrockRuntime::Types::ContentBlockStart::ToolUse)
37
+ tool_use = event.start.tool_use
38
+ @message.content[index] = Aws::BedrockRuntime::Types::ContentBlock.new(
39
+ tool_use: Aws::BedrockRuntime::Types::ToolUseBlock.new(
40
+ tool_use_id: tool_use.tool_use_id,
41
+ name: tool_use.name,
42
+ input: ""
43
+ )
44
+ )
45
+ else
46
+ @message.content[index] = Aws::BedrockRuntime::Types::ContentBlock::Text.new(text: "")
47
+ end
48
+ when :content_block_delta
49
+ index = event.content_block_index
50
+
51
+ if event.delta.is_a?(Aws::BedrockRuntime::Types::ContentBlockDelta::Text)
52
+ @message.content[index] ||= Aws::BedrockRuntime::Types::ContentBlock::Text.new(text: "")
53
+ delta = event.delta.text
54
+ @message.content[index].text += delta
55
+ elsif event.delta.is_a?(Aws::BedrockRuntime::Types::ContentBlockDelta::ToolUse)
56
+ tool_use = event.delta.tool_use
57
+ @message.content[index] ||= Aws::BedrockRuntime::Types::ContentBlock.new
58
+ @message.content[index].tool_use ||= Aws::BedrockRuntime::Types::ToolUseBlock.new(
59
+ tool_use_id: tool_use.tool_use_id,
60
+ name: tool_use.name,
61
+ input: ""
62
+ )
63
+
64
+ @message.content[index].tool_use.input += event.delta.tool_use.input
65
+ end
66
+ when :content_block_stop
67
+ content_block = @message.content[event.content_block_index]
68
+
69
+ if content_block&.tool_use&.input.is_a?(String)
70
+ begin
71
+ content_block.tool_use.input = JSON.parse(content_block.tool_use.input)
72
+ rescue JSON::ParserError
73
+ # If parsing fails, leave as a string
74
+ end
75
+ end
76
+ when :message_stop
77
+ @response.stop_reason = event.stop_reason
78
+ when :metadata
79
+ @response.usage = event.usage if event.respond_to?(:usage)
80
+ end
81
+
82
+ [delta, @response.stop_reason]
83
+ end
84
+
85
+ def current_response
86
+ @response
87
+ end
88
+
89
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::StreamingResponses::OpenAiCompletions
4
+ attr_reader :raw_response, :tool_calls
5
+
6
+ def initialize
7
+ @id = nil
8
+ @raw_response = ""
9
+ @tool_calls = []
10
+ @usage = {}
11
+ @finish_reason = nil
12
+ @response_json = {}
13
+ end
14
+
15
+ def process_streaming_event(event_type, event)
16
+ @id ||= event["id"]
17
+ delta_chunk = event.dig("choices", 0, "delta")
18
+ finish_reason = event.dig("choices", 0, "finish_reason")
19
+ @finish_reason ||= finish_reason
20
+
21
+ delta_content = delta_chunk&.dig("content")
22
+ @raw_response += delta_content if delta_content
23
+
24
+ if delta_chunk&.key?("tool_calls")
25
+ delta_chunk["tool_calls"].each do |tool_call_chunk|
26
+ index = tool_call_chunk["index"]
27
+ @tool_calls[index] ||= { "function" => {} }
28
+ @tool_calls[index]["id"] ||= tool_call_chunk["id"]
29
+ if tool_call_chunk.dig("function", "name")
30
+ @tool_calls[index]["function"]["name"] = tool_call_chunk.dig("function", "name")
31
+ end
32
+ if tool_call_chunk.dig("function", "arguments")
33
+ @tool_calls[index]["function"]["arguments"] ||= ""
34
+ @tool_calls[index]["function"]["arguments"] += tool_call_chunk.dig("function", "arguments")
35
+ end
36
+ end
37
+ end
38
+
39
+ @usage = event["usage"] if event["usage"]
40
+
41
+ [delta_content, finish_reason]
42
+ end
43
+
44
+ def current_response_json
45
+ message = {
46
+ "role" => "assistant",
47
+ "content" => @raw_response
48
+ }
49
+
50
+ if @tool_calls.any?
51
+ message["content"] = nil # Per OpenAI spec, content is null if tool_calls are present
52
+ message["tool_calls"] = @tool_calls.map do |tc|
53
+ # The streaming format for tool calls is slightly different from the final format.
54
+ # We need to adjust it here.
55
+ {
56
+ "id" => tc["id"],
57
+ "type" => "function",
58
+ "function" => {
59
+ "name" => tc.dig("function", "name"),
60
+ "arguments" => tc.dig("function", "arguments")
61
+ }
62
+ }
63
+ end
64
+ end
65
+
66
+ {
67
+ "id" => @id,
68
+ "choices" => [{
69
+ "index" => 0,
70
+ "message" => message,
71
+ "finish_reason" => @finish_reason
72
+ }],
73
+ "usage" => @usage
74
+ }
75
+ end
76
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::StreamingResponses::OpenAiResponses
4
+
5
+ def initialize
6
+ @output_items = []
7
+ @finish_reason = nil
8
+ end
9
+
10
+ def process_streaming_event(event_type, event)
11
+ output_index = event["output_index"]
12
+ content_index = event["content_index"]
13
+
14
+ delta = nil
15
+
16
+ case event["type"]
17
+ when "response.created"
18
+ @id = event["response"]["id"]
19
+ when "response.output_item.added", "response.output_item.done"
20
+ @output_items[output_index] = event["item"]
21
+ when "response.content_part.added", "response.content_part.done"
22
+ @output_items[output_index]["content"] ||= []
23
+ @output_items[output_index]["content"][content_index] = event["part"]
24
+ when "response.output_text.delta"
25
+ delta = event["delta"]
26
+ @output_items[output_index]["content"][content_index]["text"] ||= ""
27
+ @output_items[output_index]["content"][content_index]["text"] += event["delta"]
28
+ when "response.output_text.done"
29
+ @output_items[output_index]["content"][content_index]["text"] = event["text"]
30
+ when "response.completed"
31
+ @usage = event["response"]["usage"]
32
+ @finish_reason = "stop"
33
+ when "error"
34
+ raise Raif::Errors::StreamingError.new(
35
+ message: event["message"],
36
+ type: event["type"],
37
+ code: event["code"],
38
+ event: event
39
+ )
40
+ end
41
+
42
+ [delta, @finish_reason]
43
+ end
44
+
45
+ # The response we've built up so far.
46
+ def current_response_json
47
+ {
48
+ "id" => @id,
49
+ "output" => @output_items,
50
+ "usage" => @usage
51
+ }
52
+ end
53
+
54
+ end
@@ -21,6 +21,7 @@
21
21
  <%= link_to t("raif.admin.common.model_completion"), raif.admin_model_completion_path(entry.raif_model_completion) %>
22
22
  <% end %>
23
23
  </div>
24
+
24
25
  <div class="mb-3">
25
26
  <strong><%= t("raif.admin.common.user_message") %>:</strong>
26
27
  <pre class="mt-2"><%= entry.user_message %></pre>
@@ -31,4 +32,51 @@
31
32
  <pre class="mt-2"><%= entry.model_response_message %></pre>
32
33
  </div>
33
34
  <% end %>
35
+
36
+ <% if entry.raif_model_tool_invocations.any? %>
37
+ <div class="mb-3">
38
+ <strong><%= t("raif.admin.common.model_tool_invocations") %>:</strong>
39
+ <div class="ms-3 mt-2">
40
+ <% entry.raif_model_tool_invocations.each do |invocation| %>
41
+ <div class="card mb-2">
42
+ <div class="card-body p-3">
43
+ <div class="d-flex w-100 justify-content-between align-items-start">
44
+ <div>
45
+ <h6 class="mb-1">
46
+ <%= link_to invocation.tool_name, raif.admin_model_tool_invocation_path(invocation) %>
47
+ </h6>
48
+ <% if invocation.completed_at? %>
49
+ <span class="badge bg-success"><%= t("raif.admin.common.completed") %></span>
50
+ <% elsif invocation.failed_at? %>
51
+ <span class="badge bg-danger"><%= t("raif.admin.common.failed") %></span>
52
+ <% else %>
53
+ <span class="badge bg-secondary"><%= t("raif.admin.common.pending") %></span>
54
+ <% end %>
55
+ </div>
56
+ <small class="text-muted"><%= invocation.created_at.rfc822 %></small>
57
+ </div>
58
+ <div class="mt-2">
59
+ <strong><%= t("raif.admin.common.arguments") %>:</strong>
60
+ <pre class="pre-wrap mt-1"><%= begin
61
+ JSON.pretty_generate(invocation.tool_arguments)
62
+ rescue StandardError
63
+ invocation.tool_arguments
64
+ end %></pre>
65
+ </div>
66
+ <% if invocation.result.present? %>
67
+ <div class="mt-2">
68
+ <strong><%= t("raif.admin.common.result") %>:</strong>
69
+ <pre class="pre-wrap mt-1"><%= begin
70
+ JSON.pretty_generate(invocation.result)
71
+ rescue StandardError
72
+ invocation.result
73
+ end %></pre>
74
+ </div>
75
+ <% end %>
76
+ </div>
77
+ </div>
78
+ <% end %>
79
+ </div>
80
+ </div>
81
+ <% end %>
34
82
  </div>
@@ -49,7 +49,7 @@
49
49
  </div>
50
50
  <div class="card-body">
51
51
  <div class="list-group">
52
- <%= render collection: @conversation.entries.order(created_at: :asc).includes(:raif_model_completion),
52
+ <%= render collection: @conversation.entries.order(created_at: :asc).includes(:raif_model_completion, :raif_model_tool_invocations),
53
53
  partial: "raif/admin/conversations/conversation_entry",
54
54
  as: :entry %>
55
55
  </div>
@@ -6,5 +6,12 @@
6
6
  <td><%= model_completion.response_format %></td>
7
7
  <td><%= model_completion.total_tokens ? number_with_delimiter(model_completion.total_tokens) : "-" %></td>
8
8
  <td><%= model_completion.total_cost ? number_to_currency(model_completion.total_cost, precision: 6) : "-" %></td>
9
+ <td>
10
+ <% if model_completion.citations.present? %>
11
+ <span class="badge bg-info"><%= model_completion.citations.length %></span>
12
+ <% else %>
13
+ <span class="text-muted">-</span>
14
+ <% end %>
15
+ </td>
9
16
  <td><small class="text-muted"><%= truncate(model_completion.raw_response, length: 100) %></small></td>
10
17
  </tr>
@@ -14,6 +14,7 @@
14
14
  <th><%= t("raif.admin.common.response_format") %></th>
15
15
  <th><%= t("raif.admin.common.total_tokens") %></th>
16
16
  <th><%= t("raif.admin.common.total_cost") %></th>
17
+ <th><%= t("raif.admin.common.citations") %></th>
17
18
  <th><%= t("raif.admin.common.response") %></th>
18
19
  </tr>
19
20
  </thead>
@@ -142,3 +142,31 @@
142
142
  <% end %>
143
143
  </div>
144
144
  </div>
145
+
146
+ <div class="card mb-4">
147
+ <div class="card-header">
148
+ <h5 class="mb-0"><%= t("raif.admin.common.citations") %></h5>
149
+ </div>
150
+ <div class="card-body">
151
+ <% if @model_completion.citations.present? %>
152
+ <% @model_completion.citations.each_with_index do |citation, index| %>
153
+ <div class="mb-3 p-3 border rounded">
154
+ <div class="d-flex align-items-start">
155
+ <span class="badge bg-primary me-3"><%= index + 1 %></span>
156
+ <div class="flex-grow-1">
157
+ <h6 class="mb-1">
158
+ <a href="<%= citation["url"] %>" target="_blank" rel="noopener" class="text-decoration-none">
159
+ <%= citation["title"] %>
160
+ <i class="bi bi-box-arrow-up-right ms-1" style="font-size: 0.8em;"></i>
161
+ </a>
162
+ </h6>
163
+ <small class="text-muted"><%= citation["url"] %></small>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ <% end %>
168
+ <% else %>
169
+ <p class="text-muted mb-0"><%= t("raif.admin.common.no_citations") %></p>
170
+ <% end %>
171
+ </div>
172
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="conversation-entry-citations">
2
+ <h6><%= t("raif.common.sources") %></h6>
3
+ <% conversation_entry.citations.each do |citation| %>
4
+ <%= link_to citation["url"], target: "_blank", rel: "noopener noreferrer", class: "d-flex align-items-center" do %>
5
+ <%= image_tag "https://www.google.com/s2/favicons?sz=16&domain=#{citation["url"]}", class: "me-1" %>
6
+ <%= citation["title"] %>
7
+ <% end %>
8
+ <% end %>
9
+ </div>
@@ -14,7 +14,11 @@
14
14
  <% elsif conversation_entry.generating_response? %>
15
15
  <%= render "raif/conversation_entries/message",
16
16
  conversation_entry: conversation_entry,
17
- content: content_tag(:span, "", class: "raif-loader"),
17
+ content: if conversation_entry.model_response_message.present?
18
+ conversation_entry.model_response_message + content_tag(:span, "", class: "raif-streaming-cursor")
19
+ else
20
+ content_tag(:span, "", class: "raif-loader")
21
+ end,
18
22
  message_type: :model_response %>
19
23
 
20
24
  <% elsif conversation_entry.completed? %>
@@ -14,6 +14,10 @@
14
14
  <% else %>
15
15
  <%= content %>
16
16
  <% end %>
17
+
18
+ <% if message_type == :model_response && local_assigns[:conversation_entry]&.citations.present? %>
19
+ <%= render "raif/conversation_entries/citations", conversation_entry: conversation_entry %>
20
+ <% end %>
17
21
  </div>
18
22
  <% end %>
19
23
 
@@ -11,6 +11,7 @@ en:
11
11
  all: All
12
12
  arguments: Arguments
13
13
  at: at
14
+ citations: Citations
14
15
  completed: Completed
15
16
  completed_at: Completed At
16
17
  completion_tokens: Completion Tokens
@@ -41,6 +42,7 @@ en:
41
42
  model_response: Model Response
42
43
  model_tool_invocations: Model Tool Invocations
43
44
  no_agents: No agents found.
45
+ no_citations: No citations found in this response.
44
46
  no_conversations: No conversations found.
45
47
  no_model_completions: No model completions found.
46
48
  no_model_tool_invocations: No model tool invocations found.
@@ -12,6 +12,7 @@ en:
12
12
  too_short: must have at least 1 tool
13
13
  common:
14
14
  send: Send
15
+ sources: Sources
15
16
  there_was_an_error_generating_this_response: There was an error generating this response
16
17
  tools: Tools
17
18
  type_your_message: Type your message...
@@ -52,6 +53,9 @@ en:
52
53
  anthropic_claude_3_opus: Anthropic Claude 3 Opus
53
54
  anthropic_claude_4_opus: Anthropic Claude 4 Opus
54
55
  anthropic_claude_4_sonnet: Anthropic Claude 4 Sonnet
56
+ bedrock_amazon_nova_lite: Amazon Nova Lite (via AWS Bedrock)
57
+ bedrock_amazon_nova_micro: Amazon Nova Micro (via AWS Bedrock)
58
+ bedrock_amazon_nova_pro: Amazon Nova Pro (via AWS Bedrock)
55
59
  bedrock_claude_3_5_haiku: Anthropic Claude 3.5 Haiku (via AWS Bedrock)
56
60
  bedrock_claude_3_5_sonnet: Anthropic Claude 3.5 Sonnet (via AWS Bedrock)
57
61
  bedrock_claude_3_7_sonnet: Anthropic Claude 3.7 Sonnet (via AWS Bedrock)
@@ -64,6 +68,24 @@ en:
64
68
  open_ai_gpt_4_1_nano: OpenAI GPT-4.1 Nano
65
69
  open_ai_gpt_4o: OpenAI GPT-4o
66
70
  open_ai_gpt_4o_mini: OpenAI GPT-4o Mini
71
+ open_ai_o1: OpenAI o1
72
+ open_ai_o1_mini: OpenAI o1 Mini
73
+ open_ai_o3: OpenAI o3
74
+ open_ai_o3_mini: OpenAI o3 Mini
75
+ open_ai_o4_mini: OpenAI o4 Mini
76
+ open_ai_responses_gpt_3_5_turbo: OpenAI GPT-3.5 Turbo (Responses API)
77
+ open_ai_responses_gpt_4_1: OpenAI GPT-4.1 (Responses API)
78
+ open_ai_responses_gpt_4_1_mini: OpenAI GPT-4.1 Mini (Responses API)
79
+ open_ai_responses_gpt_4_1_nano: OpenAI GPT-4.1 Nano (Responses API)
80
+ open_ai_responses_gpt_4o: OpenAI GPT-4o (Responses API)
81
+ open_ai_responses_gpt_4o_mini: OpenAI GPT-4o Mini (Responses API)
82
+ open_ai_responses_o1: OpenAI o1 (Responses API)
83
+ open_ai_responses_o1_mini: OpenAI o1 Mini (Responses API)
84
+ open_ai_responses_o1_pro: OpenAI o1 Pro (Responses API)
85
+ open_ai_responses_o3: OpenAI o3 (Responses API)
86
+ open_ai_responses_o3_mini: OpenAI o3 Mini (Responses API)
87
+ open_ai_responses_o3_pro: OpenAI o3 Pro (Responses API)
88
+ open_ai_responses_o4_mini: OpenAI o4 Mini (Responses API)
67
89
  open_router_claude_3_7_sonnet: Anthropic Claude 3.7 Sonnet (via OpenRouter)
68
90
  open_router_deepseek_chat_v3: DeepSeek Chat v3 (via OpenRouter)
69
91
  open_router_gemini_2_0_flash: Google Gemini 2.0 Flash (via OpenRouter)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateRaifTables < ActiveRecord::Migration[8.0]
3
+ class CreateRaifTables < ActiveRecord::Migration[7.1]
4
4
  def change
5
5
  json_column_type = if connection.adapter_name.downcase.include?("postgresql")
6
6
  :jsonb
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class AddResponseFormatToRaifConversations < ActiveRecord::Migration[8.0]
3
+ class AddResponseFormatToRaifConversations < ActiveRecord::Migration[7.1]
4
4
  def change
5
5
  add_column :raif_conversations, :response_format, :integer, default: 0, null: false
6
6
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class AddCostColumnsToRaifModelCompletions < ActiveRecord::Migration[8.0]
3
+ class AddCostColumnsToRaifModelCompletions < ActiveRecord::Migration[7.1]
4
4
  # If you need to backfill cost columns for existing records:
5
5
  # Raif::ModelCompletion.find_each do |model_completion|
6
6
  # model_completion.calculate_costs
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class AddCreatedAtIndexes < ActiveRecord::Migration[8.0]
3
+ class AddCreatedAtIndexes < ActiveRecord::Migration[7.1]
4
4
  def change
5
5
  add_index :raif_model_completions, :created_at
6
6
  add_index :raif_tasks, :created_at
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class AddStatusIndexesToRaifTasks < ActiveRecord::Migration[8.0]
3
+ class AddStatusIndexesToRaifTasks < ActiveRecord::Migration[7.1]
4
4
  def change
5
5
  add_index :raif_tasks, :completed_at
6
6
  add_index :raif_tasks, :failed_at
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddResponseIdAndResponseArrayToModelCompletions < ActiveRecord::Migration[7.1]
4
+ def change
5
+ json_column_type = if connection.adapter_name.downcase.include?("postgresql")
6
+ :jsonb
7
+ else
8
+ :json
9
+ end
10
+
11
+ add_column :raif_model_completions, :response_id, :string
12
+ add_column :raif_model_completions, :response_array, json_column_type
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddCitationsToRaifModelCompletions < ActiveRecord::Migration[7.1]
4
+ def change
5
+ json_column_type = if connection.adapter_name.downcase.include?("postgresql")
6
+ :jsonb
7
+ else
8
+ :json
9
+ end
10
+
11
+ add_column :raif_model_completions, :citations, json_column_type
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddStreamResponseToRaifModelCompletions < ActiveRecord::Migration[7.1]
4
+ def change
5
+ add_column :raif_model_completions, :stream_response, :boolean, default: false, null: false
6
+ end
7
+ end
@@ -7,11 +7,11 @@ module Raif
7
7
  # If you set this to something other than :text, make sure to include instructions to the model in your system prompt
8
8
  llm_response_format :<%= options[:response_format] %>
9
9
 
10
- # If you want to always include a certain set of model tools with this conversation type,
10
+ # If you want to always include a certain set of model tools with this conversation type,
11
11
  # uncomment this callback to populate the available_model_tools attribute with your desired model tools.
12
12
  # before_create -> { self.available_model_tools = ["Raif::ModelTools::Example"] }
13
13
 
14
- # Override the methods below to customize the system prompt for this conversation type.
14
+ # Override the methods below to customize the system prompt for this conversation type.
15
15
  # def system_prompt_intro
16
16
  # Raif.config.conversation_system_prompt_intro
17
17
  # end
@@ -36,4 +36,4 @@ module Raif
36
36
  # end
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -17,7 +17,7 @@ Raif.configure do |config|
17
17
  # config.anthropic_models_enabled = ENV["ANTHROPIC_API_KEY"].present?
18
18
 
19
19
  # Whether Anthropic models via AWS Bedrock are enabled. Defaults to false
20
- # config.anthropic_bedrock_models_enabled = false
20
+ # config.bedrock_models_enabled = false
21
21
 
22
22
  # The AWS Bedrock region to use. Defaults to "us-east-1"
23
23
  # config.aws_bedrock_region = "us-east-1"
@@ -26,7 +26,7 @@ Raif.configure do |config|
26
26
  # config.aws_bedrock_model_name_prefix = "us"
27
27
 
28
28
  # Whether Titan embedding models are enabled. Defaults to false
29
- # config.aws_bedrock_titan_embedding_models_enabled = false
29
+ # config.bedrock_embedding_models_enabled = false
30
30
 
31
31
  # Your OpenRouter API key. Defaults to ENV["OPENROUTER_API_KEY"]
32
32
  # config.open_router_api_key = ENV["OPENROUTER_API_KEY"]
@@ -48,6 +48,12 @@ Raif.configure do |config|
48
48
  # open_ai_gpt_4o_mini
49
49
  # open_ai_gpt_4o
50
50
  # open_ai_gpt_3_5_turbo
51
+ # open_ai_responses_gpt_4_1
52
+ # open_ai_responses_gpt_4_1_mini
53
+ # open_ai_responses_gpt_4_1_nano
54
+ # open_ai_responses_gpt_4o_mini
55
+ # open_ai_responses_gpt_4o
56
+ # open_ai_gpt_3_5_turbo
51
57
  # anthropic_claude_3_7_sonnet
52
58
  # anthropic_claude_3_5_sonnet
53
59
  # anthropic_claude_3_5_haiku
@@ -56,6 +62,9 @@ Raif.configure do |config|
56
62
  # bedrock_claude_3_7_sonnet
57
63
  # bedrock_claude_3_5_haiku
58
64
  # bedrock_claude_3_opus
65
+ # bedrock_amazon_nova_micro
66
+ # bedrock_amazon_nova_lite
67
+ # bedrock_amazon_nova_pro
59
68
  # open_router_claude_3_7_sonnet
60
69
  # open_router_llama_3_3_70b_instruct
61
70
  # open_router_llama_3_1_8b_instruct
@@ -119,6 +128,9 @@ Raif.configure do |config|
119
128
  # The user tool types that are available. Defaults to []
120
129
  # config.user_tool_types = []
121
130
 
131
+ # The chunk size threshold for streaming updates. Defaults to 25.
132
+ # config.streaming_update_chunk_size_threshold = 25
133
+
122
134
  # Whether LLM API requests are enabled. Defaults to true.
123
135
  # Use this to globally disable requests to LLM APIs.
124
136
  # config.llm_api_requests_enabled = true