raif 1.1.0 → 1.2.1.pre
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.
- checksums.yaml +4 -4
- data/README.md +150 -4
- data/app/assets/builds/raif.css +26 -1
- data/app/assets/stylesheets/raif/loader.scss +27 -1
- data/app/models/raif/concerns/llm_response_parsing.rb +22 -16
- data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +56 -0
- data/app/models/raif/concerns/llms/{bedrock_claude → bedrock}/message_formatting.rb +4 -4
- data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +7 -6
- data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
- data/app/models/raif/concerns/llms/{open_ai → open_ai_completions}/message_formatting.rb +1 -1
- data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
- data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
- data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
- data/app/models/raif/conversation.rb +17 -4
- data/app/models/raif/conversation_entry.rb +18 -2
- data/app/models/raif/embedding_models/{bedrock_titan.rb → bedrock.rb} +2 -2
- data/app/models/raif/llm.rb +73 -7
- data/app/models/raif/llms/anthropic.rb +56 -36
- data/app/models/raif/llms/{bedrock_claude.rb → bedrock.rb} +62 -45
- data/app/models/raif/llms/open_ai_base.rb +66 -0
- data/app/models/raif/llms/open_ai_completions.rb +100 -0
- data/app/models/raif/llms/open_ai_responses.rb +144 -0
- data/app/models/raif/llms/open_router.rb +44 -44
- data/app/models/raif/model_completion.rb +2 -0
- data/app/models/raif/model_tool.rb +4 -0
- data/app/models/raif/model_tools/provider_managed/base.rb +9 -0
- data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
- data/app/models/raif/streaming_responses/anthropic.rb +63 -0
- data/app/models/raif/streaming_responses/bedrock.rb +89 -0
- data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
- data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
- data/app/views/raif/admin/conversations/_conversation_entry.html.erb +48 -0
- data/app/views/raif/admin/conversations/show.html.erb +1 -1
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +7 -0
- data/app/views/raif/admin/model_completions/index.html.erb +1 -0
- data/app/views/raif/admin/model_completions/show.html.erb +28 -0
- data/app/views/raif/conversation_entries/_citations.html.erb +9 -0
- data/app/views/raif/conversation_entries/_conversation_entry.html.erb +5 -1
- data/app/views/raif/conversation_entries/_message.html.erb +4 -0
- data/config/locales/admin.en.yml +2 -0
- data/config/locales/en.yml +24 -0
- data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
- data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +1 -1
- data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +1 -1
- data/db/migrate/20250424232946_add_created_at_indexes.rb +1 -1
- data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +1 -1
- data/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
- data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
- data/db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb +7 -0
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +3 -3
- data/lib/generators/raif/install/templates/initializer.rb +14 -2
- data/lib/raif/configuration.rb +27 -5
- data/lib/raif/embedding_model_registry.rb +1 -1
- data/lib/raif/engine.rb +25 -9
- data/lib/raif/errors/streaming_error.rb +18 -0
- data/lib/raif/errors.rb +1 -0
- data/lib/raif/llm_registry.rb +169 -47
- data/lib/raif/migration_checker.rb +74 -0
- data/lib/raif/utils/html_fragment_processor.rb +170 -0
- data/lib/raif/utils.rb +1 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +2 -0
- data/spec/support/complex_test_tool.rb +65 -0
- data/spec/support/rspec_helpers.rb +66 -0
- data/spec/support/test_conversation.rb +18 -0
- data/spec/support/test_embedding_model.rb +27 -0
- data/spec/support/test_llm.rb +22 -0
- data/spec/support/test_model_tool.rb +32 -0
- data/spec/support/test_task.rb +45 -0
- metadata +52 -8
- 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:
|
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
|
|
data/config/locales/admin.en.yml
CHANGED
@@ -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.
|
data/config/locales/en.yml
CHANGED
@@ -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,9 +68,29 @@ 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)
|
70
92
|
open_router_llama_3_1_8b_instruct: Meta Llama 3.1 8B Instruct (via OpenRouter)
|
71
93
|
open_router_llama_3_3_70b_instruct: Meta Llama 3.3 70B Instruct (via OpenRouter)
|
94
|
+
open_router_llama_4_maverick: Meta Llama 4 Maverick (via OpenRouter)
|
95
|
+
open_router_llama_4_scout: Meta Llama 4 Scout (via OpenRouter)
|
72
96
|
raif_test_llm: Raif Test LLM
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class AddResponseFormatToRaifConversations < ActiveRecord::Migration[
|
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[
|
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
|
@@ -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
|
@@ -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.
|
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.
|
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
|