luogu 0.1.14 → 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5932a14c78478ce16a36f5cfd141834e9f677c90ddd7c398e8cdcbb66bd1ea4
4
- data.tar.gz: a870afd3b6808cf89f01695a47b57c03ea906bf7b59e6b66bb28246a93215fac
3
+ metadata.gz: 9fc3971fa31ce1f405672f6093f5fe66b383aced4254d2fefebd562d54a5fcc9
4
+ data.tar.gz: 14857af32922e9d59d71405d75f64fb36436dbd621ed955ec3acc6e3c9be50e1
5
5
  SHA512:
6
- metadata.gz: c5e59722ac54f8a247853bd67161297eae0976a6d402819ea4a774460f3ab4d2a5758c5511b9a4758747e5a1df93f760e7ad3f5084c395c5efe496f4d4abf17f
7
- data.tar.gz: 5f69526dfc2f0b56f91daac355575703b0f6389ed889584663510598bf0f0fb5b6a53f95ccd7344656f621e0ec36b629c2d7c8009ffe9e07aa89b5a0b678a59d
6
+ metadata.gz: 7caaf61b33788dbfdae89c671b30ce7e86a5c312ed357b8615315e911fe023ef33659e7b26bafc8f9e8b98ee3e6c70b438a9037218835e42e144994f0355c5cc
7
+ data.tar.gz: 26d1472f1e2029844274c6f2f9e0cb2940ef399e656a5eebd546b7c597037f852fee1f2a334158e0bc9caa2f2335a13ab0207f47f08b72215fffc658b0dc1739
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- luogu (0.1.14)
4
+ luogu (0.1.15)
5
5
  dotenv (~> 2.8, >= 2.8.1)
6
6
  dry-cli (~> 1.0)
7
7
  http (~> 5.1, >= 5.1.1)
data/README.md CHANGED
@@ -4,7 +4,8 @@
4
4
  用来开发 prompt 工程的工具
5
5
 
6
6
  ### 更新记录
7
- - 0.1.14 http库替换成HTTP.rb并且加入了重试机制,默认为3次,可通过设置环境变量 OPENAI_REQUEST_RETRIES 来设置次数
7
+ - 0.1.15 http库替换成HTTP.rb并且加入了重试机制,默认为3次,可通过设置环境变量 OPENAI_REQUEST_RETRIES 来设置次数
8
+ - 0.1.16 增加对agent的支持
8
9
 
9
10
  ### 安装
10
11
  - 安装ruby,要求2.6以上,Mac自带不需要再安装
