luo 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/inspectionProfiles/Project_Default.xml +9 -0
  3. data/.idea/luo.iml +26 -1
  4. data/.rspec +1 -0
  5. data/Gemfile.lock +62 -1
  6. data/README.md +3 -0
  7. data/README.zh.md +9 -0
  8. data/exe/luo +7 -0
  9. data/lib/luo/agent.rb +45 -0
  10. data/lib/luo/agent_runner_base.rb +89 -0
  11. data/lib/luo/agent_runner_context.rb +33 -0
  12. data/lib/luo/aiui.rb +41 -0
  13. data/lib/luo/cli.rb +33 -0
  14. data/lib/luo/configurable.rb +14 -0
  15. data/lib/luo/http_client.rb +21 -0
  16. data/lib/luo/memory_history.rb +55 -0
  17. data/lib/luo/messages.rb +50 -0
  18. data/lib/luo/open_ai.rb +48 -0
  19. data/lib/luo/open_ai_agent_runner.rb +55 -0
  20. data/lib/luo/prompt_template.rb +39 -0
  21. data/lib/luo/prompts.rb +32 -0
  22. data/lib/luo/templates/luo_agent_input.md.erb +39 -0
  23. data/lib/luo/templates/luo_agent_system.md.erb +4 -0
  24. data/lib/luo/templates/luo_agent_tool_input.md.erb +10 -0
  25. data/lib/luo/templates/luo_commit.md.erb +2 -0
  26. data/lib/luo/templates/luo_xinghuo_agent_input.md.erb +10 -0
  27. data/lib/luo/templates/luo_xinghuo_agent_tool_input.md.erb +16 -0
  28. data/lib/luo/templates/prompt.demo.erb +1 -0
  29. data/lib/luo/version.rb +1 -1
  30. data/lib/luo/xinghuo.rb +50 -0
  31. data/lib/luo/xinghuo_agent_runner.rb +56 -0
  32. data/lib/luo.rb +18 -1
  33. data/luo.gemspec +9 -0
  34. data/sig/luo/agent.rbs +21 -0
  35. data/sig/luo/agent_runner_base.rbs +25 -0
  36. data/sig/luo/agent_runner_context.rbs +19 -0
  37. data/sig/luo/aiui.rbs +9 -0
  38. data/sig/luo/configurable.rbs +5 -0
  39. data/sig/luo/env.rbs +9 -0
  40. data/sig/luo/http_client.rbs +9 -0
  41. data/sig/luo/memory_history.rbs +18 -0
  42. data/sig/luo/messages.rbs +20 -0
  43. data/sig/luo/open_ai.rbs +11 -0
  44. data/sig/luo/prompt_template.rbs +18 -0
  45. data/sig/luo/prompts.rbs +15 -0
  46. data/sig/luo/xinghuo.rbs +9 -0
  47. metadata +186 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b22893d8f1c95e872f24ebbaf9f40746abab52e4c39765eb44de66b54e70cbf
4
- data.tar.gz: bfce4c3181de7b8ed34ab3a2dd69f8888f7187611a672ad1b9661d7f2ab5190b
3
+ metadata.gz: 7755df3627fd226e48254313905aaaf420d1804bb86d95c67ea0383ca3b6f677
4
+ data.tar.gz: 17764e813034fd6d9f159fffdc8a954e380aa41df0a1e55b45a8856197343916
5
5
  SHA512:
6
- metadata.gz: 062a4deb39d0e12f1ed6a6473c4d20fbe432da4190e5d93c6b3b420cba7999c3044a2c88549c0183a67e67223926405b03c35d9a6e30f53f628a64587a7d01e1
7
- data.tar.gz: 8c8fc5ae086cd012c62b1c2cb142987a6b1ac997466a76d481f75989c3860495607ebe09269da531aa17301a55f2cd99877bca37a81e6ea11f0da5c7c28b3223
6
+ metadata.gz: 3820ebe256d4c302fac7ee48f784636c666de704fea02d5a8b5a6d12636eb231a406c09ffdfd31f5e9038c6d0c5dd879c9aeab960c72151aae2f6562f5be1a58
7
+ data.tar.gz: c276f298d2d25b7ced2c6ca27ad427d92964cf1fff3213b5913ee02cdcd9643fdaf4de9db5eca066c77e22bd5ec4302a46eb5a710baa4f1bc8e005d8ce96f08d
@@ -0,0 +1,9 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="RubyClassVariableUsageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
5
+ <inspection_tool class="RubyResolve" enabled="false" level="WARNING" enabled_by_default="false">
6
+ <option name="warnImplicitResults" value="false" />
7
+ </inspection_tool>
8
+ </profile>
9
+ </component>
data/.idea/luo.iml CHANGED
@@ -12,13 +12,38 @@
12
12
  <orderEntry type="jdk" jdkName="ruby-3.2.1-p31" jdkType="RUBY_SDK" />
