scout-ai 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +91 -10
  3. data/Rakefile +1 -0
  4. data/VERSION +1 -1
  5. data/bin/scout-ai +2 -0
  6. data/lib/scout/llm/agent/chat.rb +24 -0
  7. data/lib/scout/llm/agent.rb +13 -13
  8. data/lib/scout/llm/ask.rb +26 -16
  9. data/lib/scout/llm/backends/bedrock.rb +129 -0
  10. data/lib/scout/llm/backends/huggingface.rb +6 -21
  11. data/lib/scout/llm/backends/ollama.rb +69 -36
  12. data/lib/scout/llm/backends/openai.rb +85 -35
  13. data/lib/scout/llm/backends/openwebui.rb +1 -1
  14. data/lib/scout/llm/backends/relay.rb +3 -2
  15. data/lib/scout/llm/backends/responses.rb +272 -0
  16. data/lib/scout/llm/chat.rb +547 -0
  17. data/lib/scout/llm/parse.rb +70 -13
  18. data/lib/scout/llm/tools.rb +126 -5
  19. data/lib/scout/llm/utils.rb +17 -10
  20. data/lib/scout/model/base.rb +19 -0
  21. data/lib/scout/model/python/base.rb +25 -0
  22. data/lib/scout/model/python/huggingface/causal/next_token.rb +23 -0
  23. data/lib/scout/model/python/huggingface/causal.rb +29 -0
  24. data/lib/scout/model/python/huggingface/classification +0 -0
  25. data/lib/scout/model/python/huggingface/classification.rb +50 -0
  26. data/lib/scout/model/python/huggingface.rb +112 -0
  27. data/lib/scout/model/python/torch/dataloader.rb +57 -0
  28. data/lib/scout/model/python/torch/helpers.rb +84 -0
  29. data/lib/scout/model/python/torch/introspection.rb +34 -0
  30. data/lib/scout/model/python/torch/load_and_save.rb +47 -0
  31. data/lib/scout/model/python/torch.rb +94 -0
  32. data/lib/scout/model/util/run.rb +181 -0
  33. data/lib/scout/model/util/save.rb +81 -0
  34. data/lib/scout-ai.rb +3 -1
  35. data/python/scout_ai/__init__.py +35 -0
  36. data/python/scout_ai/__pycache__/__init__.cpython-310.pyc +0 -0
  37. data/python/scout_ai/__pycache__/__init__.cpython-311.pyc +0 -0
  38. data/python/scout_ai/__pycache__/huggingface.cpython-310.pyc +0 -0
  39. data/python/scout_ai/__pycache__/huggingface.cpython-311.pyc +0 -0
  40. data/python/scout_ai/__pycache__/util.cpython-310.pyc +0 -0
  41. data/python/scout_ai/__pycache__/util.cpython-311.pyc +0 -0
  42. data/python/scout_ai/atcold/__init__.py +0 -0
  43. data/python/scout_ai/atcold/plot_lib.py +141 -0
  44. data/python/scout_ai/atcold/spiral.py +27 -0
  45. data/python/scout_ai/huggingface/data.py +48 -0
  46. data/python/scout_ai/huggingface/eval.py +60 -0
  47. data/python/scout_ai/huggingface/model.py +29 -0
  48. data/python/scout_ai/huggingface/rlhf.py +83 -0
  49. data/python/scout_ai/huggingface/train/__init__.py +34 -0
  50. data/python/scout_ai/huggingface/train/__pycache__/__init__.cpython-310.pyc +0 -0
  51. data/python/scout_ai/huggingface/train/__pycache__/next_token.cpython-310.pyc +0 -0
  52. data/python/scout_ai/huggingface/train/next_token.py +315 -0
  53. data/python/scout_ai/language_model.py +70 -0
  54. data/python/scout_ai/util.py +32 -0
  55. data/scout-ai.gemspec +130 -0
  56. data/scout_commands/agent/ask +133 -15
  57. data/scout_commands/agent/kb +15 -0
  58. data/scout_commands/llm/ask +71 -12
  59. data/scout_commands/llm/process +4 -2
  60. data/test/data/cat.jpg +0 -0
  61. data/test/scout/llm/agent/test_chat.rb +14 -0
  62. data/test/scout/llm/backends/test_bedrock.rb +60 -0
  63. data/test/scout/llm/backends/test_huggingface.rb +3 -3
  64. data/test/scout/llm/backends/test_ollama.rb +48 -10
  65. data/test/scout/llm/backends/test_openai.rb +96 -11
  66. data/test/scout/llm/backends/test_responses.rb +115 -0
  67. data/test/scout/llm/test_ask.rb +1 -0
  68. data/test/scout/llm/test_chat.rb +214 -0
  69. data/test/scout/llm/test_parse.rb +81 -2
  70. data/test/scout/model/python/huggingface/causal/test_next_token.rb +59 -0
  71. data/test/scout/model/python/huggingface/test_causal.rb +33 -0
  72. data/test/scout/model/python/huggingface/test_classification.rb +30 -0
  73. data/test/scout/model/python/test_base.rb +44 -0
  74. data/test/scout/model/python/test_huggingface.rb +9 -0
  75. data/test/scout/model/python/test_torch.rb +71 -0
  76. data/test/scout/model/python/torch/test_helpers.rb +14 -0
  77. data/test/scout/model/test_base.rb +117 -0
  78. data/test/scout/model/util/test_save.rb +31 -0
  79. metadata +72 -5
  80. data/questions/coach +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36833eaae86ac11476b39ac0f188cfa2106a9cad810fcdd53de37a9b356e15e0
