luo 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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