13
13
  <orderEntry type="sourceFolder" forTests="false" />
14
14
  <orderEntry type="library" scope="PROVIDED" name="bundler (v2.4.6, ruby-3.2.1-p31) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.2.2, ruby-3.2.1-p31) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, ruby-3.2.1-p31) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="dotenv (v2.8.1, ruby-3.2.1-p31) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="dry-cli (v1.0.0, ruby-3.2.1-p31) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="dry-configurable (v1.0.1, ruby-3.2.1-p31) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="dry-core (v1.0.0, ruby-3.2.1-p31) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="dry-inflector (v1.0.0, ruby-3.2.1-p31) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="dry-initializer (v3.1.1, ruby-3.2.1-p31) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="dry-logic (v1.5.0, ruby-3.2.1-p31) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="dry-schema (v1.13.1, ruby-3.2.1-p31) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="dry-types (v1.7.1, ruby-3.2.1-p31) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="faraday (v2.7.4, ruby-3.2.1-p31) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="faraday-net_http (v3.0.2, ruby-3.2.1-p31) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="faraday-retry (v2.1.0, ruby-3.2.1-p31) [gem]" level="application" />
15
29
  <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, ruby-3.2.1-p31) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.12.0, ruby-3.2.1-p31) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.12.2, ruby-3.2.1-p31) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.12.3, ruby-3.2.1-p31) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.12.5, ruby-3.2.1-p31) [gem]" level="application" />
34
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.12.0, ruby-3.2.1-p31) [gem]" level="application" />
35
+ <orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.6.8, ruby-3.2.1-p31) [gem]" level="application" />
16
36
  </component>
17
37
  <component name="RakeTasksCache">
18
38
  <option name="myRootTask">
19
39
  <RakeTaskImpl id="rake">
20
40
  <subtasks>
21
41
  <RakeTaskImpl description="Build luo-0.1.0.gem into the pkg directory" fullCommand="build" id="build" />
42
+ <RakeTaskImpl id="build">
43
+ <subtasks>
44
+ <RakeTaskImpl description="Generate SHA512 checksum if luo-0.1.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
45
+ </subtasks>
46
+ </RakeTaskImpl>
22
47
  <RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
23
48
  <RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
24
49
  <RakeTaskImpl description="Build and install luo-0.1.0.gem into system gems" fullCommand="install" id="install" />
@@ -27,7 +52,7 @@
27
52
  <RakeTaskImpl description="Build and install luo-0.1.0.gem into system gems without network access" fullCommand="install:local" id="local" />
28
53
  </subtasks>
29
54
  </RakeTaskImpl>
30
- <RakeTaskImpl description="Create tag v0.1.0 and build and push luo-0.1.0.gem to TODO: Set to your gem server 'https://example.com'" fullCommand="release[remote]" id="release[remote]" />
55
+ <RakeTaskImpl description="Create tag v0.1.0 and build and push luo-0.1.0.gem to rubygems.org" fullCommand="release[remote]" id="release[remote]" />
31
56
  <RakeTaskImpl description="" fullCommand="default" id="default" />
32
57
  <RakeTaskImpl description="" fullCommand="release" id="release" />
33
58
  <RakeTaskImpl id="release">
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile.lock CHANGED
@@ -1,19 +1,80 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- luo (0.1.0)
4
+ luo (0.1.1)
5
+ dotenv (~> 2.8, >= 2.8.1)
6
+ dry-cli (~> 1.0)
7
+ dry-configurable (~> 1.0, >= 1.0.1)
8
+ dry-schema (~> 1.13, >= 1.13.1)
9
+ faraday (~> 2.7, >= 2.7.4)
10
+ faraday-retry (~> 2.1)
11
+ zeitwerk (~> 2.6, >= 2.6.8)
5
12
 