4
- data.tar.gz: 014a3927cb41bd99b28b866c442bacca65c21a27b79491e3bd790c1051115f4b
3
+ metadata.gz: 1c66f7d74bdf6583e77cb7dd132bfc7d677399cc5ddeb42d59cacdef2c432d72
4
+ data.tar.gz: 9dcdc99b701696dd724394124a02bac3bde5706d94d9efad81c0ab11641d4e47
5
5
  SHA512:
6
- metadata.gz: 0d4ebfecd2e6ed94901b955536d6c6a36817ac7aa1eaafcfd657c563185b69c5eb03293aef2446a0444842c1d263dc79a951342e57fd3704f96b931ef02cb027
7
- data.tar.gz: 45ed2228cca6bfeea3d0161ff766b1abfb2137def68eb1c3c142ee9b32a234a9b95223612026b3683d0f72aba308853e1bd9d92ca4e5cdbfdb9500555810a4fa
6
+ metadata.gz: a72b0575c3c92eb4dfa413bada73c4448f85434ee99fe446b204b29e789142ac7cdd0fab3f422b994c3eb663c254dbbb097ae2fe0bd27aa7c56815690ee79289
7
+ data.tar.gz: 15616e4363983823e17ace7c866f49772e954e75eeae5b6677fb32bda322d9608be508594393cd0e032b0bc1666fe2a561b444a0b2af27360577c86f51f6c028
data/.vimproject CHANGED
@@ -1,10 +1,47 @@
1
- scout-ai=/$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.txt *.conf" {
1
+ scout-ai=$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.txt *.conf" {
2
2
  Rakefile
3
3
  bin=bin filter="*"{
4
4
  scout-ai
5
5
  }