@@ -0,0 +1,31 @@
1
+ module Luogu
2
+ class Agent
3
+
4
+ def call(input)
5
+ raise NotImplementedError, "call method must be implemented in subclass"
6
+ end
7
+
8
+ class << self
9
+ def desc(content)
10
+ @_desc_ = content
11
+ end
12
+
13
+ def input_desc(content)
14
+ @_input_desc_ = content
15
+ end
16
+
17
+ def name
18
+ self.to_s
19
+ end
20
+
21
+ def description
22
+ @_desc_
23
+ end
24
+
25
+ def input_description
26
+ @_input_desc_
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,159 @@
1
+ module Luogu
2
+ class AgentRunner
3
+ attr_accessor :system_prompt_template, :user_input_prompt_template
4
+ attr_reader :session
5
+
6
+ def initialize(system_prompt_template: nil, user_input_prompt_template: nil, tools_response_prompt_template: nil, session: Session.new)
7
+ @system_prompt_template = system_prompt_template || load_system_prompt_default_template
8
+ @user_input_prompt_template = user_input_prompt_template || load_user_input_prompt_default_template
9
+ @tools_response_prompt_template = tools_response_prompt_template || load_tools_response_prompt_default
10
+
11
+ @agents = []
12
+ @session = session
13
+
14
+ @chatgpt_request_body = Luogu::OpenAI::ChatRequestBody.new(temperature: 0)
15
+ @chatgpt_request_body.stop = ["\nObservation:", "\n\tObservation:"]
16
+ @limit_history = ENV.fetch('OPENAI_LIMIT_HISTORY', '6').to_i * 2
17
+ @histories = HistoryQueue.new @limit_history
18
+
19
+ @last_user_input = ''
20
+ @tools_response = []
21
+ end
22
+
23
+ def openai_configuration(&block)
24
+ block.call @chatgpt_request_body
25
+ end
26
+
27
+ def register(agent)
28
+ raise AssertionError.new('agent must inherit from Luogu::Agent') unless agent < Agent
29
+ @agents << agent
30
+ self
31
+ end
32
+
33
+ def request(messages)
34
+ @chatgpt_request_body.messages = messages
35
+ response = client.chat(params: @chatgpt_request_body.to_h)
36
+ if response.code == 200
37
+ response.parse
38
+ else
39
+ logger.error response.body.to_s
40
+ raise OpenAI::RequestError
41
+ end
42
+ end
43
+
44
+ def user_input_prompt_template
45
+ @user_input_prompt_template.result binding
46
+ end
47
+
48
+ def system_prompt_template
49
+ @system_prompt_template.result binding
50
+ end
51
+
52
+ def tools_response_prompt_template
53
+ @tools_response_prompt_template.result binding
54
+ end
55
+
56
+ def find_final_answer(content)
57
+ if content.is_a?(Hash) && content['action'] == 'Final Answer'
58
+ content['action_input']
59
+ elsif content.is_a?(Array)
60
+ result = content.find { |element| element["action"] == "Final Answer" }
61
+ if result
62
+ result["action_input"]
63
+ else
64
+ nil
65
+ end
66
+ else
67
+ nil
68
+ end
69
+ end
70
+
71
+ def find_and_save_final_answer(content)
72
+ if answer = self.find_final_answer(content)
73
+ self.save_history(answer)
74
+ answer
75
+ else
76
+ nil
77
+ end
78
+ end
79
+
80
+ def save_history(finnal_answer)
81
+ @histories.enqueue({role: "user", content: @last_user_input})
82
+ @histories.enqueue({role: "assistant", content: finnal_answer})
83
+ end
84
+
85
+ def parse_json(markdown)
86
+ json_regex = /```json(.+?)```/im
87
+ json_blocks = markdown.scan(json_regex)
88
+ result_json = nil
89
+ json_blocks.each do |json_block|
90
+ json_string = json_block[0]
91
+ result_json = JSON.parse(json_string)
92
+ end
93
+
94
+ if result_json.nil?
95
+ JSON.parse markdown
96
+ else
97
+ result_json
98
+ end
99
+ end
100
+
101
+ def create_messages(messages)
102
+ [{role: "system", content: self.system_prompt_template}] + @histories.to_a + messages
103
+ end
104
+
105
+ def request_chat(messages)
106
+ logger.debug "request chat: #{messages}"
107
+ response = request messages
108
+ content = self.parse_json response.dig("choices", 0, "message", "content")
109
+ logger.debug content
110
+ if answer = self.find_and_save_final_answer(content)
111
+ logger.info "finnal answer: #{answer}"
112
+ elsif content.is_a?(Array)
113
+ self.run_agents(content)
114
+ end
115
+ end
116
+
117
+ def chat(message)
118
+ @last_user_input = message
119
+ messages = self.create_messages([{role: "user", content: self.user_input_prompt_template}])
120
+ self.request_chat(messages)
121
+ end
122
+
123
+ def run_agents(agents)
124
+ if answer = self.find_and_save_final_answer(agents)
125
+ logger.info "finnal answer: #{answer}"
126
+ return
127
+ end
128
+ @tools_response = []
129
+ agents.each do |agent|
130
+ agent_class = Module.const_get(agent['action'])
131
+ logger.info "running #{agent_class}"
132
+ response = agent_class.new.call(agent['action_input'])
133
+ @tools_response << {name: agent['action'], response: response}
134
+ end
135
+ messages = self.create_messages([
136
+ {role: "user", content: self.user_input_prompt_template},
137
+ {role: "assistant", content: agents.to_json},
138
+ {role: "user", content: self.tools_response_prompt_template},
139
+ ])
140
+ self.request_chat messages
141
+ end
142
+
143
+ private
144
+ def load_system_prompt_default_template
145
+ PromptTemplate.load_template('agent_system.md.erb')
146
+ end
147
+
148
+ def load_user_input_prompt_default_template
149
+ PromptTemplate.load_template('agent_input.md.erb')
150
+ end
151
+
152
+ def load_tools_response_prompt_default
153
+ PromptTemplate.load_template('agent_tool_input.md.erb')
154
+ end
155
+ end
156
+
157
+ class AssertionError < StandardError
158
+ end
159
+ end
data/lib/luogu/chatgpt.rb CHANGED
@@ -26,7 +26,7 @@ module Luogu
26
26
  @context = OpenStruct.new
