llm_chain 0.5.3 → 0.5.5

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -2
  3. data/README.md +15 -6
  4. data/examples/quick_demo.rb +1 -1
  5. data/examples/tools_example.rb +2 -2
  6. data/exe/llm-chain +7 -4
  7. data/lib/llm_chain/builders/memory_context.rb +24 -0
  8. data/lib/llm_chain/builders/prompt.rb +26 -0
  9. data/lib/llm_chain/builders/rag_documents.rb +25 -0
  10. data/lib/llm_chain/builders/retriever_context.rb +25 -0
  11. data/lib/llm_chain/builders/tool_responses.rb +27 -0
  12. data/lib/llm_chain/chain.rb +89 -88
  13. data/lib/llm_chain/client_registry.rb +2 -0
  14. data/lib/llm_chain/clients/base.rb +24 -2
  15. data/lib/llm_chain/clients/deepseek_coder_v2.rb +32 -0
  16. data/lib/llm_chain/configuration_validator.rb +1 -1
  17. data/lib/llm_chain/interfaces/builders/memory_context_builder.rb +20 -0
  18. data/lib/llm_chain/interfaces/builders/prompt_builder.rb +23 -0
  19. data/lib/llm_chain/interfaces/builders/rag_documents_builder.rb +20 -0
  20. data/lib/llm_chain/interfaces/builders/retriever_context_builder.rb +22 -0
  21. data/lib/llm_chain/interfaces/builders/tool_responses_builder.rb +20 -0
  22. data/lib/llm_chain/interfaces/memory.rb +38 -0
  23. data/lib/llm_chain/interfaces/tool_manager.rb +87 -0
  24. data/lib/llm_chain/memory/array.rb +18 -1
  25. data/lib/llm_chain/memory/redis.rb +20 -3
  26. data/lib/llm_chain/system_diagnostics.rb +73 -0
  27. data/lib/llm_chain/tools/base.rb +103 -0
  28. data/lib/llm_chain/tools/base_tool.rb +6 -76
  29. data/lib/llm_chain/tools/calculator.rb +118 -45
  30. data/lib/llm_chain/tools/code_interpreter.rb +43 -43
  31. data/lib/llm_chain/tools/date_time.rb +58 -0
  32. data/lib/llm_chain/tools/tool_manager.rb +46 -88
  33. data/lib/llm_chain/tools/tool_manager_factory.rb +44 -0
  34. data/lib/llm_chain/tools/web_search.rb +168 -336
  35. data/lib/llm_chain/version.rb +1 -1
  36. data/lib/llm_chain.rb +58 -56
  37. metadata +19 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c94520a98ace3d2bb61ca2b470af041e0f95d6818120ce01dfc5a9cfe76316b
4
- data.tar.gz: 5155099cdf777ecaed3a30c9da1e7e200476258369ef3b976967888daf001876
3
+ metadata.gz: 1963821b40f17c255520b55422bbc7cf6603be88555a99403a8ce24b1c4914f4
4
+ data.tar.gz: f27902811ba56ee4b1d9da09b22bd99204f01573b848c16e6a9e368f68eb40a4
5
5
  SHA512:
6
- metadata.gz: b37c6ed16c6b6a66d1f9c6e2a62717e576c5211aba605b98b97e5ba54b436d9a38602aabcedf34a23963ee034d6332dadefe03fa6c6a5d4d3f4941b2df9efd88
7
- data.tar.gz: 4e5c3b6921e4f0656ea8266ca67a00dbcbc8c11736573df9172b8cb2d8821d3799f87085f31e7975d916d56c29f10a4637c6df5f102520fc2da3f5284143ef79
6
+ metadata.gz: 4a3611448d4e85e2f3bcec56f0c8d1c599bcf9da467a963b23fcffe6466e28e1d99dcae34449bffde4bb4672b1be5a6fe4dc35146c97dba8d968ab1fd6b77af1
7
+ data.tar.gz: e918646ffa824e762d7cefa92da2effcb5a23b412202c6d967e99e6d719035fc89834c0c6f65aea68fe131a92e41474bcf20d93d6ec1cdc2900943f0d51faf39
data/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.5] - 2025-07-17
11
+
12
+ ### Changed
13
+ * **Major refactor:**
14
+ * Core classes have been extensively refactored for improved modularity, clarity, and maintainability:
15
+ * Adopted SOLID principles throughout the codebase.
16
+ * Extracted interfaces for memory, tool management, and all builder components, now located in a dedicated `interfaces` namespace.
17
+ * Introduced a `builders` folder and builder pattern for prompt, memory context, tool responses, RAG documents, and retriever context.
18
+ * Improved dependency injection and separation of concerns, making the codebase easier to extend and test.
19
+ * Centralized error handling and configuration validation.
20
+ * Enhanced documentation and type signatures for all major classes.
21
+ * The public API remains minimal and idiomatic, with extensibility via interfaces and factories.
22
+ * `Chain#ask` method rewritten following Ruby best practices: now declarative, each pipeline stage is a private method, code is cleaner and easier to extend.
23
+ * All ToolManager creation and configuration is now done via a dedicated factory: `ToolManagerFactory`. Old calls (`ToolManager.create_default_toolset`, `ToolManager.from_config`) have been replaced with factory methods.
24
+
25
+ ## [0.5.4] - 2025-07-08
26
+
27
+ ### Added
28
+ * **Deepseek-Coder-V2 Client** - Support for Deepseek-Coder-V2 models via Ollama
29
+ * Available variants: `deepseek-coder-v2:latest`, `deepseek-coder-v2:16b`, `deepseek-coder-v2:236b`
30
+ * Optimized settings for code generation tasks (low temperature, large context)
31
+ * Integrated with existing tool ecosystem (CodeInterpreter, WebSearch, Calculator)
32
+ * Full compatibility with Chain, ClientRegistry, and CLI
33
+
34
+ ### Changed
35
+ * Updated model support table in README with Deepseek-Coder-V2 information
36
+
10
37
  ## [0.5.3] - 2025-07-05
11
38
 
12
39
  ### Added
@@ -83,8 +110,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
83
110
  ### Changed
84
111
  - Initial stable release with core functionality
85
112
 
86
- [Unreleased]: https://github.com/FuryCow/llm_chain/compare/v0.5.3...HEAD
113
+ [Unreleased]: https://github.com/FuryCow/llm_chain/compare/v0.5.4...HEAD
114
+ [0.5.4]: https://github.com/FuryCow/llm_chain/compare/v0.5.3...v0.5.4
87
115
  [0.5.3]: https://github.com/FuryCow/llm_chain/compare/v0.5.2...v0.5.3
88
116
  [0.5.2]: https://github.com/FuryCow/llm_chain/compare/v0.5.1...v0.5.2
89
117
  [0.5.1]: https://github.com/FuryCow/llm_chain/compare/v0.5.0...v0.5.1