6
- etc=etc filter="*"{
7
- config
6
+ agents=agents{
7
+ analyst.rb
8
+ }
9
+ chats=chats{
10
+ system=system{
11
+ scout-ai
12
+ }
13
+ doc=doc{
14
+ chat
15
+ model.analysis
16
+ model
17
+ }
18
+ develop=develop{
19
+ training=training{
20
+ intro
21
+ basics
22
+ data=data{
23
+ main
24
+ next_token
25
+ }
26
+ data.tmp
27
+ python
28
+ }
29
+ chat
30
+ model
31
+ causalLM
32
+ rf
33
+ }
34
+ refactor=refactor{
35
+ chat
36
+ }
37
+ help=help{
38
+ tools
39
+ from_pretrained
40
+ fine-tunning
41
+ }
42
+ test=test{
43
+ rf.rb
44
+ }
8
45
  }
9
46
  lib=lib {
10
47
  scout-ai.rb
@@ -12,24 +49,72 @@ scout-ai=/$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *
12
49
  llm=llm{
13
50
  utils.rb
14
51
  parse.rb
52
+ tools.rb
53
+ chat.rb
54
+
15
55
  backends=backends{
16
- ollama.rb
17
56
  openai.rb
57
+ responses.rb
58
+ ollama.rb
18
59
  openwebui.rb
19
60
  huggingface.rb
20
61
  relay.rb
21
62
  }
63
+
22
64
  ask.rb
23
65
 
24
66
  embed.rb
25
67
 
26
68
  rag.rb
27
69
 
28
- tools.rb
29
70
  agent.rb
30
71
  }
72
+ model=model{
73
+ util=util{
74
+ save.rb
75
+ run.rb
76
+ }
77
+ base.rb
78
+
79
+ python=python{
80
+ base.rb
81
+ torch.rb
82
+ torch=torch{
83
+ helpers.rb
84
+ dataloader.rb
85
+ introspection.rb
86
+ load_and_save.rb
87
+ }
88
+ huggingface.rb
89
+ huggingface=huggingface{
90
+ classification.rb
91
+ causal.rb
92
+ causal=causal{
93
+ next_token.rb
94
+ }
95
+ }
96
+ }
97
+ }
31
98
  }
32
99
  }
100
+ python=python filter="*"{
101
+ scout_ai=scout_ai{
102
+ __init__.py
103
+ language_model.py
104
+ util.py
105
+ huggingface=huggingface{
106
+ data.py
107
+ eval.py
108
+ model.py
109
+ train=train{
110
+ __init__.py
111
+ next_token.py
112
+ }
113
+ rlhf.py
114
+ }
115
+ }
116
+ }
117
+
33
118
  test=test {
34
119
  data=data filter="*"{
35
120
  person=person{
@@ -51,11 +136,7 @@ scout-ai=/$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *
51
136
  }
52
137
  agent=agent{
53
138
  ask
139
+ kb
54
140
  }
55
141
  }
56
- questions=questions filter="*"{
57
- coach
58
- evaluator
59
- templater
60
- }
61
142
  }
data/Rakefile CHANGED
@@ -16,6 +16,7 @@ Juwelier::Tasks.new do |gem|
16
16
  gem.authors = ["Miguel Vazquez"]
17
17
 
18
18
  # dependencies defined in Gemfile
19
+ gem.add_runtime_dependency 'scout-rig', '>= 0'
19
20
  end
20
21
  Juwelier::RubygemsDotOrgTasks.new
21
22
  require 'rake/testtask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 1.0.0
data/bin/scout-ai CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $LOAD_PATH.unshift File.join(__dir__, '../lib')
4
+
3
5
  require 'scout-ai'
4
6
 
5
7
  load Scout.bin.scout.find
@@ -0,0 +1,24 @@
1
+ module LLM
2
+ class Agent
3
+ def start_chat
4
+ @start_chat ||= Chat.setup []
5
+ end
6
+
7
+ def start(chat=nil)
8
+ if chat
9
+ (@current_chat || start_chat).annotate chat unless Chat === chat
10
+ @current_chat = chat
11
+ else
12
+ @current_chat = start_chat.branch
13
+ end
14
+ end
15
+
16
+ def current_chat
17
+ @current_chat ||= start
18
+ end
19
+
20
+ def method_missing(name,...)
21
+ current_chat.send(name, ...)
22
+ end
23
+ end
24
+ end
@@ -2,13 +2,12 @@ require_relative 'ask'
2
2
 