27
27
 
28
28
  if @plugin.setup_proc
29
- @plugin.setup_proc.call(self, OpenStruct.new)
29
+ @plugin.setup_proc.call(self, @context)
30
30
  end
31
31
  end
32
32
 
@@ -41,10 +41,10 @@ module Luogu
41
41
  @context.request_params = params
42
42
  params = @plugin.before_request_proc.call(self, @context).request_params
43
43
  end
44
- response = client.chat(parameters: params)
44
+ response = client.chat(parameters: params).parse
45
45
  @context.response = response
46
46
  @plugin.after_request_proc.call(self, @context) if @plugin.after_request_proc
47
- response.parse.dig("choices", 0, "message", "content")
47
+ response.dig("choices", 0, "message", "content")
48
48
  end
49
49
 
50
50
  def chat(user_message)
data/lib/luogu/cli.rb CHANGED
@@ -74,11 +74,21 @@ module Luogu
74
74
 
75
75
  end
76
76
 
77
+ class Agent < Dry::CLI::Command
78
+ desc "运行 Agent 文件"
79
+ argument :agent_file, type: :string, require: true, desc: "运行 agent 文件"
80
+
81
+ def call(agent_file: nil, **)
82
+ exec "ruby #{agent_file}"
83
+ end
84
+ end
85
+
77
86
  register "version", Version, aliases: ["v", "-v", "--version"]
78
87
  register "build", Build, aliases: ["b"]
79
88
  register "run", Run, aliases: ["r"]
80
89
  register "generate", Generate, aliases: ["g"]
81
90
  register "test", Test, aliases: ["t"]
91
+ register "agent", Agent, aliases: ["a"]
82
92
 
83
93
  end
84
94
  end
data/lib/luogu/init.rb CHANGED
@@ -7,7 +7,10 @@ require "dry/cli"
7
7
  require 'fileutils'
8
8
  require 'ostruct'
9
9
  require 'benchmark'
10
+ require 'erb'
11
+ require 'logger'
10
12
 
13
+ require_relative "prompt_template"
11
14
  require_relative 'openai'
12
15
  require_relative 'plugin'
13
16
  require_relative 'history_queue'
@@ -15,10 +18,26 @@ require_relative "prompt_parser"
15
18
  require_relative "chatgpt"
16
19
  require_relative "cli"
17
20
 
21
+ require_relative "agent"
22
+ require_relative "session"
23
+ require_relative "agent_runner"
24
+
25
+
26
+
18
27
  def client
19
28
  $client ||= Luogu::OpenAI::Client.new
20
29
  end
21
30
 
31
+ def logger_init
32
+ logger = Logger.new(STDOUT)
33
+ logger.level = ENV['LOG_LEVEL'] ? Logger.const_get(ENV['LOG_LEVEL']) : Logger::INFO
34
+ logger
35
+ end
36
+
37
+ def logger
38
+ $logger ||= logger_init
39
+ end
40
+
22
41
  class String
23
42
  def cover_chinese
24
43
  self.gsub(/[\uFF01-\uFF0F]/) {|s| (s.ord-65248).chr}
data/lib/luogu/openai.rb CHANGED
@@ -1,11 +1,31 @@
1
1
  module Luogu::OpenAI
2
+
3
+ class ChatRequestBody < Struct.new(:model, :messages, :temperature,
4
+ :top_p, :n, :stream, :stop, :max_tokens,
5
+ :presence_penalty, :frequency_penalty, :logit_bias, :user)
6
+
7
+ def initialize(*args)
8
+ defaults = { model: "gpt-3.5-turbo", messages: []}
9
+ super(**defaults.merge(args.first || {}))
10
+ end
11
+
12
+ def to_h
13
+ super.reject { |_, v| v.nil? }
14
+ end
15
+
16
+ alias to_hash to_h
17
+ end
18
+
19
+ class RequestError < StandardError
20
+ end
21
+
2
22
  class Client
3
23
  def initialize
4
24
  @access_token = ENV.fetch('OPENAI_ACCESS_TOKEN')
5
25
  @client = HTTP.auth("Bearer #{@access_token}").persistent "https://api.openai.com"
6
26
  end
7
27
 