90
- [0.5.0]: https://github.com/FuryCow/llm_chain/releases/tag/v0.5.0
118
+ [0.5.0]: https://github.com/FuryCow/llm_chain/releases/tag/v0.5.0
data/README.md CHANGED
@@ -159,7 +159,7 @@ chain = LLMChain.quick_chain(
159
159
  # Manual validation
160
160
  LLMChain::ConfigurationValidator.validate_chain_config!(
161
161
  model: "qwen3:1.7b",
162
- tools: LLMChain::Tools::ToolManager.create_default_toolset
162
+ tools: LLMChain::Tools::ToolManagerFactory.create_default_toolset
163
163
  )
164
164
  ```
165
165
 
@@ -182,7 +182,7 @@ chain.ask("Execute code: puts (1..10).sum")
182
182
  # 💻 Result: 55
183
183
 
184
184
  # Traditional setup
185
- tool_manager = LLMChain::Tools::ToolManager.create_default_toolset
185
+ tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
186
186
  chain = LLMChain::Chain.new(
187
187
  model: "qwen3:1.7b",
188
188
  tools: tool_manager
@@ -342,6 +342,7 @@ end
342
342
 
343
343
  # Usage
344
344
  weather = WeatherTool.new(api_key: "your-key")
345
+ tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
345
346
  tool_manager.register_tool(weather)
346
347
  ```
347
348
 
@@ -353,6 +354,7 @@ tool_manager.register_tool(weather)
353
354
  | **Qwen/Qwen2** | Ollama | ✅ Supported | 0.5B - 72B parameters |
354
355
  | **LLaMA2/3** | Ollama | ✅ Supported | 7B, 13B, 70B |
355
356
  | **Gemma** | Ollama | ✅ Supported | 2B, 7B, 9B, 27B |
357
+ | **Deepseek-Coder-V2** | Ollama | ✅ Supported | 16B, 236B - Code specialist |
356
358
  | **Mistral/Mixtral** | Ollama | 🔄 In development | 7B, 8x7B |
357
359
  | **Claude** | Anthropic | 🔄 Planned | Haiku, Sonnet, Opus |
358
360
  | **Command R+** | Cohere | 🔄 Planned | Optimized for RAG |
@@ -375,6 +377,13 @@ llama_chain = LLMChain::Chain.new(
375
377
  temperature: 0.8,
376
378
  top_p: 0.95
377
379
  )
380
+
381
+ # Deepseek-Coder-V2 for code tasks
382
+ deepseek_chain = LLMChain::Chain.new(model: "deepseek-coder-v2:16b")
383
+
384
+ # Direct client usage
385
+ deepseek_client = LLMChain::Clients::DeepseekCoderV2.new(model: "deepseek-coder-v2:16b")
386
+ response = deepseek_client.chat("Create a Ruby method to sort an array")
378
387
  ```
379
388
 
380
389
  ## 💾 Memory System
@@ -480,7 +489,7 @@ chain.ask("Tell me about Ruby history", stream: true) do |chunk|
480
489
  end
481
490
 
482
491
  # Streaming with tools
483
- tool_manager = LLMChain::Tools::ToolManager.create_default_toolset
492
+ tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
484
493
  chain = LLMChain::Chain.new(
485
494
  model: "qwen3:1.7b",
486
495
  tools: tool_manager
@@ -535,7 +544,7 @@ tools_config = [
535
544
  }
536
545
  ]
537
546
 
538
- tool_manager = LLMChain::Tools::ToolManager.from_config(tools_config)
547
+ tool_manager = LLMChain::Tools::ToolManagerFactory.from_config(tools_config)
539
548
  ```
540
549
 
541
550
  ### Client Settings
@@ -625,7 +634,7 @@ require 'llm_chain'
625
634
 
626
635
  class ChatBot
627
636
  def initialize
628
- @tool_manager = LLMChain::Tools::ToolManager.create_default_toolset
637
+ @tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
629
638
  @memory = LLMChain::Memory::Array.new(max_size: 20)
630
639
  @chain = LLMChain::Chain.new(
631
640
  model: "qwen3:1.7b",
@@ -660,7 +669,7 @@ bot.chat_loop
660
669
  ```ruby
661
670
  data_chain = LLMChain::Chain.new(
662
671
  model: "qwen3:7b",
663
- tools: LLMChain::Tools::ToolManager.create_default_toolset
672
+ tools: LLMChain::Tools::ToolManagerFactory.create_default_toolset
664
673
  )
665
674
 
666
675
  # Analyze CSV data
@@ -71,7 +71,7 @@ end
71
71
  # 5. Chain with tools
72
72
  puts "\n5. 🛠️ Chain with automatic tools"
73
73
  begin
74
- tool_manager = LLMChain::Tools::ToolManager.create_default_toolset
74
+ tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
75
75
  smart_chain = LLMChain::Chain.new(
76
76
  model: "qwen3:1.7b",
77
77
  tools: tool_manager,
@@ -101,7 +101,7 @@ end
101
101
 
102
102
  # 4. Tool Manager Usage
103
103
  puts "\n4. 🎯 Tool Manager"
104
- tool_manager = LLMChain::Tools::ToolManager.create_default_toolset
104
+ tool_manager = LLMChain::Tools::ToolManagerFactory.create_default_toolset
105
105
 
106
106
  puts "Registered tools: #{tool_manager.list_tools.map(&:name).join(', ')}"
107
107
 
@@ -243,7 +243,7 @@ tools_config = [
243
243
  }
244
244
  ]
245
245
 
246
- config_tool_manager = LLMChain::Tools::ToolManager.from_config(tools_config)
246
+ config_tool_manager = LLMChain::Tools::ToolManagerFactory.from_config(tools_config)
247
247
  puts "Tools from config: #{config_tool_manager.list_tools.map(&:name).join(', ')}"
248
248
 
249
249
  # Test configuration-based setup
data/exe/llm-chain CHANGED
@@ -2,15 +2,18 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Load Bundler only if running from the development repo (Gemfile present)
5
- if File.exist?(File.expand_path("../../Gemfile", __dir__))
5
+ if File.exist?(File.expand_path("../Gemfile", __dir__))
6
+ # In development mode, always load local version
7
+ require_relative "../lib/llm_chain"
6
8
  begin
7
9
  require "bundler/setup"
8
10
  rescue LoadError
9
11
  warn "[llm-chain] Bundler not available; continuing without it"
10
12
  end
13
+ else
14
+ # In production mode, load installed gem
15
+ require "llm_chain"
11
16
  end
12
-
13
- require "llm_chain"
14
17
  require "optparse"
15
18
  require "readline"
16
19
 
@@ -109,7 +112,7 @@ when "tools"
109
112
  sub = ARGV.shift
110
113
  case sub
111
114
  when "list"
112
- tm = LLMChain::Tools::ToolManager.create_default_toolset
115
+ tm = LLMChain::Tools::ToolManagerFactory.create_default_toolset
113
116
  puts tm.tools_description
114
117
  else
115
118
  warn "Unknown tools subcommand"; warn USAGE
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/builders/memory_context_builder'
4
+
5
+ module LLMChain
6
+ module Builders
7
+ # Production implementation of memory context builder for LLMChain.
8
+ # Formats conversation history for inclusion in the prompt.
9
+ class MemoryContext < Interfaces::Builders::MemoryContext
10
+ # Build the memory context string for the prompt.
11
+ # @param memory_history [Array<Hash>] conversation history
12
+ # @return [String] formatted memory context
13
+ def build(memory_history)
14
+ return "" if memory_history.nil? || memory_history.empty?
15
+ parts = ["Dialogue history:"]
16
+ memory_history.each do |item|
17
+ parts << "User: #{item[:prompt]}"
18
+ parts << "Assistant: #{item[:response]}"
19
+ end
20
+ parts.join("\n")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/builders/prompt_builder'
4
+
5
+ module LLMChain
6
+ module Builders
7
+ # Production implementation of prompt builder for LLMChain.
8
+ # Assembles the final prompt from memory, tools, RAG, and user prompt.
9
+ class Prompt < Interfaces::Builders::Prompt
10
+ # Build the final prompt for the LLM.
11
+ # @param memory_context [String]
12
+ # @param tool_responses [String]
13
+ # @param rag_documents [String]
14
+ # @param prompt [String]
15
+ # @return [String] final prompt for LLM
16
+ def build(memory_context:, tool_responses:, rag_documents:, prompt:)
17
+ parts = []
18
+ parts << memory_context if memory_context && !memory_context.empty?
19
+ parts << rag_documents if rag_documents && !rag_documents.empty?
20
+ parts << tool_responses if tool_responses && !tool_responses.empty?
21
+ parts << "Current question: #{prompt}"
22
+ parts.join("\n\n")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/builders/rag_documents_builder'
4
+
5
+ module LLMChain
6
+ module Builders
7
+ # Production implementation of RAG documents builder for LLMChain.
8
+ # Formats retrieved documents for inclusion in the prompt.
9
+ class RagDocuments < Interfaces::Builders::RagDocuments
10
+ # Build the RAG documents string for the prompt.
11
+ # @param rag_documents [Array<Hash>] list of retrieved documents
12
+ # @return [String] formatted RAG context
13
+ def build(rag_documents)
14
+ return "" if rag_documents.nil? || rag_documents.empty?
15
+ parts = ["Relevant documents:"]
16
+ rag_documents.each_with_index do |doc, i|
17
+ parts << "Document #{i + 1}: #{doc['content'] || doc[:content]}"
18
+ meta = doc['metadata'] || doc[:metadata]
19
+ parts << "Metadata: #{meta.to_json}" if meta
20
+ end
21
+ parts.join("\n")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/builders/retriever_context_builder'
4
+
5
+ module LLMChain
6
+ module Builders
7
+ # Production implementation of retriever context builder for LLMChain.
8
+ # Retrieves and formats RAG context documents.
9
+ class RetrieverContext < Interfaces::Builders::RetrieverContext
10
+ # Retrieve and format RAG context documents.
11
+ # @param retriever [Object] retriever instance (must respond to #search)
12
+ # @param query [String] user query
13
+ # @param options [Hash]
14
+ # @return [Array<Hash>] list of retrieved documents
15
+ def retrieve(retriever, query, options = {})
16
+ return [] unless retriever && retriever.respond_to?(:search)
17
+ limit = options[:limit] || 3
18
+ retriever.search(query, limit: limit)
19
+ rescue => e
20
+ warn "[RetrieverContext] Error retrieving context: #{e.message}"
21
+ []
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/builders/tool_responses_builder'
4
+
5
+ module LLMChain
6
+ module Builders
7
+ # Production implementation of tool responses builder for LLMChain.
8
+ # Formats tool results for inclusion in the prompt.
9
+ class ToolResponses < Interfaces::Builders::ToolResponses
10
+ # Build the tool responses string for the prompt.
11
+ # @param tool_results [Hash] tool name => result
12
+ # @return [String] formatted tool responses
13
+ def build(tool_results)
14
+ return "" if tool_results.nil? || tool_results.empty?
15
+ parts = ["Tool results:"]
16
+ tool_results.each do |name, response|
17
+ if response.is_a?(Hash) && response[:formatted]
18
+ parts << "#{name}: #{response[:formatted]}"
19
+ else
20
+ parts << "#{name}: #{response}"
21
+ end
22
+ end
23
+ parts.join("\n")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,21 +1,49 @@
1
1
  require 'json'
2
+ require_relative 'memory/array'
3
+ require_relative 'tools/tool_manager_factory'
4
+ require_relative 'builders/prompt'
5
+ require_relative 'builders/memory_context'
6
+ require_relative 'builders/tool_responses'
7
+ require_relative 'builders/rag_documents'
8
+ require_relative 'builders/retriever_context'
2
9
 
3
10
  module LLMChain
11
+ # High-level interface that ties together an LLM client, optional memory,
12
+ # tool system and RAG retriever. Use {LLMChain.quick_chain} for the common
13
+ # defaults or build manually via this class.
4
14
  class Chain
15
+ # @return [String] selected model identifier
16
+ # @return [Object] memory backend
17
+ # @return [Array, Tools::ToolManager, nil] tools collection
18
+ # @return [Object, nil] RAG retriever
5
19
  attr_reader :model, :memory, :tools, :retriever
6
20
 
7
- # @param model [String] Имя модели (gpt-4, llama3 и т.д.)
8
- # @param memory [#recall, #store] Объект памяти
9
- # @param tools [Array<Tool>] Массив инструментов
10
- # @param retriever [#search] RAG-ретривер (Weaviate, Pinecone и т.д.)
11
- # @param client_options [Hash] Опции для клиента LLM
12
- def initialize(model: nil, memory: nil, tools: [], retriever: nil, validate_config: true, **client_options)
13
- # Валидация конфигурации (можно отключить через validate_config: false)
21
+ # Create a new chain.
22
+ #
23
+ # @param model [String] model name, e.g. "gpt-4" or "qwen3:1.7b"
24
+ # @param memory [LLMChain::Interfaces::Memory] conversation memory backend
25
+ # @param tools [LLMChain::Interfaces::ToolManager, Array, true, false, nil]
26
+ # @param retriever [#search, false, nil] document retriever for RAG
27
+ # @param prompt_builder [LLMChain::Interfaces::Builders::Prompt]
28
+ # @param memory_context_builder [LLMChain::Interfaces::Builders::MemoryContext]
29
+ # @param tool_responses_builder [LLMChain::Interfaces::Builders::ToolResponses]
30
+ # @param rag_documents_builder [LLMChain::Interfaces::Builders::RagDocuments]
31
+ # @param retriever_context_builder [LLMChain::Interfaces::Builders::RetrieverContext]
32
+ # @param validate_config [Boolean] run {ConfigurationValidator}
33
+ # @param client_options [Hash] extra LLM-client options (api_key etc.)
34
+ def initialize(
35
+ model: nil,
36
+ memory: nil,
37
+ tools: true,
38
+ retriever: false,
39
+ validate_config: true,
40
+ **client_options
41
+ )
14
42
  if validate_config
15
43
  begin
16
44
  ConfigurationValidator.validate_chain_config!(
17
- model: model,
18
- tools: tools,
45
+ model: model,
46
+ tools: tools,
19
47
  memory: memory,
20
48
  retriever: retriever,
21
49
  **client_options
@@ -27,7 +55,18 @@ module LLMChain
27
55
 
28
56
  @model = model
29
57
  @memory = memory || Memory::Array.new
30
- @tools = tools
58
+ @tools =
59
+ if tools == true
60
+ Tools::ToolManagerFactory.create_default_toolset
61
+ elsif tools.is_a?(Array)
62
+ Tools::ToolManager.new(tools: tools)
63
+ elsif tools.is_a?(Tools::ToolManager)
64
+ tools
65
+ elsif tools.is_a?(Hash) && tools[:config]
66
+ Tools::ToolManagerFactory.from_config(tools[:config])
67
+ else
68
+ nil
69
+ end
31
70
  @retriever = if retriever.nil?
32
71
  Embeddings::Clients::Local::WeaviateRetriever.new
33
72
  elsif retriever == false
@@ -36,101 +75,63 @@ module LLMChain
36
75
  retriever
37
76
  end
38
77
  @client = ClientRegistry.client_for(model, **client_options)
78
+
79
+ # Always use default builders
80
+ @prompt_builder = Builders::Prompt.new
81
+ @memory_context_builder = Builders::MemoryContext.new
82
+ @tool_responses_builder = Builders::ToolResponses.new
83
+ @rag_documents_builder = Builders::RagDocuments.new
84
+ @retriever_context_builder = Builders::RetrieverContext.new
39
85
  end
40
86
 
41
- # Основной метод для взаимодействия с цепочкой
42
- # @param prompt [String] Входной промпт
43
- # @param stream [Boolean] Использовать ли потоковый вывод
44
- # @param rag_context [Boolean] Использовать ли RAG-контекст
45
- # @param rag_options [Hash] Опции для RAG-поиска
46
- # @yield [String] Передает чанки ответа если stream=true
87
+ # Main inference entrypoint.
88
+ #
89
+ # @param prompt [String] user prompt
90
+ # @param stream [Boolean] if `true` yields chunks and returns full string
91
+ # @param rag_context [Boolean] whether to include retriever context
92
+ # @param rag_options [Hash] options passed to retriever (eg. :limit)
93
+ # @yield [String] chunk — called when `stream` is true
94
+ # @return [String] assistant response
47
95
  def ask(prompt, stream: false, rag_context: false, rag_options: {}, &block)
48
- context = collect_context(prompt, rag_context, rag_options)
49
- full_prompt = build_prompt(prompt: prompt, **context)
96
+ memory_context = build_memory_context(prompt)
97
+ tool_responses = build_tool_responses(prompt)
98
+ rag_documents = build_rag_documents(prompt, rag_context, rag_options)
99
+ full_prompt = build_full_prompt(prompt, memory_context, tool_responses, rag_documents)
100
+
50
101
  response = generate_response(full_prompt, stream: stream, &block)
51
- memory.store(prompt, response)
102
+ store_memory(prompt, response)
52
103
  response
53
104
  end
54
105
 
55
- def collect_context(prompt, rag_context, rag_options)
56
- context = memory.recall(prompt)
57
- tool_responses = process_tools(prompt)
58
- rag_documents = retrieve_rag_context(prompt, rag_options) if rag_context
59
- { memory_context: context, tool_responses: tool_responses, rag_documents: rag_documents }
60
- end
61
-
62
106
  private
63
107
 
64
- def retrieve_rag_context(query, options = {})
65
- return [] unless @retriever
66
-
67
- limit = options[:limit] || 3
68
- @retriever.search(query, limit: limit)
69
- rescue => e
70
- raise Error, "Cannot retrieve rag context"
108
+ def build_memory_context(prompt)
109
+ history = @memory&.recall(prompt)
110
+ @memory_context_builder.build(history)
71
111
  end
72
112
 
73
- def process_tools(prompt)
74
- return {} if @tools.nil? || (@tools.respond_to?(:empty?) && @tools.empty?)
75
-
76
- # Если @tools - это ToolManager
77
- if @tools.respond_to?(:auto_execute)
78
- @tools.auto_execute(prompt)
79
- elsif @tools.is_a?(Array)
80
- # Старая логика для массива инструментов
81
- @tools.each_with_object({}) do |tool, acc|
82
- if tool.match?(prompt)
83
- response = tool.call(prompt)
84
- acc[tool.name] = response unless response.nil?
85
- end
86
- end
87
- else
88
- {}
89
- end
90
- end
91
-
92
- def build_prompt(prompt:, memory_context: nil, tool_responses: {}, rag_documents: nil)
93
- parts = []
94
- parts << build_memory_context(memory_context) if memory_context&.any?
95
- parts << build_rag_documents(rag_documents) if rag_documents&.any?
96
- parts << build_tool_responses(tool_responses) unless tool_responses.empty?
97
- parts << "Сurrent question: #{prompt}"
98
- parts.join("\n\n")
113
+ def build_tool_responses(prompt)
114
+ results = @tools&.execute_tools(prompt) || {}
115
+ @tool_responses_builder.build(results)
99
116
  end
100
117
 
101
- def build_memory_context(memory_context)
102
- parts = ["Dialogue history:"]
103
- memory_context.each do |item|
104
- parts << "User: #{item[:prompt]}"
105
- parts << "Assistant: #{item[:response]}"
106
- end
107
- parts.join("\n")
118
+ def build_rag_documents(prompt, rag_context, rag_options)
119
+ return "" unless rag_context && @retriever
120
+ docs = @retriever_context_builder.retrieve(@retriever, prompt, rag_options)
121
+ @rag_documents_builder.build(docs)
108
122
  end
109
123
 
110
- def build_rag_documents(rag_documents)
111
- parts = ["Relevant documents:"]
112
- rag_documents.each_with_index do |doc, i|
113
- parts << "Document #{i + 1}: #{doc['content']}"
114
- parts << "Metadata: #{doc['metadata'].to_json}" if doc['metadata']
115
- end
116
- parts.join("\n")
124
+ def build_full_prompt(prompt, memory_context, tool_responses, rag_documents)
125
+ @prompt_builder.build(
126
+ memory_context: memory_context,
127
+ tool_responses: tool_responses,
128
+ rag_documents: rag_documents,
129
+ prompt: prompt
130
+ )
117
131
  end
118
132
 
119
- def build_tool_responses(tool_responses)
120
- parts = ["Tool results:"]
121
- tool_responses.each do |name, response|
122
- if response.is_a?(Hash) && response[:formatted]
123
- # Особая обработка для поиска без результатов
124
- if name == "web_search" && response[:results] && response[:results].empty?
125
- parts << "#{name}: No search results found. Please answer based on your knowledge, but indicate that search was unavailable."
126
- else
127
- parts << "#{name}: #{response[:formatted]}"
128
- end
129
- else
130
- parts << "#{name}: #{response}"
131
- end
132
- end
133
- parts.join("\n")
133
+ def store_memory(prompt, response)
134
+ @memory&.store(prompt, response)
134
135
  end
135
136
 
136
137
  def generate_response(prompt, stream: false, &block)
@@ -16,6 +16,8 @@ module LLMChain
16
16
  Clients::Llama2
17
17
  when /gemma3/
18
18
  Clients::Gemma3
19
+ when /deepseek-coder-v2/
20
+ Clients::DeepseekCoderV2
19
21
  else
20
22
  raise UnknownModelError, "Unknown model: #{model}"
21
23
  end
@@ -1,15 +1,37 @@
1
1
  module LLMChain
2
2
  module Clients
3
+ # Abstract base class for an LLM client adapter.
4
+ #
5
+ # Concrete clients **must** implement two methods:
6
+ # * `#chat(prompt, **options)` – single-shot request
7
+ # * `#stream_chat(prompt, **options)` – streaming request yielding chunks
8
+ #
9
+ # Constructor should accept `model:` plus any client-specific options
10
+ # (`api_key`, `base_url`, …).
11
+ #
12
+ # @abstract
3
13
  class Base
14
+ # @param model [String]
4
15
  def initialize(model)
5
16
  @model = model
6
17
  end
7
18
 
8
- def chat(_prompt)
19
+ # Send a non-streaming chat request.
20
+ #
21
+ # @param prompt [String]
22
+ # @param options [Hash]
23
+ # @return [String] assistant response
24
+ def chat(prompt, **options)
9
25
  raise NotImplementedError
10
26
  end
11
27
 
12
- def stream_chat(_prompt)
28
+ # Send a streaming chat request.
29
+ #
30
+ # @param prompt [String]
31
+ # @param options [Hash]
32
+ # @yieldparam chunk [String] partial response chunk
33
+ # @return [String] full concatenated response
34
+ def stream_chat(prompt, **options, &block)
13
35
  raise NotImplementedError
14
36
  end
15
37
  end
@@ -0,0 +1,32 @@
1
+ module LLMChain
2
+ module Clients
3
+ # Deepseek-Coder-V2 client for Ollama
4
+ #
5
+ # An open-source Mixture-of-Experts (MoE) code language model that achieves
6
+ # performance comparable to GPT4-Turbo in code-specific tasks.
7
+ #
8
+ # @example Using default model
9
+ # client = LLMChain::Clients::DeepseekCoderV2.new
10
+ # response = client.chat("Write a Python function to sort a list")
11
+ #
12
+ # @example Using specific model variant
13
+ # client = LLMChain::Clients::DeepseekCoderV2.new(model: "deepseek-coder-v2:16b")
14
+ # response = client.chat("Explain this algorithm")
15
+ #
16
+ class DeepseekCoderV2 < OllamaBase
17
+ DEFAULT_MODEL = "deepseek-coder-v2:latest".freeze
18
+
19
+ # Optimized settings for code generation tasks
20
+ DEFAULT_OPTIONS = {
21
+ temperature: 0.1, # Lower temperature for more precise code
22
+ top_p: 0.95, # High top_p for diverse but relevant responses
23
+ num_ctx: 8192, # Large context for complex code analysis
24
+ stop: ["User:", "Assistant:"] # Stop tokens for chat format
25
+ }.freeze
26
+
27
+ def initialize(model: DEFAULT_MODEL, base_url: nil, **options)
28
+ super(model: model, base_url: base_url, default_options: DEFAULT_OPTIONS.merge(options))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -57,7 +57,7 @@ module LLMChain
57
57
  case model.to_s
58
58
  when /^gpt/
59
59
  validate_openai_requirements!(model)
60
- when /qwen|llama|gemma/
60
+ when /qwen|llama|gemma|deepseek-coder-v2/
61
61
  validate_ollama_requirements!(model)
62
62
  else
63
63
  add_warning("Unknown model type: #{model}. Proceeding with default settings.")