6
13
  GEM
7
14
  remote: https://rubygems.org/
8
15
  specs:
16
+ concurrent-ruby (1.2.2)
17
+ diff-lcs (1.5.0)
18
+ dotenv (2.8.1)
19
+ dry-cli (1.0.0)
20
+ dry-configurable (1.0.1)
21
+ dry-core (~> 1.0, < 2)
22
+ zeitwerk (~> 2.6)
23
+ dry-core (1.0.0)
24
+ concurrent-ruby (~> 1.0)
25
+ zeitwerk (~> 2.6)
26
+ dry-inflector (1.0.0)
27
+ dry-initializer (3.1.1)
28
+ dry-logic (1.5.0)
29
+ concurrent-ruby (~> 1.0)
30
+ dry-core (~> 1.0, < 2)
31
+ zeitwerk (~> 2.6)
32
+ dry-schema (1.13.1)
33
+ concurrent-ruby (~> 1.0)
34
+ dry-configurable (~> 1.0, >= 1.0.1)
35
+ dry-core (~> 1.0, < 2)
36
+ dry-initializer (~> 3.0)
37
+ dry-logic (>= 1.4, < 2)
38
+ dry-types (>= 1.7, < 2)
39
+ zeitwerk (~> 2.6)
40
+ dry-types (1.7.1)
41
+ concurrent-ruby (~> 1.0)
42
+ dry-core (~> 1.0)
43
+ dry-inflector (~> 1.0)
44
+ dry-logic (~> 1.4)
45
+ zeitwerk (~> 2.6)
46
+ faraday (2.7.4)
47
+ faraday-net_http (>= 2.0, < 3.1)
48
+ ruby2_keywords (>= 0.0.4)
49
+ faraday-net_http (3.0.2)
50
+ faraday-retry (2.1.0)
51
+ faraday (~> 2.0)
9
52
  rake (13.0.6)
53
+ rspec (3.12.0)
54
+ rspec-core (~> 3.12.0)
55
+ rspec-expectations (~> 3.12.0)
56
+ rspec-mocks (~> 3.12.0)
57
+ rspec-core (3.12.2)
58
+ rspec-support (~> 3.12.0)
59
+ rspec-expectations (3.12.3)
60
+ diff-lcs (>= 1.2.0, < 2.0)
61
+ rspec-support (~> 3.12.0)
62
+ rspec-mocks (3.12.5)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.12.0)
65
+ rspec-support (3.12.0)
66
+ ruby2_keywords (0.0.5)
67
+ zeitwerk (2.6.8)
10
68
 
11
69
  PLATFORMS
12
70
  arm64-darwin-22
71
+ ruby
72
+ x86_64-linux
13
73
 
14
74
  DEPENDENCIES
15
75
  luo!
16
76
  rake (~> 13.0)
77
+ rspec (~> 3.12)
17
78
 
18
79
  BUNDLED WITH
19
80
  2.4.6
data/README.md CHANGED
@@ -29,3 +29,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
29
29
  ## Contributing
30
30
 