3
3
  module LLM
4
4
  class Agent
5
- attr_accessor :system, :workflow, :knowledge_base
6
- def initialize(system = nil, workflow: nil, knowledge_base: nil, model: nil, **kwargs)
7
- @system = system
5
+ attr_accessor :workflow, :knowledge_base, :start_chat
6
+ def initialize(workflow: nil, knowledge_base: nil, start_chat: nil, **kwargs)
8
7
  @workflow = workflow
9
8
  @knowledge_base = knowledge_base
10
- @model = model
11
9
  @other_options = kwargs
10
+ @start_chat = start_chat
12
11
  end
13
12
 
14
13
  def format_message(message, prefix = "user")
@@ -19,17 +18,17 @@ module LLM
19
18
 
20
19
  def system_prompt
21
20
  system = @system
21
+ system = [] if system.nil?
22
22
  system = [system] unless system.nil? || system.is_a?(Array)
23
+ system = [] if system.nil?
23
24
 
24
- if @knowledge_base
25
+ if @knowledge_base and @knowledge_base.all_databases.any?
25
26
  system << <<-EOF
26
27
  You have access to the following databases associating entities:
27
28
  EOF
28
29
 
29
30
  knowledge_base.all_databases.each do |database|
30
- system << <<-EOF.strip + (knowledge_base.undirected(database) ? ". Undirected" : "")
31
- * #{database}: #{knowledge_base.source(database)} => #{knowledge_base.target(database)}
32
- EOF
31
+ system << knowledge_base.markdown(database)
33
32
  end
34
33
  end
35
34
 
@@ -45,21 +44,21 @@ You have access to the following databases associating entities:
45
44
  end
46
45
 
47
46
  # function: takes an array of messages and calls LLM.ask with them
48
- def ask(messages, model = nil)
47
+ def ask(messages, model = nil, options = {})
49
48
  messages = [messages] unless messages.is_a? Array
50
- model ||= @model
49
+ model ||= @model if model
51
50
 
52
51
  tools = []
53
52
  tools += LLM.workflow_tools(workflow) if workflow
54
- tools += LLM.knowledge_base_tool_definition(knowledge_base) if knowledge_base
53
+ tools += LLM.knowledge_base_tool_definition(knowledge_base) if knowledge_base and knowledge_base.all_databases.any?
55
54
 
56
- LLM.ask prompt(messages), @other_options.merge(model: model, log_errors: true, tools: tools) do |name,parameters|
55
+ LLM.ask prompt(messages), @other_options.merge(log_errors: true, tools: tools) do |name,parameters|
57
56
  case name
58
57
  when 'children'
59
58
  parameters = IndiferentHash.setup(parameters)
60
59
  database, entities = parameters.values_at "database", "entities"
61
60
  Log.high "Finding #{entities} children in #{database}"
62
- knowledge_base.children(database, entities).target
61
+ knowledge_base.children(database, entities)
63
62
  else
64
63
  if workflow
65
64
  begin
@@ -76,3 +75,4 @@ You have access to the following databases associating entities:
76
75
  end
77
76
  end
78
77
  end
78
+ require_relative 'agent/chat'
data/lib/scout/llm/ask.rb CHANGED
@@ -2,32 +2,42 @@ require 'scout'
2
2
  require_relative 'backends/openai'
3
3
  require_relative 'backends/ollama'
4
4
  require_relative 'backends/openwebui'
5
+ require_relative 'backends/bedrock'
5
6
  require_relative 'backends/relay'
7
+ require_relative 'backends/responses'
6
8
 
7
9
  module LLM
8
10
  def self.ask(question, options = {}, &block)