8
- def chat(parameters: _params, params: nil, retries: 3)
28
+ def chat(parameters: nil, params: nil, retries: 3)
9
29
  params ||= parameters
10
30
  retries_left = ENV.fetch('OPENAI_REQUEST_RETRIES', retries)
11
31
  begin
@@ -0,0 +1,24 @@
1
+ module Luogu
2
+ class PromptTemplate
3
+ def initialize(file_path)
4
+ @file_path = file_path
5
+ @template = ERB.new(File.read(@file_path), trim_mode: '-')
6
+ end
7
+
8
+ def result(_binding_ = nil)
9
+ @template.result(_binding_ || binding)
10
+ end
11
+
12
+ alias render result
13
+
14
+ class << self
15
+ def load(file_path)
16
+ self.new(file_path)
17
+ end
18
+
19
+ def load_template(file_name)
20
+ self.new File.join(File.dirname(__FILE__), 'templates', file_name)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ module Luogu
2
+ class Session
3
+ end
4
+ end
@@ -0,0 +1,39 @@
1
+ TOOLS
2
+ ------
3
+ Assistant can ask the user to use tools to look up information that may be helpful in answering the users original question. The tools the human can use are:
4
+
5
+ <% @agents.each do |agent| -%>
6
+ > <%= agent.name %>: <%= agent.description %>
7
+ <% end -%>
8
+
9
+ RESPONSE FORMAT INSTRUCTIONS
10
+ ----------------------------
11
+
12
+ When responding to me please, please output a response in one of two formats:
13
+
14
+ **Option 1:**
15
+ Use this if you want humans to use the tools.
16
+ Markdown code snippet formatted in the following schema:
17
+
18
+ ```json
19
+ [{
20
+ "action": string \ The action to take. Must be one of <%= @agents.map do |a| a.name end.join(', ') %>
21
+ "action_input": string \ The input to the action
22
+ }]
23
+ ```
24
+
25
+ **Option #2:**
26
+ Use this if you want to respond directly to the human. Markdown code snippet formatted in the following schema:
27
+
28
+ ```json
29
+ {
30
+ "action": "Final Answer",
31
+ "action_input": string \ You should put what you want to return to use here
32
+ }
33
+ ```
34
+
35
+ USER'S INPUT
36
+ --------------------
37
+ Here is the user's input (remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else):
38
+
39
+ <%= @last_user_input %>
@@ -0,0 +1,4 @@
1
+ Assistant is a large language model trained by OpenAI.
2
+ Assistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussions on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand.
3
+ Assistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics.
4
+ Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist.
@@ -0,0 +1,10 @@
1
+ TOOL RESPONSE:
2
+ ---------------------
3
+ <% @tools_response.each do |r| -%>
4
+ > <%= r[:name] %>: <%= r[:response] %>
5
+ <% end -%>
6
+
7
+ USER'S INPUT
8
+ --------------------
9
+
10
+ Okay, so what is the response to my last comment? If using information obtained from the tools you must mention it explicitly without mentioning the tool names - I have forgotten all TOOL RESPONSES! Remember to respond with a markdown code snippet of a json blob with a single action, and NOTHING else.
data/lib/luogu/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Luogu
4
- VERSION = "0.1.14"
4
+ VERSION = "0.1.16"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luogu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - MJ
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-19 00:00:00.000000000 Z
11
+ date: 2023-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -92,6 +92,8 @@ files:
92
92
  - Rakefile
93
93
  - exe/luogu
94
94
  - lib/luogu.rb
95
+ - lib/luogu/agent.rb
96
+ - lib/luogu/agent_runner.rb
95
97
  - lib/luogu/chatgpt.rb
96
98
  - lib/luogu/cli.rb
97
99
  - lib/luogu/history_queue.rb
@@ -99,6 +101,11 @@ files:
99
101
  - lib/luogu/openai.rb
100
102
  - lib/luogu/plugin.rb
101
103
  - lib/luogu/prompt_parser.rb
104
+ - lib/luogu/prompt_template.rb
105
+ - lib/luogu/session.rb
106
+ - lib/luogu/templates/agent_input.md.erb
107
+ - lib/luogu/templates/agent_system.md.erb
108
+ - lib/luogu/templates/agent_tool_input.md.erb
102
109
  - lib/luogu/version.rb
103
110
  - luogu.gemspec
104
111
  - sig/luogu.rbs