31
31
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/luo.
32
+
33
+ ENV.fetch('OPENAI_HOST', 'https://api.openai.com')
34
+ ENV.fetch('OPENAI_ACCESS_TOKEN')
data/README.zh.md ADDED
@@ -0,0 +1,9 @@
1
+ # Luo
2
+ 是一款基于大模型的开发框架(面向产品经理),当前支持大模型提供商有: OpenAI、星火大模型。通过DSL能够快速创作并且测试大模型的效果。
3
+
4
+ ## 安装
5
+ ```
6
+ gem install luo
7
+ ```
8
+
9
+ ## 使用
data/exe/luo ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "luo"
6
+
7
+ Dry::CLI.new(Luo::CLI::Commands).call
data/lib/luo/agent.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class Agent
5
+ attr_reader :context, :action_input, :client
6
+ def initialize(context: nil, action_input: nil, client: nil)
7
+ @context = context
8
+ @action_input = action_input
9
+ @client = client
10
+ end
11
+
12
+ def call
13
+ raise NotImplementedError, "call method must be implemented in subclass"
14
+ end
15
+
16
+ class << self
17
+ def on_call(&block)
18
+ define_method(:call, &block)
19
+ end
20
+
21
+ def on_call_with_final_result(&block)
22
+ define_method(:call) do
23
+ context.final_result = instance_eval(&block)
24
+ context.final_result
25
+ end
26
+ end
27
+
28
+ def self.create_parameter_method(method_name, not_provided = Object.new, &block)
29
+ define_method(method_name.to_sym) do |content = not_provided|
30
+ if content === not_provided
31
+ class_variable_get("@@#{method_name}")
32
+ else
33
+ content = block.call(content) if block_given?
34
+ class_variable_set("@@#{method_name}", content)
35
+ end
36
+ end
37
+ end
38
+
39
+ create_parameter_method(:agent_desc) { |content| content.gsub(/\n/, "") }
40
+ create_parameter_method(:agent_name) { |context| context.gsub(/[[:punct:]]/, '') }
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class AgentRunnerBase
5
+
6
+ include Configurable
7
+
8
+ setting :language, default: "en"
9
+
10
+ def initialize
11
+ on_init
12
+ end
13
+
14
+ def on_init
15
+ end
16
+
17
+ def context
18
+ @context ||= Luo::AgentRunnerContext.new
19
+ end
20
+
21
+ def reset_context
22
+ histories = context.histories
23
+ @context = Luo::AgentRunnerContext.new
24
+ @context.histories = histories
25
+ @context
26
+ end
27
+
28
+ def call(user_input)
29
+ context.user_input = user_input
30
+ on_request
31
+ on_result
32
+ on_run
33
+ after_run
34
+ rt = Marshal.load(Marshal.dump(context))
35
+ reset_context
36
+ rt
37
+ end
38
+
39
+ def on_request
40
+ raise NotImplementedError, "call method must be implemented in subclass"
41
+ end
42
+
43
+ def on_result
44
+ raise NotImplementedError, "call method must be implemented in subclass"
45
+ end
46
+
47
+ def after_run
48
+ end
49
+
50
+ def on_run
51
+ context.have_running_agents.each do |agent|
52
+ context.agent_results << { agent_name: agent.class.agent_name, agent_response: agent.call }
53
+ end
54
+ context.have_running_agents.clear
55
+ end
56
+
57
+ def add_agent(agent)
58
+ context.have_running_agents << agent
59
+ end
60
+
61
+ class << self
62
+
63
+ def agents
64
+ @agents ||= {}
65
+ end
66
+
67
+ [:on_result, :on_request, :on_init, :after_run].each do |method_name|
68
+ define_method(method_name) do |&block|
69
+ define_method(method_name, &block)
70
+ end
71
+ end
72
+
73
+ def register(agent)
74
+ agents[agent.agent_name] = agent
75
+ end
76
+
77
+ def language_info
78
+ if self.config.language == "en"
79
+ ""
80
+ elsif self.config.language == "zh"
81
+ "(must be a Chinese string)"
82
+ else
83
+ ""
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class AgentRunnerContext
5
+ attr_accessor :user_input, :action_input, :response, :agent_results, :final_result, :messages, :retries
6
+
7
+ def initialize
8
+ @agent_results = []
9
+ @retries = 0
10
+ end
11
+
12
+ def histories
13
+ @histories ||= MemoryHistory.new
14
+ end
15
+
16
+ def histories=(histories)
17
+ @histories = histories
18
+ end
19
+
20
+ def client
21
+ @client
22
+ end
23
+
24
+ def client=(client)
25
+ @client = client
26
+ end
27
+
28
+ def have_running_agents
29
+ @running_agents ||= Set.new
30
+ end
31
+
32
+ end
33
+ end
data/lib/luo/aiui.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class AIUI
5
+ include Configurable
6
+
7
+ setting :id, default: ENV.fetch('AIUI_APP_ID')
8
+ setting :key, default: ENV.fetch('AIUI_APP_KEY')
9
+ setting :host, default: 'http://api.iflyos.cn'
10
+ setting :uid, default: -> { SecureRandom.hex(16) }
11
+ setting :retries, default: ENV.fetch('AIUI_REQUEST_RETRIES', 3).to_i
12
+
13
+ include HttpClient.init_client
14
+
15
+ PARAMS = Dry::Schema.Params do
16
+ required(:appid).filled(:string)
17
+ required(:appkey).filled(:string)
18
+ required(:uid).filled(:string)
19
+ required(:text).filled(:string)
20
+ end
21
+
22
+ def request_aiui(params)
23
+ client.post('/external/ls_log/aiui_request', params.to_h)
24
+ end
25
+
26
+ def chat(messages)
27
+ if messages.is_a?(Messages)
28
+ messages = messages.to_a
29
+ end
30
+ message = messages.last&.fetch(:content, nil)
31
+ params = PARAMS.call(
32
+ appid: config.id,
33
+ appkey: config.key,
34
+ uid: config.uid.call,
35
+ text: message
36
+ )
37
+ return params.errors unless params.success?
38
+ request_aiui(params).body.dig("data", 0, "intent", "answer")
39
+ end
40
+ end
41
+ end
data/lib/luo/cli.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ module CLI
5
+ module Commands
6
+ extend Dry::CLI::Registry
7
+
8
+ class Version < Dry::CLI::Command
9
+ desc "Print version"
10
+
11
+ def call(*)
12
+ puts Luo::VERSION
13
+ end
14
+ end
15
+
16
+ class Commit < Dry::CLI::Command
17
+ desc "Commit with Luo"
18
+
19
+ argument :message, desc: "Commit message", required: true, type: :string
20
+
21
+ def call(message:, **)
22
+ messages = Messages.create.system(prompt: Luo::Prompts.luo_commit, context: {commit: message}).to_a
23
+ response = OpenAI.new.chat(messages)
24
+ exec "git commit -m '#{response}'"
25
+ end
26
+ end
27
+
28
+ register "version", Version, aliases: %w[v -v --version]
29
+ register "commit", Commit, aliases: ["c"]
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ module Configurable
5
+ def self.included(base)
6
+ base.extend(Dry::Configurable)
7
+ end
8
+
9
+ def config
10
+ self.class.config
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ module HttpClient
5
+ def self.init_client
6
+ Module.new do
7
+ define_method :client do
8
+ @client ||= Faraday.new(self.class.config.host) { |conn|
9
+ conn.request :authorization, 'Bearer', self.class.config.access_token if self.class.config.respond_to?(:access_token) && !self.class.config.access_token.nil?
10
+ conn.request :retry, max: (self.class.config.retries || 3), interval: 0.05,
11
+ interval_randomness: 0.5, backoff_factor: 2,
12
+ exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
13
+ conn.request :json
14
+ conn.response :json
15
+ }
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+
5
+ ##
6
+ # 用来保存回话历史的简单内存队列
7
+ class MemoryHistory
8
+ include Configurable
9
+
10
+ setting :max_size, default: 12
11
+
12
+ ##
13
+ # 初始化一个队列
14
+ # @param [Integer] max_size 队列的最大长度
15
+ def initialize(max_size = config.max_size)
16
+ @queue = []
17
+ @max_size = max_size
18
+ end
19
+
20
+ ##
21
+ # 入队
22
+ def enqueue(element)
23
+ if @queue.size == @max_size
24
+ @queue.shift
25
+ end
26
+ @queue << element
27
+ end
28
+
29
+ def user(content)
30
+ enqueue({role: "user", content: content})
31
+ end
32
+
33
+ def assistant(content)
34
+ enqueue({role: "assistant", content: content})
35
+ end
36
+
37
+ alias push enqueue
38
+
39
+ def dequeue
40
+ @queue.shift
41
+ end
42
+
43
+ def size
44
+ @queue.size
45
+ end
46
+
47
+ def to_a
48
+ @queue
49
+ end
50
+
51
+ def to_json
52
+ JSON.pretty_generate @queue
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class Messages
5
+ def initialize(history: [])
6
+ @history = history
7
+ @messages = []
8
+ @system = {}
9
+ end
10
+
11
+ def self.create_role_message_method(role)
12
+ define_method(role) do |text: nil, file: '', prompt: nil, context: {}|
13
+ if prompt.nil?
14
+ data = text || File.read(file)
15
+ else
16
+ data = prompt.render(context)
17
+ end
18
+ if role.to_s == "system"
19
+ @system = {role: role, content: data}
20
+ else
21
+ @messages << {role: role, content: data}
22
+ end
23
+ self
24
+ end
25
+ end
26
+
27
+ def self.create_role_message_methods
28
+ %w(system user assistant).each do |role|
29
+ create_role_message_method(role)
30
+ end
31
+ end
32
+
33
+ create_role_message_methods
34
+
35
+ def to_a
36
+ if @system.empty?
37
+ @history.to_a + @messages
38
+ else
39
+ (@history.to_a + @messages).unshift(@system)
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def create(history: [])
45
+ self.new(history: history)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class OpenAI
5
+ include Configurable
6
+
7
+ setting :access_token, default: ENV.fetch('OPENAI_ACCESS_TOKEN')
8
+ setting :retries, default: ENV.fetch('OPENAI_REQUEST_RETRIES', 3).to_i
9
+ setting :host, default: ENV.fetch('OPENAI_HOST', 'https://api.openai.com')
10
+ setting :temperature, default: ENV.fetch('OPENAI_TEMPERATURE', 1).to_i
11
+
12
+ include HttpClient.init_client
13
+
14
+ PARAMS = Dry::Schema.Params do
15
+ required(:model).filled(:string)
16
+ required(:temperature).filled(:float)
17
+ required(:messages).filled(:array)
18
+ optional(:top_p).maybe(:float)
19
+ optional(:n).maybe(:integer)
20
+ optional(:stream).maybe(:bool)
21
+ optional(:stop).maybe(:array)
22
+ optional(:max_tokens).maybe(:integer)
23
+ optional(:presence_penalty).maybe(:float)
24
+ optional(:frequency_penalty).maybe(:float)
25
+ optional(:logit_bias).maybe(:hash)
26
+ optional(:user).maybe(:string)
27
+ end
28
+
29
+ def chat_completions(params)
30
+ client.post('/v1/chat/completions', params.to_h)
31
+ end
32
+
33
+ def chat(messages)
34
+ if messages.is_a?(Messages)
35
+ messages = messages.to_a
36
+ end
37
+ params = PARAMS.call(
38
+ model: 'gpt-3.5-turbo',
39
+ temperature: config.temperature,
40
+ messages: messages
41
+ )
42
+ return params.errors unless params.success?
43
+ chat_completions(params).body.dig("choices", 0, "message", "content")
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Luo
4
+ class OpenAIAgentRunner < AgentRunnerBase
5
+
6
+ include Configurable
7
+
8
+ setting :retires, default: 3
9
+
10
+ on_init do
11
+ @openai = OpenAI.new
12
+ end
13
+
14
+ on_request do
15
+ context.messages = Messages.create(history: context.histories)
16
+ .system(prompt: Luo::Prompts.luo_agent_system)
17
+ .user(prompt: Luo::Prompts.luo_agent_input, context: {agents: self.class.agents, last_user_input: context.user_input})
18
+ context.response = @openai.chat(context.messages)
19
+ end
20
+
21
+ on_result do
22
+ actions = JSON.parse(context.response)
23
+ actions = [actions] if actions.is_a?(Hash)
24
+ actions.each do |action|
25
+ agent = self.class.agents[action['action']]&.new(
26
+ context: context,
27
+ action_input: action['action_input'],
28
+ client: @openai
29
+ )
30
+ add_agent(agent) if agent
31
+ if action['action'] == "Final Answer"
32
+ context.final_result = action['action_input']
33
+ context.histories.user(context.user_input)
34
+ context.histories.assistant(context.final_result)
35
+ end
36
+ end
37
+ end
38
+
39
+ after_run do
40
+ if context.retries < config.retires && context.final_result.nil?
41
+ context.messages = context.messages.assistant(
42
+ prompt: Luo::Prompts.luo_agent_tool_input,
43
+ context: {
44
+ tools_response: context.agent_results
45
+ }
46
+ )
47
+ context.response = @openai.chat(context.messages)
48
+ context.retries += 1
49
+ on_result
50
+ on_run
51
+ after_run
52
+ end
53
+ end
54
+ end
55
+ end