9
- endpoint = IndiferentHash.process_options options, :endpoint
11
+ messages = LLM.chat(question)
12
+ options = IndiferentHash.add_defaults LLM.options(messages), options
10
13
 
11
- endpoint ||= Scout::Config.get :endpoint, :ask, :llm, env: 'ASK_ENDPOINT,LLM_ENDPOINT', default: :openai
14
+ endpoint, persist = IndiferentHash.process_options options, :endpoint, :persist, persist: true
15
+
16
+ endpoint ||= Scout::Config.get :endpoint, :ask, :llm, env: 'ASK_ENDPOINT,LLM_ENDPOINT'
12
17
  if endpoint && Scout.etc.AI[endpoint].exists?
13
18
  options = IndiferentHash.add_defaults options, Scout.etc.AI[endpoint].yaml
14
19
  end
15
20
 
16
- backend = IndiferentHash.process_options options, :backend
17
- backend ||= Scout::Config.get :backend, :ask, :llm, env: 'ASK_BACKEND,LLM_BACKEND', default: :openai
18
-
21
+ Persist.persist(endpoint, :json, prefix: "LLM ask", other: options.merge(messages: messages), persist: persist) do
22
+ backend = IndiferentHash.process_options options, :backend
23
+ backend ||= Scout::Config.get :backend, :ask, :llm, env: 'ASK_BACKEND,LLM_BACKEND', default: :openai
19
24
 
20
- case backend
21
- when :openai, "openai"
22
- LLM::OpenAI.ask(question, options, &block)
23
- when :ollama, "ollama"
24
- LLM::OLlama.ask(question, options, &block)
25
- when :openwebui, "openwebui"
26
- LLM::OpenWebUI.ask(question, options, &block)
27
- when :relay, "relay"
28
- LLM::Relay.ask(question, options, &block)
29
- else
30
- raise "Unknown backend: #{backend}"
25
+ case backend
26
+ when :openai, "openai"
27
+ LLM::OpenAI.ask(messages, options, &block)
28
+ when :responses, "responses"
29
+ LLM::Responses.ask(messages, options, &block)
30
+ when :ollama, "ollama"
31
+ LLM::OLlama.ask(messages, options, &block)
32
+ when :openwebui, "openwebui"
33
+ LLM::OpenWebUI.ask(messages, options, &block)
34
+ when :relay, "relay"
35
+ LLM::Relay.ask(messages, options, &block)
36
+ when :bedrock, "bedrock"
37
+ LLM::Bedrock.ask(messages, options, &block)
38
+ else
39
+ raise "Unknown backend: #{backend}"
40
+ end
31
41
  end
32
42
  end
33
43
 
