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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -2
- data/README.md +15 -6
- data/examples/quick_demo.rb +1 -1
- data/examples/tools_example.rb +2 -2
- data/exe/llm-chain +7 -4
- data/lib/llm_chain/builders/memory_context.rb +24 -0
- data/lib/llm_chain/builders/prompt.rb +26 -0
- data/lib/llm_chain/builders/rag_documents.rb +25 -0
- data/lib/llm_chain/builders/retriever_context.rb +25 -0
- data/lib/llm_chain/builders/tool_responses.rb +27 -0
- data/lib/llm_chain/chain.rb +89 -88
- data/lib/llm_chain/client_registry.rb +2 -0
- data/lib/llm_chain/clients/base.rb +24 -2
- data/lib/llm_chain/clients/deepseek_coder_v2.rb +32 -0
- data/lib/llm_chain/configuration_validator.rb +1 -1
- data/lib/llm_chain/interfaces/builders/memory_context_builder.rb +20 -0
- data/lib/llm_chain/interfaces/builders/prompt_builder.rb +23 -0
- data/lib/llm_chain/interfaces/builders/rag_documents_builder.rb +20 -0
- data/lib/llm_chain/interfaces/builders/retriever_context_builder.rb +22 -0
- data/lib/llm_chain/interfaces/builders/tool_responses_builder.rb +20 -0
- data/lib/llm_chain/interfaces/memory.rb +38 -0
- data/lib/llm_chain/interfaces/tool_manager.rb +87 -0
- data/lib/llm_chain/memory/array.rb +18 -1
- data/lib/llm_chain/memory/redis.rb +20 -3
- data/lib/llm_chain/system_diagnostics.rb +73 -0
- data/lib/llm_chain/tools/base.rb +103 -0
- data/lib/llm_chain/tools/base_tool.rb +6 -76
- data/lib/llm_chain/tools/calculator.rb +118 -45
- data/lib/llm_chain/tools/code_interpreter.rb +43 -43
- data/lib/llm_chain/tools/date_time.rb +58 -0
- data/lib/llm_chain/tools/tool_manager.rb +46 -88
- data/lib/llm_chain/tools/tool_manager_factory.rb +44 -0
- data/lib/llm_chain/tools/web_search.rb +168 -336
- data/lib/llm_chain/version.rb +1 -1
- data/lib/llm_chain.rb +58 -56
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1963821b40f17c255520b55422bbc7cf6603be88555a99403a8ce24b1c4914f4
|
4
|
+
data.tar.gz: f27902811ba56ee4b1d9da09b22bd99204f01573b848c16e6a9e368f68eb40a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
672
|
+
tools: LLMChain::Tools::ToolManagerFactory.create_default_toolset
|
664
673
|
)
|
665
674
|
|
666
675
|
# Analyze CSV data
|
data/examples/quick_demo.rb
CHANGED
@@ -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::
|
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,
|
data/examples/tools_example.rb
CHANGED
@@ -101,7 +101,7 @@ end
|
|
101
101
|
|
102
102
|
# 4. Tool Manager Usage
|
103
103
|
puts "\n4. 🎯 Tool Manager"
|
104
|
-
tool_manager = LLMChain::Tools::
|
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::
|
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("
|
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::
|
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
|
data/lib/llm_chain/chain.rb
CHANGED
@@ -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
|
-
#
|
8
|
-
#
|
9
|
-
# @param
|
10
|
-
# @param
|
11
|
-
# @param
|
12
|
-
|
13
|
-
|
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 =
|
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
|
-
#
|
43
|
-
# @param
|
44
|
-
# @param
|
45
|
-
# @param
|
46
|
-
# @
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
65
|
-
|
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
|
74
|
-
|
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
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
120
|
-
|
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)
|
@@ -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
|
-
|
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
|
-
|
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.")
|