ruby_llm_swarm 1.9.1

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 (154) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +175 -0
  4. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +187 -0
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  8. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +13 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +7 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +28 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
  22. data/lib/generators/ruby_llm/generator_helpers.rb +194 -0
  23. data/lib/generators/ruby_llm/install/install_generator.rb +106 -0
  24. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
  25. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
  26. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +7 -0
  27. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +16 -0
  28. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +45 -0
  29. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +20 -0
  30. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +12 -0
  31. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -0
  32. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
  33. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
  34. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
  35. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +124 -0
  36. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  37. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  38. data/lib/ruby_llm/active_record/acts_as.rb +174 -0
  39. data/lib/ruby_llm/active_record/acts_as_legacy.rb +384 -0
  40. data/lib/ruby_llm/active_record/chat_methods.rb +350 -0
  41. data/lib/ruby_llm/active_record/message_methods.rb +81 -0
  42. data/lib/ruby_llm/active_record/model_methods.rb +84 -0
  43. data/lib/ruby_llm/aliases.json +295 -0
  44. data/lib/ruby_llm/aliases.rb +38 -0
  45. data/lib/ruby_llm/attachment.rb +220 -0
  46. data/lib/ruby_llm/chat.rb +816 -0
  47. data/lib/ruby_llm/chunk.rb +6 -0
  48. data/lib/ruby_llm/configuration.rb +78 -0
  49. data/lib/ruby_llm/connection.rb +126 -0
  50. data/lib/ruby_llm/content.rb +73 -0
  51. data/lib/ruby_llm/context.rb +29 -0
  52. data/lib/ruby_llm/embedding.rb +29 -0
  53. data/lib/ruby_llm/error.rb +84 -0
  54. data/lib/ruby_llm/image.rb +49 -0
  55. data/lib/ruby_llm/message.rb +86 -0
  56. data/lib/ruby_llm/mime_type.rb +71 -0
  57. data/lib/ruby_llm/model/info.rb +111 -0
  58. data/lib/ruby_llm/model/modalities.rb +22 -0
  59. data/lib/ruby_llm/model/pricing.rb +48 -0
  60. data/lib/ruby_llm/model/pricing_category.rb +46 -0
  61. data/lib/ruby_llm/model/pricing_tier.rb +33 -0
  62. data/lib/ruby_llm/model.rb +7 -0
  63. data/lib/ruby_llm/models.json +33198 -0
  64. data/lib/ruby_llm/models.rb +231 -0
  65. data/lib/ruby_llm/models_schema.json +168 -0
  66. data/lib/ruby_llm/moderation.rb +56 -0
  67. data/lib/ruby_llm/provider.rb +243 -0
  68. data/lib/ruby_llm/providers/anthropic/capabilities.rb +134 -0
  69. data/lib/ruby_llm/providers/anthropic/chat.rb +125 -0
  70. data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
  71. data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
  72. data/lib/ruby_llm/providers/anthropic/media.rb +92 -0
  73. data/lib/ruby_llm/providers/anthropic/models.rb +63 -0
  74. data/lib/ruby_llm/providers/anthropic/streaming.rb +45 -0
  75. data/lib/ruby_llm/providers/anthropic/tools.rb +109 -0
  76. data/lib/ruby_llm/providers/anthropic.rb +36 -0
  77. data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
  78. data/lib/ruby_llm/providers/bedrock/chat.rb +63 -0
  79. data/lib/ruby_llm/providers/bedrock/media.rb +61 -0
  80. data/lib/ruby_llm/providers/bedrock/models.rb +98 -0
  81. data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
  82. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +51 -0
  83. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +71 -0
  84. data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +67 -0
  85. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +80 -0
  86. data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +78 -0
  87. data/lib/ruby_llm/providers/bedrock/streaming.rb +18 -0
  88. data/lib/ruby_llm/providers/bedrock.rb +82 -0
  89. data/lib/ruby_llm/providers/deepseek/capabilities.rb +130 -0
  90. data/lib/ruby_llm/providers/deepseek/chat.rb +16 -0
  91. data/lib/ruby_llm/providers/deepseek.rb +30 -0
  92. data/lib/ruby_llm/providers/gemini/capabilities.rb +281 -0
  93. data/lib/ruby_llm/providers/gemini/chat.rb +454 -0
  94. data/lib/ruby_llm/providers/gemini/embeddings.rb +37 -0
  95. data/lib/ruby_llm/providers/gemini/images.rb +47 -0
  96. data/lib/ruby_llm/providers/gemini/media.rb +112 -0
  97. data/lib/ruby_llm/providers/gemini/models.rb +40 -0
  98. data/lib/ruby_llm/providers/gemini/streaming.rb +61 -0
  99. data/lib/ruby_llm/providers/gemini/tools.rb +198 -0
  100. data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
  101. data/lib/ruby_llm/providers/gemini.rb +37 -0
  102. data/lib/ruby_llm/providers/gpustack/chat.rb +27 -0
  103. data/lib/ruby_llm/providers/gpustack/media.rb +46 -0
  104. data/lib/ruby_llm/providers/gpustack/models.rb +90 -0
  105. data/lib/ruby_llm/providers/gpustack.rb +34 -0
  106. data/lib/ruby_llm/providers/mistral/capabilities.rb +155 -0
  107. data/lib/ruby_llm/providers/mistral/chat.rb +24 -0
  108. data/lib/ruby_llm/providers/mistral/embeddings.rb +33 -0
  109. data/lib/ruby_llm/providers/mistral/models.rb +48 -0
  110. data/lib/ruby_llm/providers/mistral.rb +32 -0
  111. data/lib/ruby_llm/providers/ollama/chat.rb +27 -0
  112. data/lib/ruby_llm/providers/ollama/media.rb +46 -0
  113. data/lib/ruby_llm/providers/ollama/models.rb +36 -0
  114. data/lib/ruby_llm/providers/ollama.rb +30 -0
  115. data/lib/ruby_llm/providers/openai/capabilities.rb +299 -0
  116. data/lib/ruby_llm/providers/openai/chat.rb +88 -0
  117. data/lib/ruby_llm/providers/openai/embeddings.rb +33 -0
  118. data/lib/ruby_llm/providers/openai/images.rb +38 -0
  119. data/lib/ruby_llm/providers/openai/media.rb +81 -0
  120. data/lib/ruby_llm/providers/openai/models.rb +39 -0
  121. data/lib/ruby_llm/providers/openai/moderation.rb +34 -0
  122. data/lib/ruby_llm/providers/openai/streaming.rb +46 -0
  123. data/lib/ruby_llm/providers/openai/tools.rb +98 -0
  124. data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
  125. data/lib/ruby_llm/providers/openai.rb +44 -0
  126. data/lib/ruby_llm/providers/openai_responses.rb +395 -0
  127. data/lib/ruby_llm/providers/openrouter/models.rb +73 -0
  128. data/lib/ruby_llm/providers/openrouter.rb +26 -0
  129. data/lib/ruby_llm/providers/perplexity/capabilities.rb +137 -0
  130. data/lib/ruby_llm/providers/perplexity/chat.rb +16 -0
  131. data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
  132. data/lib/ruby_llm/providers/perplexity.rb +48 -0
  133. data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
  134. data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
  135. data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
  136. data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
  137. data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
  138. data/lib/ruby_llm/providers/vertexai.rb +55 -0
  139. data/lib/ruby_llm/railtie.rb +35 -0
  140. data/lib/ruby_llm/responses_session.rb +77 -0
  141. data/lib/ruby_llm/stream_accumulator.rb +101 -0
  142. data/lib/ruby_llm/streaming.rb +153 -0
  143. data/lib/ruby_llm/tool.rb +209 -0
  144. data/lib/ruby_llm/tool_call.rb +22 -0
  145. data/lib/ruby_llm/tool_executors.rb +125 -0
  146. data/lib/ruby_llm/transcription.rb +35 -0
  147. data/lib/ruby_llm/utils.rb +91 -0
  148. data/lib/ruby_llm/version.rb +5 -0
  149. data/lib/ruby_llm.rb +140 -0
  150. data/lib/tasks/models.rake +525 -0
  151. data/lib/tasks/release.rake +67 -0
  152. data/lib/tasks/ruby_llm.rake +15 -0
  153. data/lib/tasks/vcr.rake +92 -0
  154. metadata +346 -0
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Mistral
6
+ # Determines capabilities for Mistral models
7
+ module Capabilities
8
+ module_function
9
+
10
+ def supports_streaming?(model_id)
11
+ !model_id.match?(/embed|moderation|ocr|transcriptions/)
12
+ end
13
+
14
+ def supports_tools?(model_id)
15
+ !model_id.match?(/embed|moderation|ocr|voxtral|transcriptions|mistral-(tiny|small)-(2312|2402)/)
16
+ end
17
+
18
+ def supports_vision?(model_id)
19
+ model_id.match?(/pixtral|mistral-small-(2503|2506)|mistral-medium/)
20
+ end
21
+
22
+ def supports_json_mode?(model_id)
23
+ !model_id.match?(/embed|moderation|ocr|voxtral|transcriptions/) && supports_tools?(model_id)
24
+ end
25
+
26
+ def format_display_name(model_id)
27
+ case model_id
28
+ when /mistral-large/ then 'Mistral Large'
29
+ when /mistral-medium/ then 'Mistral Medium'
30
+ when /mistral-small/ then 'Mistral Small'
31
+ when /ministral-3b/ then 'Ministral 3B'
32
+ when /ministral-8b/ then 'Ministral 8B'
33
+ when /codestral/ then 'Codestral'
34
+ when /pixtral-large/ then 'Pixtral Large'
35
+ when /pixtral-12b/ then 'Pixtral 12B'
36
+ when /mistral-embed/ then 'Mistral Embed'
37
+ when /mistral-moderation/ then 'Mistral Moderation'
38
+ else model_id.split('-').map(&:capitalize).join(' ')
39
+ end
40
+ end
41
+
42
+ def model_family(model_id)
43
+ case model_id
44
+ when /mistral-large/ then 'mistral-large'
45
+ when /mistral-medium/ then 'mistral-medium'
46
+ when /mistral-small/ then 'mistral-small'
47
+ when /ministral/ then 'ministral'
48
+ when /codestral/ then 'codestral'
49
+ when /pixtral/ then 'pixtral'
50
+ when /mistral-embed/ then 'mistral-embed'
51
+ when /mistral-moderation/ then 'mistral-moderation'
52
+ else 'mistral'
53
+ end
54
+ end
55
+
56
+ def context_window_for(_model_id)
57
+ 32_768
58
+ end
59
+
60
+ def max_tokens_for(_model_id)
61
+ 8192
62
+ end
63
+
64
+ def modalities_for(model_id)
65
+ case model_id
66
+ when /pixtral/
67
+ {
68
+ input: %w[text image],
69
+ output: ['text']
70
+ }
71
+ when /embed/
72
+ {
73
+ input: ['text'],
74
+ output: ['embeddings']
75
+ }
76
+ else
77
+ {
78
+ input: ['text'],
79
+ output: ['text']
80
+ }
81
+ end
82
+ end
83
+
84
+ def capabilities_for(model_id) # rubocop:disable Metrics/PerceivedComplexity
85
+ case model_id
86
+ when /moderation/ then ['moderation']
87
+ when /voxtral.*transcribe/ then ['transcription']
88
+ when /ocr/ then ['vision']
89
+ else
90
+ capabilities = []
91
+ capabilities << 'streaming' if supports_streaming?(model_id)
92
+ capabilities << 'function_calling' if supports_tools?(model_id)
93
+ capabilities << 'structured_output' if supports_json_mode?(model_id)
94
+ capabilities << 'vision' if supports_vision?(model_id)
95
+
96
+ capabilities << 'reasoning' if model_id.match?(/magistral/)
97
+ capabilities << 'batch' unless model_id.match?(/voxtral|ocr|embed|moderation/)
98
+ capabilities << 'fine_tuning' if model_id.match?(/mistral-(small|medium|large)|devstral/)
99
+ capabilities << 'distillation' if model_id.match?(/ministral/)
100
+ capabilities << 'predicted_outputs' if model_id.match?(/codestral/)
101
+
102
+ capabilities.uniq
103
+ end
104
+ end
105
+
106
+ def pricing_for(_model_id)
107
+ {
108
+ input: 0.0,
109
+ output: 0.0
110
+ }
111
+ end
112
+
113
+ def release_date_for(model_id)
114
+ case model_id
115
+ when 'open-mistral-7b', 'mistral-tiny' then '2023-09-27'
116
+ when 'mistral-medium-2312', 'mistral-small-2312', 'mistral-small',
117
+ 'open-mixtral-8x7b', 'mistral-tiny-2312' then '2023-12-11'
118
+
119
+ when 'mistral-embed' then '2024-01-11'
120
+ when 'mistral-large-2402', 'mistral-small-2402' then '2024-02-26'
121
+ when 'open-mixtral-8x22b', 'open-mixtral-8x22b-2404' then '2024-04-17'
122
+ when 'codestral-2405' then '2024-05-22'
123
+ when 'codestral-mamba-2407', 'codestral-mamba-latest', 'open-codestral-mamba' then '2024-07-16'
124
+ when 'open-mistral-nemo', 'open-mistral-nemo-2407', 'mistral-tiny-2407',
125
+ 'mistral-tiny-latest' then '2024-07-18'
126
+ when 'mistral-large-2407' then '2024-07-24'
127
+ when 'pixtral-12b-2409', 'pixtral-12b-latest', 'pixtral-12b' then '2024-09-17'
128
+ when 'mistral-small-2409' then '2024-09-18'
129
+ when 'ministral-3b-2410', 'ministral-3b-latest', 'ministral-8b-2410',
130
+ 'ministral-8b-latest' then '2024-10-16'
131
+ when 'pixtral-large-2411', 'pixtral-large-latest', 'mistral-large-pixtral-2411' then '2024-11-12'
132
+ when 'mistral-large-2411', 'mistral-large-latest', 'mistral-large' then '2024-11-20'
133
+ when 'codestral-2411-rc5', 'mistral-moderation-2411', 'mistral-moderation-latest' then '2024-11-26'
134
+ when 'codestral-2412' then '2024-12-17'
135
+
136
+ when 'mistral-small-2501' then '2025-01-13'
137
+ when 'codestral-2501' then '2025-01-14'
138
+ when 'mistral-saba-2502', 'mistral-saba-latest' then '2025-02-18'
139
+ when 'mistral-small-2503' then '2025-03-03'
140
+ when 'mistral-ocr-2503' then '2025-03-21'
141
+ when 'mistral-medium', 'mistral-medium-latest', 'mistral-medium-2505' then '2025-05-06'
142
+ when 'codestral-embed', 'codestral-embed-2505' then '2025-05-21'
143
+ when 'mistral-ocr-2505', 'mistral-ocr-latest' then '2025-05-23'
144
+ when 'devstral-small-2505' then '2025-05-28'
145
+ when 'mistral-small-2506', 'mistral-small-latest', 'magistral-medium-2506',
146
+ 'magistral-medium-latest' then '2025-06-10'
147
+ when 'devstral-small-2507', 'devstral-small-latest', 'devstral-medium-2507',
148
+ 'devstral-medium-latest' then '2025-07-09'
149
+ when 'codestral-2508', 'codestral-latest' then '2025-08-30'
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Mistral
6
+ # Chat methods for Mistral API
7
+ module Chat
8
+ module_function
9
+
10
+ def format_role(role)
11
+ role.to_s
12
+ end
13
+
14
+ # rubocop:disable Metrics/ParameterLists
15
+ def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil)
16
+ payload = super
17
+ payload.delete(:stream_options)
18
+ payload
19
+ end
20
+ # rubocop:enable Metrics/ParameterLists
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Mistral
6
+ # Embeddings methods for Mistral API
7
+ module Embeddings
8
+ module_function
9
+
10
+ def embedding_url(...)
11
+ 'embeddings'
12
+ end
13
+
14
+ def render_embedding_payload(text, model:, dimensions:) # rubocop:disable Lint/UnusedMethodArgument
15
+ {
16
+ model: model,
17
+ input: text
18
+ }
19
+ end
20
+
21
+ def parse_embedding_response(response, model:, text:)
22
+ data = response.body
23
+ input_tokens = data.dig('usage', 'prompt_tokens') || 0
24
+ vectors = data['data'].map { |d| d['embedding'] }
25
+
26
+ vectors = vectors.first if vectors.length == 1 && !text.is_a?(Array)
27
+
28
+ Embedding.new(vectors:, model:, input_tokens:)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Mistral
6
+ # Model information for Mistral
7
+ module Models
8
+ module_function
9
+
10
+ def models_url
11
+ 'models'
12
+ end
13
+
14
+ def headers(config)
15
+ {
16
+ 'Authorization' => "Bearer #{config.mistral_api_key}"
17
+ }
18
+ end
19
+
20
+ def parse_list_models_response(response, slug, capabilities)
21
+ Array(response.body['data']).map do |model_data|
22
+ model_id = model_data['id']
23
+
24
+ release_date = capabilities.release_date_for(model_id)
25
+ created_at = release_date ? Time.parse(release_date) : nil
26
+
27
+ Model::Info.new(
28
+ id: model_id,
29
+ name: capabilities.format_display_name(model_id),
30
+ provider: slug,
31
+ family: capabilities.model_family(model_id),
32
+ created_at: created_at,
33
+ context_window: capabilities.context_window_for(model_id),
34
+ max_output_tokens: capabilities.max_tokens_for(model_id),
35
+ modalities: capabilities.modalities_for(model_id),
36
+ capabilities: capabilities.capabilities_for(model_id),
37
+ pricing: capabilities.pricing_for(model_id),
38
+ metadata: {
39
+ object: model_data['object'],
40
+ owned_by: model_data['owned_by']
41
+ }
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ # Mistral API integration.
6
+ class Mistral < OpenAI
7
+ include Mistral::Chat
8
+ include Mistral::Models
9
+ include Mistral::Embeddings
10
+
11
+ def api_base
12
+ 'https://api.mistral.ai/v1'
13
+ end
14
+
15
+ def headers
16
+ {
17
+ 'Authorization' => "Bearer #{@config.mistral_api_key}"
18
+ }
19
+ end
20
+
21
+ class << self
22
+ def capabilities
23
+ Mistral::Capabilities
24
+ end
25
+
26
+ def configuration_requirements
27
+ %i[mistral_api_key]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Ollama
6
+ # Chat methods of the Ollama API integration
7
+ module Chat
8
+ module_function
9
+
10
+ def format_messages(messages)
11
+ messages.map do |msg|
12
+ {
13
+ role: format_role(msg.role),
14
+ content: Ollama::Media.format_content(msg.content),
15
+ tool_calls: format_tool_calls(msg.tool_calls),
16
+ tool_call_id: msg.tool_call_id
17
+ }.compact
18
+ end
19
+ end
20
+
21
+ def format_role(role)
22
+ role.to_s
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Ollama
6
+ # Handles formatting of media content (images, audio) for Ollama APIs
7
+ module Media
8
+ extend OpenAI::Media
9
+
10
+ module_function
11
+
12
+ def format_content(content)
13
+ return content.value if content.is_a?(RubyLLM::Content::Raw)
14
+ return content.to_json if content.is_a?(Hash) || content.is_a?(Array)
15
+ return content unless content.is_a?(Content)
16
+
17
+ parts = []
18
+ parts << format_text(content.text) if content.text
19
+
20
+ content.attachments.each do |attachment|
21
+ case attachment.type
22
+ when :image
23
+ parts << Ollama::Media.format_image(attachment)
24
+ when :text
25
+ parts << format_text_file(attachment)
26
+ else
27
+ raise UnsupportedAttachmentError, attachment.mime_type
28
+ end
29
+ end
30
+
31
+ parts
32
+ end
33
+
34
+ def format_image(image)
35
+ {
36
+ type: 'image_url',
37
+ image_url: {
38
+ url: image.for_llm,
39
+ detail: 'auto'
40
+ }
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class Ollama
6
+ # Models methods for the Ollama API integration
7
+ module Models
8
+ def models_url
9
+ 'models'
10
+ end
11
+
12
+ def parse_list_models_response(response, slug, _capabilities)
13
+ data = response.body['data'] || []
14
+ data.map do |model|
15
+ Model::Info.new(
16
+ id: model['id'],
17
+ name: model['id'],
18
+ provider: slug,
19
+ family: 'ollama',
20
+ created_at: model['created'] ? Time.at(model['created']) : nil,
21
+ modalities: {
22
+ input: %w[text image],
23
+ output: %w[text]
24
+ },
25
+ capabilities: %w[streaming function_calling structured_output vision],
26
+ pricing: {},
27
+ metadata: {
28
+ owned_by: model['owned_by']
29
+ }
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ # Ollama API integration.
6
+ class Ollama < OpenAI
7
+ include Ollama::Chat
8
+ include Ollama::Media
9
+ include Ollama::Models
10
+
11
+ def api_base
12
+ @config.ollama_api_base
13
+ end
14
+
15
+ def headers
16
+ {}
17
+ end
18
+
19
+ class << self
20
+ def configuration_requirements
21
+ %i[ollama_api_base]
22
+ end
23
+
24
+ def local?
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end