@@ -0,0 +1,129 @@
1
+ require 'scout'
2
+ require 'aws-sdk-bedrockruntime'
3
+ require_relative '../parse'
4
+ require_relative '../tools'
5
+ require_relative '../utils'
6
+
7
+ module LLM
8
+ module Bedrock
9
+ def self.client(region, access_key, secret_key)
10
+
11
+ credentials = Aws::Credentials.new(access_key, secret_key) if access_key and secret_key
12
+ options = {}
13
+ options[:region] = region if region
14
+ options[:credentials] = credentials if credentials
15
+ Aws::BedrockRuntime::Client.new(options)
16
+ end
17
+
18
+ def self.messages_to_prompt(messages)
19
+ system = []
20
+ user = []
21
+ messages.each do |info|
22
+ role, content = info.values_at :role, :content
23
+ if role.to_s == 'system'
24
+ system << content
25
+ else
26
+ user << content
27
+ end
28
+ end
29
+ [system*"\n", user*"\n"]
30
+ end
31
+
32
+ def self.ask(question, options = {}, &block)
33
+ client, region, access_key, secret_key, type = IndiferentHash.process_options options, :client, :region, :access_key, :secret_key, :type
34
+
35
+ model_options = IndiferentHash.pull_keys options, :model
36
+ model = IndiferentHash.process_options model_options, :model
37
+
38
+ if client.nil?
39
+ region ||= Scout::Config.get(:region, :bedrock_ask, :ask, :bedrock, env: 'AWS_REGION')
40
+ access_key ||= LLM.get_url_config(:access_key, nil, :bedrock_ask, :ask, :bedrock, env: 'AWS_ACCESS_KEY_ID')
41
+ secret_key ||= LLM.get_url_config(:secret_key, nil, :bedrock_ask, :ask, :bedrock, env: 'AWS_SECRET_ACCESS_KEY')
42
+ client = self.client(region, access_key, secret_key)
43
+ end
44
+
45
+ model ||= Scout::Config.get(:model, :bedrock_ask, :ask, :bedrock, env: 'BEDROCK_MODEL_ID')
46
+ type ||= Scout::Config.get(:type, model, default: :messages)
47
+
48
+ role = IndiferentHash.process_options options, :role
49
+ messages = LLM.parse(question, role)
50
+
51
+ case type.to_sym
52
+ when :messages
53
+ body = model_options.merge({
54
+ system: messages.select{|m| m[:role] == 'system'}.collect{|m| m[:content]}*"\n",
55
+ messages: messages.select{|m| m[:role] == 'user'}
56
+ })
57
+ when :prompt
58
+ system, user = messages_to_prompt messages
59
+ body = model_options.merge({
60
+ prompt: user
61
+ })
62
+ else
63
+ raise "Unkown type #{type}"
64
+ end
65
+
66
+ Log.debug "Calling bedrock with model: #{model} parameters: #{Log.fingerprint body}"
67
+
68
+ response = client.invoke_model(
69
+ model_id: model,
70
+ content_type: 'application/json',
71
+ body: body.to_json
72
+ )
73
+
74
+ result = JSON.parse(response.body.string)
75
+ Log.debug "Response: #{Log.fingerprint result}"
76
+ message = result
77
+ tool_calls = message.dig('content').select{|m| m['tool_calls']}
78
+
79
+ while tool_calls && tool_calls.any?
80
+ messages << message
81
+
82
+ cpus = Scout::Config.get :cpus, :tool_calling, default: 3
83
+ tool_calls.each do |tool_call|
84
+ response_message = LLM.tool_response(tool_call, &block)
85
+ messages << response_message
86
+ end
87
+
88
+ body[:messages] = messages.compact
89
+ Log.debug "Calling bedrock with parameters: #{Log.fingerprint body}"
90
+ response = client.invoke_model(
91
+ model_id: model,
92
+ content_type: 'application/json',
93
+ body: body.to_json
94
+ )
95
+ result = JSON.parse(response.body.string)
96
+ Log.debug "Response: #{Log.fingerprint result}"
97
+
98
+ message = result
99
+ tool_calls = message.dig('content').select{|m| m['tool_calls']}
100
+ end
101
+
102
+ message.dig('content').collect{|m|
103
+ m['text']
104
+ } * "\n"
105
+ end
106
+
107
+ def self.embed(text, options = {})
108
+ client, region, access_key, secret_key, model = IndiferentHash.process_options options, :client, :region, :access_key, :secret_key, :model
109
+
110
+ if client.nil?
111
+ region ||= Scout::Config.get(:region, :bedrock_embed, :embed, :bedrock, env: 'AWS_REGION')
112
+ access_key ||= LLM.get_url_config(:access_key, nil, :bedrock_embed, :embed, :bedrock, env: 'AWS_ACCESS_KEY_ID')
113
+ secret_key ||= LLM.get_url_config(:secret_key, nil, :bedrock_embed, :embed, :bedrock, env: 'AWS_SECRET_ACCESS_KEY')
114
+ client = self.client(region, access_key, secret_key)
115
+ end
116
+
117
+ model ||= Scout::Config.get(:model, :bedrock_embed, :embed, :bedrock, env: 'BEDROCK_EMBED_MODEL_ID', default: 'amazon.titan-embed-text-v1')
118
+
119
+ response = client.invoke_model(
120
+ model_id: model,
121
+ content_type: 'application/json',
122
+ body: { inputText: text }.to_json
123
+ )
124
+
125
+ result = JSON.parse(response.body.string)
126
+ result['embedding']
127
+ end
128
+ end
129
+ end
@@ -1,17 +1,18 @@
1
1
  require_relative '../parse'
