luo 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.idea/inspectionProfiles/Project_Default.xml +9 -0
- data/.idea/luo.iml +26 -1
- data/.rspec +1 -0
- data/Gemfile.lock +62 -1
- data/README.md +3 -0
- data/README.zh.md +9 -0
- data/exe/luo +7 -0
- data/lib/luo/agent.rb +45 -0
- data/lib/luo/agent_runner_base.rb +89 -0
- data/lib/luo/agent_runner_context.rb +33 -0
- data/lib/luo/aiui.rb +41 -0
- data/lib/luo/cli.rb +33 -0
- data/lib/luo/configurable.rb +14 -0
- data/lib/luo/http_client.rb +21 -0
- data/lib/luo/memory_history.rb +55 -0
- data/lib/luo/messages.rb +50 -0
- data/lib/luo/open_ai.rb +48 -0
- data/lib/luo/open_ai_agent_runner.rb +55 -0
- data/lib/luo/prompt_template.rb +39 -0
- data/lib/luo/prompts.rb +32 -0
- data/lib/luo/templates/luo_agent_input.md.erb +39 -0
- data/lib/luo/templates/luo_agent_system.md.erb +4 -0
- data/lib/luo/templates/luo_agent_tool_input.md.erb +10 -0
- data/lib/luo/templates/luo_commit.md.erb +2 -0
- data/lib/luo/templates/luo_xinghuo_agent_input.md.erb +10 -0
- data/lib/luo/templates/luo_xinghuo_agent_tool_input.md.erb +16 -0
- data/lib/luo/templates/prompt.demo.erb +1 -0
- data/lib/luo/version.rb +1 -1
- data/lib/luo/xinghuo.rb +50 -0
- data/lib/luo/xinghuo_agent_runner.rb +56 -0
- data/lib/luo.rb +18 -1
- data/luo.gemspec +9 -0
- data/sig/luo/agent.rbs +21 -0
- data/sig/luo/agent_runner_base.rbs +25 -0
- data/sig/luo/agent_runner_context.rbs +19 -0
- data/sig/luo/aiui.rbs +9 -0
- data/sig/luo/configurable.rbs +5 -0
- data/sig/luo/env.rbs +9 -0
- data/sig/luo/http_client.rbs +9 -0
- data/sig/luo/memory_history.rbs +18 -0
- data/sig/luo/messages.rbs +20 -0
- data/sig/luo/open_ai.rbs +11 -0
- data/sig/luo/prompt_template.rbs +18 -0
- data/sig/luo/prompts.rbs +15 -0
- data/sig/luo/xinghuo.rbs +9 -0
- metadata +186 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7755df3627fd226e48254313905aaaf420d1804bb86d95c67ea0383ca3b6f677
|
4
|
+
data.tar.gz: 17764e813034fd6d9f159fffdc8a954e380aa41df0a1e55b45a8856197343916
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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
data/exe/luo
ADDED
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,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
|
data/lib/luo/messages.rb
ADDED
@@ -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
|
data/lib/luo/open_ai.rb
ADDED
@@ -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
|