2
2
  require_relative '../tools'
3
+ require_relative '../chat'
3
4
 
4
5
  module LLM
5
6
  module Huggingface
6
7
 
7
8
  def self.model(model_options)
8
- require 'rbbt-util'
9
- require 'rbbt/vector/model/huggingface'
9
+ require 'scout/model/python/huggingface'
10
+ require 'scout/model/python/huggingface/causal'
10
11
 
11
12
  model, task, checkpoint, dir = IndiferentHash.process_options model_options, :model, :task, :checkpoint, :dir
12
13
  model ||= Scout::Config.get(:model, :huggingface, env: 'HUGGINGFACE_MODEL,HF_MODEL')
13
14
 
14
- HuggingfaceModel.new task, model, dir, model_options
15
+ CausalModel.new model, dir, model_options
15
16
  end
16
17
 
17
18
  def self.ask(question, options = {}, &block)
@@ -20,7 +21,7 @@ module LLM
20
21
 
21
22
  model = self.model model_options
22
23
 
23
- messages = LLM.parse(question)
24
+ messages = LLM.messages(question)
24
25
 
25
26
  system = []
26
27
  prompt = []
@@ -36,23 +37,7 @@ module LLM
36
37
  parameters = options.merge(messages: messages)
37
38
  Log.debug "Calling client with parameters: #{Log.fingerprint parameters}"
38
39
 
39
- response = model.eval(messages)
40
- message = response[-1]
41
- while message["role"] == "assistant" && message["tool_calls"]
42
- messages << message
43
-
44
- message["tool_calls"].each do |tool_call|
45
- response_message = LLM.tool_response(tool_call, &block)
46
- messages << response_message
47
- end
48
-
49
- parameters[:messages] = messages
50
- Log.debug "Calling client with parameters: #{Log.fingerprint parameters}"
51
- response = model.eval(parameters)
52
- message = response[-1]
53
- end
54
-
55
- message["content"]
40
+ model.eval(messages)
56
41
  end
57
42
 
58
43
  def self.embed(text, options = {})
@@ -2,6 +2,7 @@ require 'ollama-ai'
2
2
  require_relative '../parse'
3
3
  require_relative '../tools'
4
4
  require_relative '../utils'
5
+ require_relative '../chat'
5
6
 
6
7
  module LLM
7
8
  module OLlama
@@ -15,9 +16,34 @@ module LLM
15
16
  )
16
17
  end
17
18
 
19
+
20
+ def self.process_response(responses, &block)
21
+ responses.collect do |response|
22
+ Log.debug "Respose: #{Log.fingerprint response}"
23
+
24
+ message = response['message']
25
+ tool_calls = response.dig("tool_calls") ||
26
+ response.dig("message", "tool_calls")
27
+
28
+ if tool_calls && tool_calls.any?
29
+ LLM.call_tools tool_calls, &block
30
+ else
31
+ [message]
32
+ end
33
+ end.flatten
34
+ end
35
+
18
36
  def self.ask(question, options = {}, &block)
37
+ original_options = options.dup
19
38
 
20
- client, url, key, model = IndiferentHash.process_options options, :client, :url, :key, :model
39
+ messages = LLM.chat(question)
40
+ options = options.merge LLM.options messages
41
+ tools = LLM.tools messages
42
+ associations = LLM.associations messages
43
+
44
+ client, url, key, model, return_messages, format, stream = IndiferentHash.process_options options,
45
+ :client, :url, :key, :model, :return_messages, :format, :stream,
46
+ stream: false
21
47
 
22
48
  if client.nil?
23
49
  url ||= Scout::Config.get(:url, :ollama_ask, :ask, :ollama, env: 'OLLAMA_URL', default: "http://localhost:11434")
@@ -30,51 +56,58 @@ module LLM
30
56
  model ||= LLM.get_url_config(:model, url, :ollama_ask, :ask, :ollama, env: 'OLLAMA_MODEL', default: "mistral")
31
57
  end
32
58
 
33
- mode = IndiferentHash.process_options options, :mode
34
59
 
35
- messages = LLM.parse(question)
36
-
37
- system = []
38
- prompt = []
39
- messages.each do |message|
40
- role, content = message.values_at :role, :content
41
- if role == 'system'
42
- system << content
43
- else
44
- prompt << content
60
+ case format.to_sym
61
+ when :json, :json_object
62
+ options[:response_format] = {type: 'json_object'}
63
+ else
64
+ options[:response_format] = {type: format}
65
+ end if format
66
+
67
+ parameters = options.merge(model: model)
68
+
69
+ if tools.any? || associations.any?
70
+ parameters[:tools] = []
71
+ parameters[:tools] += tools.values.collect{|a| a.last } if tools
72
+ parameters[:tools] += associations.values.collect{|a| a.last } if associations
73
+ if not block_given?
74
+ block = Proc.new do |name,parameters|
75
+ IndiferentHash.setup parameters
76
+ if tools[name]
77
+ workflow = tools[name].first
78
+ jobname = parameters.delete :jobname
79
+ workflow.job(name, jobname, parameters).run
80
+ else
81
+ kb = associations[name].first
82
+ entities, reverse = IndiferentHash.process_options parameters, :entities, :reverse
83
+ if reverse
84
+ kb.parents(name, entities)
85
+ else
86
+ kb.children(name, entities)
87
+ end
88
+ end
89
+ end
45
90
  end
46
91
  end
47
92
 
48
- case mode
49
- when :chat, 'chat'
50
- parameters = options.merge(model: model, messages: messages)
51
- Log.debug "Calling client with parameters: #{Log.fingerprint parameters}"
93
+ Log.low "Calling client with parameters #{Log.fingerprint parameters}\n#{LLM.print messages}"
52
94
 
53
- response = client.chat(parameters)
54
- response.collect do |choice|
55
- message=choice['message']
56
- while message["role"] == "assistant" && message["tool_calls"]
57
- messages << message
95
+ parameters[:messages] = LLM.tools_to_ollama messages
58
96
 
59
- message["tool_calls"].each do |tool_call|
60
- response_message = LLM.tool_response(tool_call, &block)
61
- messages << response_message
62
- end
97
+ parameters[:stream] = stream
63
98
 
64
- parameters[:messages] = messages
65
- Log.debug "Calling client with parameters: #{Log.fingerprint parameters}"
66
- response = client.chat(parameters)
99
+ response = self.process_response client.chat(parameters), &block
67
100
 
68
- message = response[0]['message']
69
- end
101
+ res = if response.last[:role] == 'function_call_output'
102
+ response + self.ask(messages + response, original_options.except(:tool_choice).merge(return_messages: true, tools: parameters[:tools]), &block)
103
+ else
104
+ response
105
+ end
70
106
 
71
- message["content"]
72
- end * ""
107
+ if return_messages
108
+ res
73
109
  else
74
- parameters = options.merge(model: model, prompt: prompt * "\n", system: system*"\n")
75
- Log.debug "Calling client with parameters: #{Log.fingerprint parameters}"
76
- response = client.generate(parameters)
77
- response.collect{|e| e['response']} * ""
110
+ res.last['content']
78
111
  end
79
112
  end
80
113