luogu 0.1.19 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +10 -1
- data/LICENSE +21 -0
- data/README.md +62 -48
- data/lib/luogu/agent_runner.rb +76 -116
- data/lib/luogu/application.rb +25 -0
- data/lib/luogu/base.rb +17 -0
- data/lib/luogu/{chatgpt.rb → chatllm.rb} +55 -40
- data/lib/luogu/cli.rb +4 -4
- data/lib/luogu/error.rb +11 -0
- data/lib/luogu/init.rb +10 -19
- data/lib/luogu/openai.rb +62 -30
- data/lib/luogu/plugin.rb +28 -40
- data/lib/luogu/terminal.rb +40 -0
- data/lib/luogu/version.rb +1 -1
- data/luogu.gemspec +1 -0
- data/prompt.md +2 -0
- data/prompt.plugin.rb +9 -0
- data/sig/luogu/client.rbs +9 -0
- metadata +31 -4
- data/lib/luogu/session.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50079e5c23717d6caf07800b5878ba3b6398252caea1ff033175e16cbbe360b7
|
4
|
+
data.tar.gz: 4fa6b63d827b806de779caa38e085126c0fd112b02baad9984fece843801d7f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb4de03374038e36faa54427c667017e03f1ab6a1980b606e69d8975f28395adba2c6be9f3065b2d1a578f43c3c89385abe9ac429d888529f172734167bad0dd
|
7
|
+
data.tar.gz: 18c4ee842a0e12b64cce016798a52f6efd9b89d97018e2b800c64226d743f042f866977418c550a0d2e2db7fa2bd041a3fe2729f28fbbc7345048611a96696a1
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
luogu (0.
|
4
|
+
luogu (0.2.0)
|
5
5
|
dotenv (~> 2.8, >= 2.8.1)
|
6
6
|
dry-cli (~> 1.0)
|
7
|
+
dry-configurable (~> 1.0, >= 1.0.1)
|
7
8
|
http (~> 5.1, >= 5.1.1)
|
8
9
|
tty-prompt (~> 0.23.1)
|
9
10
|
|
@@ -12,10 +13,17 @@ GEM
|
|
12
13
|
specs:
|
13
14
|
addressable (2.8.4)
|
14
15
|
public_suffix (>= 2.0.2, < 6.0)
|
16
|
+
concurrent-ruby (1.2.2)
|
15
17
|
domain_name (0.5.20190701)
|
16
18
|
unf (>= 0.0.5, < 1.0.0)
|
17
19
|
dotenv (2.8.1)
|
18
20
|
dry-cli (1.0.0)
|
21
|
+
dry-configurable (1.0.1)
|
22
|
+
dry-core (~> 1.0, < 2)
|
23
|
+
zeitwerk (~> 2.6)
|
24
|
+
dry-core (1.0.0)
|
25
|
+
concurrent-ruby (~> 1.0)
|
26
|
+
zeitwerk (~> 2.6)
|
19
27
|
ffi (1.15.5)
|
20
28
|
ffi-compiler (1.0.1)
|
21
29
|
ffi (>= 1.0.0)
|
@@ -49,6 +57,7 @@ GEM
|
|
49
57
|
unf_ext
|
50
58
|
unf_ext (0.0.8.2)
|
51
59
|
wisper (2.0.1)
|
60
|
+
zeitwerk (2.6.7)
|
52
61
|
|
53
62
|
PLATFORMS
|
54
63
|
arm64-darwin-22
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Mj
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
# Luogu
|
2
|
-
> 锣鼓,现在的写代码就如古代的求神仪式一样,以前敲锣打鼓求天,现在敲锣打鼓求大模型
|
3
2
|
|
4
|
-
|
3
|
+
> 锣鼓,现在的写代码就如古代的求神仪式一样,以前敲锣打鼓求天,现在敲锣打鼓求大模型,PS.本文档也是通过GPT来做优化的,具体查看`prompt.md`和`prompt.plugin.rb`
|
5
4
|
|
6
|
-
|
7
|
-
- 0.1.15 http库替换成HTTP.rb并且加入了重试机制,默认为3次,可通过设置环境变量 OPENAI_REQUEST_RETRIES 来设置次数
|
8
|
-
- 0.1.16 增加对agent的支持
|
5
|
+
Luogu是一个针对产品经理的Prompt设计工具,可以帮助你更方便地编写和测试Prompt。
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
## 安装
|
8
|
+
|
9
|
+
1. 安装Ruby,要求2.6以上,Mac自带不需要再安装
|
10
|
+
2. 在终端中运行`gem install luogu`命令安装Luogu
|
11
|
+
3. 如果使用Mac可能需要使用sudo权限
|
12
|
+
4. 如果需要在终端显示markdown,需要安装[glow](https://github.com/charmbracelet/glow)
|
13
|
+
|
14
|
+
## 使用
|
15
|
+
|
16
|
+
Luogu提供了以下命令:
|
15
17
|
|
16
|
-
### 使用
|
17
18
|
```Bash
|
18
19
|
Commands:
|
19
20
|
luogu build PROMPT_FILE [TARGET_FILE] # 编译 Prompt.md 成能够提交给 ChatGPT API 的 messages. 默认输出为 <同文件名>.json
|
@@ -23,52 +24,28 @@ Commands:
|
|
23
24
|
luogu version # 打印版本
|
24
25
|
```
|
25
26
|
|
26
|
-
你可以在项目目录的.env
|
27
|
+
你可以在项目目录的.env中设置下面的环境变量,或者直接系统设置:
|
28
|
+
|
27
29
|
```
|
28
30
|
OPENAI_ACCESS_TOKEN=zheshiyigetoken
|
29
31
|
OPENAI_TEMPERATURE=0.7
|
30
32
|
OPENAI_LIMIT_HISTORY=6
|
31
33
|
```
|
32
34
|
|
33
|
-
|
34
|
-
```
|
35
|
-
@s
|
36
|
-
你是一个罗纳尔多的球迷
|
35
|
+
## 进入run模式
|
37
36
|
|
38
|
-
|
39
|
-
好的
|
40
|
-
|
41
|
-
@u
|
42
|
-
罗纳尔多是谁?
|
37
|
+
在run模式下,你可以使用以下命令:
|
43
38
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
如果需要处理历史记录的是否进入上下文可以使用,在 prompt.md写入
|
49
|
-
|
50
|
-
|
51
|
-
@callback
|
52
|
-
```ruby
|
53
|
-
if assistant_message =~ /```ruby/
|
54
|
-
puts "记录本次记录"
|
55
|
-
self.push_history(user_message, assistant_message)
|
56
|
-
else
|
57
|
-
puts "抛弃本次记录"
|
58
|
-
end
|
59
|
-
```
|
39
|
+
- save:保存对话
|
40
|
+
- row history:查看对话历史
|
41
|
+
- history:查看当前上下文
|
42
|
+
- exit:退出
|
60
43
|
|
44
|
+
## 插件模式
|
61
45
|
|
62
|
-
|
63
|
-
- save 保存对话
|
64
|
-
- row history 查看对话历史
|
65
|
-
- history 查看当前上下文
|
66
|
-
- exit 退出
|
46
|
+
在run和test中可以使用插件模式,可以使用--plugin=<file>.plugin.rb。默认情况下,你可以使用<文件名>.plugin.rb来实现一个prompt.md的插件。
|
67
47
|
|
68
|
-
|
69
|
-
在 run 和 test 中可以使用,可以使用 --plugin=<file>.plugin.rb
|
70
|
-
默认情况下你可以使用 <文件名>.plugin.rb 来实现一个prompt.md的插件
|
71
|
-
在插件中有两个对象
|
48
|
+
在插件中有两个对象:
|
72
49
|
|
73
50
|
```ruby
|
74
51
|
#gpt
|
@@ -88,7 +65,8 @@ OpenStruct.new(
|
|
88
65
|
# 如果需要在方法中使用使用变量传递,必须使用context包括你要的变量名,比如 context.name = "luogu"
|
89
66
|
```
|
90
67
|
|
91
|
-
|
68
|
+
支持的回调:
|
69
|
+
|
92
70
|
```ruby
|
93
71
|
# 所有方法都必须返回context
|
94
72
|
# 可以使用
|
@@ -127,8 +105,44 @@ end
|
|
127
105
|
after_save_historydo |gpt, context|
|
128
106
|
context
|
129
107
|
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## 示例
|
111
|
+
|
112
|
+
下面是一个prompt.md的示例:
|
130
113
|
|
114
|
+
```
|
115
|
+
@s
|
116
|
+
你是一个罗纳尔多的球迷
|
131
117
|
|
118
|
+
@a
|
119
|
+
好的
|
120
|
+
|
121
|
+
@u
|
122
|
+
罗纳尔多是谁?
|
123
|
+
|
124
|
+
@a
|
125
|
+
是大罗,不是C罗
|
132
126
|
```
|
133
127
|
|
134
|
-
|
128
|
+
如果需要处理历史记录的是否进入上下文可以使用,在prompt.md写入:
|
129
|
+
|
130
|
+
@callback
|
131
|
+
```ruby
|
132
|
+
if assistant_message =~ /```ruby/
|
133
|
+
puts "记录本次记录"
|
134
|
+
self.push_history(user_message, assistant_message)
|
135
|
+
else
|
136
|
+
puts "抛弃本次记录"
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
## 更新记录
|
141
|
+
|
142
|
+
- 0.1.15:http库替换成HTTP.rb并且加入了重试机制,默认为3次,可通过设置环境变量OPENAI_REQUEST_RETRIES来设置次数
|
143
|
+
- 0.1.16:增加对agent的支持
|
144
|
+
- 0.2.0:重构了代码,agent的支持基本达到可用状态,具体看`bin/agent.rb`示例,有破坏性更新,不兼容0.1.x的agent写法
|
145
|
+
|
146
|
+
## License
|
147
|
+
|
148
|
+
This project is licensed under the terms of the MIT license. See the [LICENSE](LICENSE) file for details.
|
data/lib/luogu/agent_runner.rb
CHANGED
@@ -1,27 +1,44 @@
|
|
1
|
-
|
2
|
-
class AgentRunner
|
3
|
-
attr_accessor :system_prompt_template, :user_input_prompt_template
|
4
|
-
attr_reader :session, :histories
|
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
|
1
|
+
# frozen_string_literal: true
|
18
2
|
|
3
|
+
module Luogu
|
4
|
+
class AgentRunner < Base
|
5
|
+
setting :templates do
|
6
|
+
setting :system, default: PromptTemplate.load_template('agent_system.md.erb')
|
7
|
+
setting :user, default: PromptTemplate.load_template('agent_input.md.erb')
|
8
|
+
setting :tool, default: PromptTemplate.load_template('agent_tool_input.md.erb')
|
9
|
+
end
|
10
|
+
|
11
|
+
setting :run_agent_retries, default: Application.config.run_agent_retries
|
12
|
+
|
13
|
+
setting :provider do
|
14
|
+
setting :parameter_model, default: ->() {
|
15
|
+
OpenAI::ChatRequestParams.new(
|
16
|
+
model: 'gpt-3.5-turbo',
|
17
|
+
stop: %W[\nObservation: \n\tObservation:],
|
18
|
+
temperature: 0
|
19
|
+
)
|
20
|
+
}
|
21
|
+
setting :request, default: ->(params) { OpenAI.chat(params: params) }
|
22
|
+
setting :parse, default: ->(response) { OpenAI.chat_response_handle(response) }
|
23
|
+
setting :find_final_answer, default: ->(content) { OpenAI.find_final_answer(content) }
|
24
|
+
setting :history_limit, default: Application.config.openai.history_limit
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :request_params, :agents
|
28
|
+
def initialize()
|
29
|
+
@request_params = provider.parameter_model.call
|
30
|
+
@histories = HistoryQueue.new provider.history_limit
|
19
31
|
@last_user_input = ''
|
32
|
+
@agents = []
|
20
33
|
@tools_response = []
|
21
34
|
end
|
22
35
|
|
23
|
-
def
|
24
|
-
|
36
|
+
def provider
|
37
|
+
config.provider
|
38
|
+
end
|
39
|
+
|
40
|
+
def templates
|
41
|
+
config.templates
|
25
42
|
end
|
26
43
|
|
27
44
|
def register(agent)
|
@@ -30,126 +47,69 @@ module Luogu
|
|
30
47
|
self
|
31
48
|
end
|
32
49
|
|
33
|
-
def
|
34
|
-
@
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
+
def run(text)
|
51
|
+
@last_user_input = text
|
52
|
+
messages = create_messages(
|
53
|
+
[{role: "user", content: templates.user.result(binding)}]
|
54
|
+
)
|
55
|
+
request(messages)
|
50
56
|
end
|
57
|
+
alias_method :chat, :run
|
51
58
|
|
52
|
-
def
|
53
|
-
|
59
|
+
def create_messages(messages)
|
60
|
+
[
|
61
|
+
{ role: "system", content: templates.system.result(binding) }
|
62
|
+
] + @histories.to_a + messages
|
54
63
|
end
|
55
64
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
65
|
+
def request(messages, run_agent_retries: 0)
|
66
|
+
logger.debug "request chat: #{messages}"
|
67
|
+
@request_params.messages = messages
|
68
|
+
response = provider.request.call(@request_params.to_h)
|
69
|
+
unless response.code == 200
|
70
|
+
logger.error response.body
|
71
|
+
raise RequestError
|
72
|
+
end
|
73
|
+
content = provider.parse.call(response)
|
74
|
+
logger.debug content
|
75
|
+
if (answer = self.find_and_save_final_answer(content))
|
76
|
+
logger.info "final answer: #{answer}"
|
77
|
+
answer
|
59
78
|
elsif content.is_a?(Array)
|
60
|
-
|
61
|
-
if result
|
62
|
-
result["action_input"]
|
63
|
-
else
|
64
|
-
nil
|
65
|
-
end
|
66
|
-
else
|
67
|
-
nil
|
79
|
+
run_agents(content, messages, run_agent_retries: run_agent_retries)
|
68
80
|
end
|
69
81
|
end
|
70
82
|
|
71
83
|
def find_and_save_final_answer(content)
|
72
|
-
if answer =
|
73
|
-
|
84
|
+
if (answer = provider.find_final_answer.call(content))
|
85
|
+
@histories.enqueue({role: "user", content: @last_user_input})
|
86
|
+
@histories.enqueue({role: "assistant", content: answer})
|
74
87
|
answer
|
75
88
|
else
|
76
89
|
nil
|
77
90
|
end
|
78
91
|
end
|
79
92
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
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, messages)
|
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, _messages_)
|
124
|
-
if answer = self.find_and_save_final_answer(agents)
|
125
|
-
logger.info "finnal answer: #{answer}"
|
93
|
+
def run_agents(agents, _messages_, run_agent_retries: 0)
|
94
|
+
return if run_agent_retries > config.run_agent_retries
|
95
|
+
run_agent_retries += 1
|
96
|
+
if (answer = find_and_save_final_answer(agents))
|
97
|
+
logger.info "final answer: #{answer}"
|
126
98
|
return
|
127
99
|
end
|
128
100
|
@tools_response = []
|
129
101
|
agents.each do |agent|
|
130
102
|
agent_class = Module.const_get(agent['action'])
|
131
|
-
logger.info "running #{agent_class}"
|
103
|
+
logger.info "#{run_agent_retries} running #{agent_class} input: #{agent['action_input']}"
|
132
104
|
response = agent_class.new.call(agent['action_input'])
|
133
105
|
@tools_response << {name: agent['action'], response: response}
|
134
106
|
end
|
135
|
-
messages = _messages_ + [
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
def load_system_prompt_default_template
|
141
|
-
PromptTemplate.load_template('agent_system.md.erb')
|
107
|
+
messages = _messages_ + [
|
108
|
+
{ role: "assistant", content: agents.to_json },
|
109
|
+
{ role: "user", content: templates.tool.result(binding) }
|
110
|
+
]
|
111
|
+
request messages, run_agent_retries: run_agent_retries
|
142
112
|
end
|
143
113
|
|
144
|
-
def load_user_input_prompt_default_template
|
145
|
-
PromptTemplate.load_template('agent_input.md.erb')
|
146
|
-
end
|
147
|
-
|
148
|
-
def load_tools_response_prompt_default
|
149
|
-
PromptTemplate.load_template('agent_tool_input.md.erb')
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
class AssertionError < StandardError
|
154
114
|
end
|
155
|
-
end
|
115
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Luogu
|
4
|
+
module Application
|
5
|
+
extend Dry::Configurable
|
6
|
+
|
7
|
+
setting :openai do
|
8
|
+
setting :access_token, default: ENV.fetch('OPENAI_ACCESS_TOKEN')
|
9
|
+
setting :retries, default: ENV.fetch('OPENAI_REQUEST_RETRIES', 3).to_i
|
10
|
+
setting :host, default: ENV.fetch('OPENAI_HOST', 'https://api.openai.com')
|
11
|
+
setting :history_limit, default: ENV.fetch('OPENAI_LIMIT_HISTORY', '6').to_i * 2
|
12
|
+
setting :temperature, default: ENV.fetch('OPENAI_TEMPERATURE', 1).to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
setting :run_agent_retries, default: ENV.fetch('RUN_AGENT_RETRIES', 5).to_i
|
16
|
+
|
17
|
+
setting :logger, reader: true,
|
18
|
+
default: ENV.fetch('LOG_LEVEL', Logger::INFO),
|
19
|
+
constructor: proc { |value|
|
20
|
+
logger = Logger.new(STDOUT)
|
21
|
+
logger.level = value
|
22
|
+
logger
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
data/lib/luogu/base.rb
ADDED
@@ -1,64 +1,76 @@
|
|
1
1
|
module Luogu
|
2
|
-
class
|
2
|
+
class ChatLLM < Base
|
3
|
+
|
4
|
+
setting :provider do
|
5
|
+
setting :parameter_model, default: ->() {
|
6
|
+
OpenAI::ChatRequestParams.new(
|
7
|
+
model: 'gpt-3.5-turbo',
|
8
|
+
temperature: Application.config.openai.temperature
|
9
|
+
)
|
10
|
+
}
|
11
|
+
setting :request, default: ->(params) { OpenAI.chat(params: params) }
|
12
|
+
setting :parse, default: ->(response) { OpenAI.get_content(response) }
|
13
|
+
setting :find_final_answer, default: ->(content) { OpenAI.find_final_answer(content) }
|
14
|
+
setting :history_limit, default: Application.config.openai.history_limit
|
15
|
+
end
|
3
16
|
|
4
|
-
attr_accessor :
|
17
|
+
attr_accessor :context
|
18
|
+
attr_reader :plugin
|
5
19
|
|
6
20
|
def initialize(file, history_path='.', plugin_file_path=nil)
|
7
21
|
@plugin_file_path = plugin_file_path || file.sub(File.extname(file), ".plugin.rb")
|
8
|
-
|
9
22
|
if File.exist?(@plugin_file_path)
|
10
23
|
@plugin = Plugin.new(@plugin_file_path).load()
|
11
24
|
else
|
12
25
|
@plugin = Plugin.new(@plugin_file_path)
|
13
26
|
end
|
14
|
-
|
15
|
-
@temperature = ENV.fetch('OPENAI_TEMPERATURE', '0.7').to_f
|
16
|
-
@limit_history = ENV.fetch('OPENAI_LIMIT_HISTORY', '6').to_i * 2
|
17
|
-
@model_name = "gpt-3.5-turbo"
|
18
|
-
|
19
27
|
@history_path = history_path
|
20
28
|
@prompt_file = file
|
21
|
-
|
22
29
|
@prompt = PromptParser.new(file)
|
23
30
|
@row_history = []
|
24
|
-
@
|
31
|
+
@histories = HistoryQueue.new provider.history_limit
|
32
|
+
|
33
|
+
@request_params = provider.parameter_model.call
|
25
34
|
|
26
35
|
@context = OpenStruct.new
|
27
36
|
|
28
|
-
|
29
|
-
|
30
|
-
|
37
|
+
run_plugin :setup
|
38
|
+
end
|
39
|
+
|
40
|
+
def run_plugin(method_name, &block)
|
41
|
+
plugin.run method_name: method_name, llm: self, context: @context, &block
|
42
|
+
end
|
43
|
+
|
44
|
+
def provider
|
45
|
+
config.provider
|
31
46
|
end
|
32
47
|
|
33
48
|
def request(messages)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@context.request_params = params
|
42
|
-
params = @plugin.before_request_proc.call(self, @context).request_params
|
49
|
+
@request_params.messages = messages
|
50
|
+
@context.request_params = @request_params
|
51
|
+
run_plugin :before_request
|
52
|
+
response = provider.request.call(@request_params.to_h)
|
53
|
+
unless response.code == 200
|
54
|
+
logger.error response.body.to_s
|
55
|
+
raise RequestError
|
43
56
|
end
|
44
|
-
response = client.chat(parameters: params).parse
|
45
57
|
@context.response = response
|
46
|
-
|
47
|
-
|
58
|
+
run_plugin :after_request
|
59
|
+
|
60
|
+
provider.parse.call(response)
|
48
61
|
end
|
49
62
|
|
50
63
|
def chat(user_message)
|
51
|
-
|
52
|
-
|
53
|
-
user_message =
|
54
|
-
end
|
55
|
-
messages = (@prompt.render + @history.to_a) << {role: "user", content: user_message}
|
56
|
-
if @plugin.after_input_proc
|
57
|
-
@context.request_messages = messages
|
58
|
-
messages = @plugin.after_input_proc.call(self, @context).request_messages
|
64
|
+
@context.user_input = user_message
|
65
|
+
run_plugin :before_input do |context|
|
66
|
+
user_message = context.user_input
|
59
67
|
end
|
60
68
|
|
61
|
-
|
69
|
+
messages = (@prompt.render + @histories.to_a) << { role: "user", content: user_message}
|
70
|
+
run_plugin :after_input do
|
71
|
+
messages = @context.request_messages
|
72
|
+
end
|
73
|
+
assistant_message = request(messages)
|
62
74
|
|
63
75
|
self.push_row_history(user_message, assistant_message)
|
64
76
|
|
@@ -68,12 +80,15 @@ module Luogu
|
|
68
80
|
elsif @plugin.before_save_history_proc
|
69
81
|
@context.user_input = user_message
|
70
82
|
@context.response_message = assistant_message
|
71
|
-
|
83
|
+
|
84
|
+
run_plugin :before_save_history
|
72
85
|
else
|
73
86
|
puts "执行默认的历史记录"
|
74
87
|
self.push_history(user_message, assistant_message)
|
75
88
|
end
|
76
89
|
|
90
|
+
run_plugin :after_save_history
|
91
|
+
|
77
92
|
assistant_message
|
78
93
|
end
|
79
94
|
|
@@ -83,8 +98,8 @@ module Luogu
|
|
83
98
|
end
|
84
99
|
|
85
100
|
def push_history(user_message, assistant_message)
|
86
|
-
@
|
87
|
-
@
|
101
|
+
@histories.enqueue({ role: "user", content: user_message})
|
102
|
+
@histories.enqueue({ role: "assistant", content: assistant_message})
|
88
103
|
if @plugin.after_save_history_proc
|
89
104
|
@context.user_input = user_message
|
90
105
|
@context.response_message = response_message
|
@@ -118,11 +133,11 @@ module Luogu
|
|
118
133
|
when "save"
|
119
134
|
file_name = File.basename(@prompt_file, ".*")
|
120
135
|
self.class.save @row_history, File.join(@history_path, "#{file_name}.row_history.md")
|
121
|
-
self.class.save @
|
136
|
+
self.class.save @histories.to_a, File.join(@history_path, "#{file_name}.history.md")
|
122
137
|
when "row history"
|
123
138
|
p @row_history
|
124
139
|
when "history"
|
125
|
-
p @
|
140
|
+
p @histories.to_a
|
126
141
|
when "exit"
|
127
142
|
puts "再见!"
|
128
143
|
break
|
@@ -147,7 +162,7 @@ module Luogu
|
|
147
162
|
file_name = File.basename(@prompt_file, ".*")
|
148
163
|
|
149
164
|
self.class.save @row_history, File.join(@history_path, "#{file_name}-#{now}.row_history.md")
|
150
|
-
self.class.save @
|
165
|
+
self.class.save @histories.to_a, File.join(@history_path, "#{file_name}-#{now}.history.md")
|
151
166
|
end
|
152
167
|
|
153
168
|
class << self
|
data/lib/luogu/cli.rb
CHANGED
@@ -35,7 +35,7 @@ module Luogu
|
|
35
35
|
option :plugin, type: :string, desc: "运行的时候载入对应的插件"
|
36
36
|
|
37
37
|
def call(prompt_file: nil, **options)
|
38
|
-
chatgpt =
|
38
|
+
chatgpt = ChatLLM.new(prompt_file, options.fetch(:out), options.fetch(:plugin, nil))
|
39
39
|
chatgpt.run
|
40
40
|
end
|
41
41
|
|
@@ -51,7 +51,7 @@ module Luogu
|
|
51
51
|
json = JSON.parse(File.read(json_file), symbolize_names: true)
|
52
52
|
prompt_file ||= json_file.sub(File.extname(json_file), ".md")
|
53
53
|
|
54
|
-
chatgpt =
|
54
|
+
chatgpt = ChatLLM.save(json, prompt_file)
|
55
55
|
end
|
56
56
|
|
57
57
|
end
|
@@ -67,7 +67,7 @@ module Luogu
|
|
67
67
|
def call(prompt_file: nil, test_file:nil, **options)
|
68
68
|
test_file ||= prompt_file.sub(File.extname(prompt_file), ".test.yml")
|
69
69
|
|
70
|
-
chatgpt =
|
70
|
+
chatgpt = ChatLLM.new(prompt_file, options.fetch(:out), options.fetch(:plugin, nil))
|
71
71
|
messages = YAML.load_file(test_file)
|
72
72
|
chatgpt.playload messages
|
73
73
|
end
|
@@ -83,7 +83,7 @@ module Luogu
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
register "version", Version, aliases: [
|
86
|
+
register "version", Version, aliases: %w[v -v --version]
|
87
87
|
register "build", Build, aliases: ["b"]
|
88
88
|
register "run", Run, aliases: ["r"]
|
89
89
|
register "generate", Generate, aliases: ["g"]
|
data/lib/luogu/error.rb
ADDED
data/lib/luogu/init.rb
CHANGED
@@ -3,40 +3,31 @@ require 'dotenv/load'
|
|
3
3
|
require "tty-prompt"
|
4
4
|
require 'json'
|
5
5
|
require 'yaml'
|
6
|
-
require "dry/cli"
|
7
6
|
require 'fileutils'
|
8
7
|
require 'ostruct'
|
9
8
|
require 'benchmark'
|
10
9
|
require 'erb'
|
11
10
|
require 'logger'
|
12
11
|
|
12
|
+
require "dry/cli"
|
13
|
+
require 'dry-configurable'
|
14
|
+
|
15
|
+
require_relative 'base'
|
16
|
+
require_relative 'error'
|
17
|
+
require_relative 'application'
|
18
|
+
|
13
19
|
require_relative "prompt_template"
|
14
|
-
require_relative 'openai'
|
15
20
|
require_relative 'plugin'
|
16
21
|
require_relative 'history_queue'
|
17
22
|
require_relative "prompt_parser"
|
18
|
-
require_relative "
|
23
|
+
require_relative "chatllm"
|
19
24
|
require_relative "cli"
|
20
25
|
|
21
26
|
require_relative "agent"
|
22
|
-
require_relative "session"
|
23
27
|
require_relative "agent_runner"
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
def client
|
28
|
-
$client ||= Luogu::OpenAI::Client.new
|
29
|
-
end
|
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
|
29
|
+
require_relative 'openai'
|
30
|
+
require_relative 'terminal'
|
40
31
|
|
41
32
|
class String
|
42
33
|
def cover_chinese
|
data/lib/luogu/openai.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
|
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 || {}).values_at(*self.class.members))
|
10
|
-
end
|
1
|
+
# frozen_string_literal: true
|
11
2
|
|
3
|
+
module Luogu::OpenAI
|
4
|
+
class ChatRequestParams < Struct.new(:model, :messages, :temperature,
|
5
|
+
:top_p, :n, :stream, :stop,
|
6
|
+
:max_tokens, :presence_penalty,
|
7
|
+
:frequency_penalty, :logit_bias, :user)
|
12
8
|
def to_h
|
13
9
|
super.reject { |_, v| v.nil? }
|
14
10
|
end
|
@@ -16,31 +12,67 @@ module Luogu::OpenAI
|
|
16
12
|
alias to_hash to_h
|
17
13
|
end
|
18
14
|
|
19
|
-
|
15
|
+
def chat(parameters: nil, params: nil, retries: nil)
|
16
|
+
params ||= parameters
|
17
|
+
retries_left = retries || Luogu::Application.config.openai.retries
|
18
|
+
begin
|
19
|
+
client.post('/v1/chat/completions', json: params)
|
20
|
+
rescue HTTP::Error => e
|
21
|
+
if retries_left > 0
|
22
|
+
puts "retrying ..."
|
23
|
+
retries_left -= 1
|
24
|
+
sleep(1)
|
25
|
+
retry
|
26
|
+
else
|
27
|
+
puts "Connection error #{e}"
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def client
|
34
|
+
@client ||= HTTP.auth("Bearer #{Luogu::Application.config.openai.access_token}")
|
35
|
+
.persistent Luogu::Application.config.openai.host
|
20
36
|
end
|
21
37
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
38
|
+
def parse_json(markdown)
|
39
|
+
json_regex = /```json(.+?)```/im
|
40
|
+
json_blocks = markdown.scan(json_regex)
|
41
|
+
result_json = nil
|
42
|
+
json_blocks.each do |json_block|
|
43
|
+
json_string = json_block[0]
|
44
|
+
result_json = JSON.parse(json_string)
|
26
45
|
end
|
27
46
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
47
|
+
if result_json.nil?
|
48
|
+
JSON.parse markdown
|
49
|
+
else
|
50
|
+
result_json
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def chat_response_handle(response)
|
55
|
+
parse_json get_content(response)
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_content(response)
|
59
|
+
response.parse.dig("choices", 0, "message", "content")
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_final_answer(content)
|
63
|
+
if content.is_a?(Hash) && content['action'] == 'Final Answer'
|
64
|
+
content['action_input']
|
65
|
+
elsif content.is_a?(Array)
|
66
|
+
result = content.find { |element| element["action"] == "Final Answer" }
|
67
|
+
if result
|
68
|
+
result["action_input"]
|
69
|
+
else
|
70
|
+
nil
|
43
71
|
end
|
72
|
+
else
|
73
|
+
nil
|
44
74
|
end
|
45
75
|
end
|
76
|
+
|
77
|
+
module_function :chat, :client, :parse_json, :chat_response_handle, :find_final_answer, :get_content
|
46
78
|
end
|
data/lib/luogu/plugin.rb
CHANGED
@@ -1,52 +1,40 @@
|
|
1
1
|
module Luogu
|
2
2
|
class Plugin
|
3
|
-
attr_reader :before_input_proc, :before_save_history_proc, :after_input_proc,
|
4
|
-
|
3
|
+
attr_reader :plugin_file_path, :before_input_proc, :before_save_history_proc, :after_input_proc,
|
4
|
+
:after_save_history_proc, :setup_proc, :before_request_proc, :after_request_proc
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@after_input_proc = nil
|
13
|
-
@after_save_history_proc = nil
|
14
|
-
|
15
|
-
@setup_proc = nil
|
16
|
-
end
|
17
|
-
|
18
|
-
def before_input(&block)
|
19
|
-
@before_input_proc = block
|
20
|
-
end
|
21
|
-
|
22
|
-
def before_save_history(&block)
|
23
|
-
@before_save_history_proc = block
|
24
|
-
end
|
25
|
-
|
26
|
-
def after_input(&block)
|
27
|
-
@after_input_proc = block
|
28
|
-
end
|
29
|
-
|
30
|
-
def after_save_history(&block)
|
31
|
-
@after_save_history_proc = block
|
32
|
-
end
|
33
|
-
|
34
|
-
def setup(&block)
|
35
|
-
@setup_proc = block
|
6
|
+
# 定义一个元编程方法来动态定义属性设置方法
|
7
|
+
def self.define_attr_setter(attr_name)
|
8
|
+
define_method("#{attr_name}") do |&block|
|
9
|
+
instance_variable_set("@#{attr_name}_proc", block)
|
10
|
+
end
|
36
11
|
end
|
37
12
|
|
38
|
-
def
|
39
|
-
@
|
13
|
+
def initialize(plugin_file_path)
|
14
|
+
@plugin_file_path = plugin_file_path
|
40
15
|
end
|
41
16
|
|
42
|
-
|
43
|
-
|
44
|
-
|
17
|
+
# 使用元编程方法来动态定义属性设置方法
|
18
|
+
define_attr_setter :before_input
|
19
|
+
define_attr_setter :before_save_history
|
20
|
+
define_attr_setter :after_input
|
21
|
+
define_attr_setter :after_save_history
|
22
|
+
define_attr_setter :setup
|
23
|
+
define_attr_setter :before_request
|
24
|
+
define_attr_setter :after_request
|
45
25
|
|
46
26
|
def load()
|
47
|
-
self.instance_eval File.read(
|
27
|
+
self.instance_eval File.read(plugin_file_path)
|
48
28
|
self
|
49
29
|
end
|
50
|
-
|
30
|
+
|
31
|
+
def run(method_name: nil, llm: nil, context: nil, &block)
|
32
|
+
method_name = "#{method_name}_proc".to_sym
|
33
|
+
if send(method_name).respond_to?(:call)
|
34
|
+
rt = send(method_name).call(llm, context)
|
35
|
+
block.call(rt) unless block.nil?
|
36
|
+
rt
|
37
|
+
end
|
38
|
+
end
|
51
39
|
end
|
52
|
-
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Luogu
|
4
|
+
class Terminal < Base
|
5
|
+
def initialize(desc: "请输入你的指令>")
|
6
|
+
@desc = desc
|
7
|
+
@default_action = nil
|
8
|
+
@actions = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def action(action_name, &block)
|
12
|
+
@actions << { action_name: action_name, action_method: block }
|
13
|
+
end
|
14
|
+
|
15
|
+
def default(&block)
|
16
|
+
@default_action = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
loop do
|
21
|
+
# 从命令行读取输入
|
22
|
+
input = ask(@desc).cover_chinese
|
23
|
+
|
24
|
+
if input == 'exit'
|
25
|
+
break
|
26
|
+
end
|
27
|
+
|
28
|
+
if (action = @actions.find { |h| h[:action_name].to_s == input })
|
29
|
+
action.fetch(:action_method)&.call(input)
|
30
|
+
else
|
31
|
+
@default_action&.call(input)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def ask(message)
|
37
|
+
TTY::Prompt.new.ask(message)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/luogu/version.rb
CHANGED
data/luogu.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'tty-prompt', '~> 0.23.1'
|
37
37
|
spec.add_dependency 'dry-cli', '~> 1.0'
|
38
38
|
spec.add_dependency 'http', '~> 5.1', '>= 5.1.1'
|
39
|
+
spec.add_dependency 'dry-configurable', '~> 1.0', '>= 1.0.1'
|
39
40
|
|
40
41
|
# For more information and examples about making a new gem, check out our
|
41
42
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/prompt.md
ADDED
data/prompt.plugin.rb
ADDED
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.
|
4
|
+
version: 0.2.0
|
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-
|
11
|
+
date: 2023-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dotenv
|
@@ -78,6 +78,26 @@ dependencies:
|
|
78
78
|
- - ">="
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: 5.1.1
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: dry-configurable
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '1.0'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 1.0.1
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.0'
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 1.0.1
|
81
101
|
description: 使用markdown来快速实现 Prompt工程研发
|
82
102
|
email:
|
83
103
|
- tywf91@gmail.com
|
@@ -88,27 +108,34 @@ extra_rdoc_files: []
|
|
88
108
|
files:
|
89
109
|
- Gemfile
|
90
110
|
- Gemfile.lock
|
111
|
+
- LICENSE
|
91
112
|
- README.md
|
92
113
|
- Rakefile
|
93
114
|
- exe/luogu
|
94
115
|
- lib/luogu.rb
|
95
116
|
- lib/luogu/agent.rb
|
96
117
|
- lib/luogu/agent_runner.rb
|
97
|
-
- lib/luogu/
|
118
|
+
- lib/luogu/application.rb
|
119
|
+
- lib/luogu/base.rb
|
120
|
+
- lib/luogu/chatllm.rb
|
98
121
|
- lib/luogu/cli.rb
|
122
|
+
- lib/luogu/error.rb
|
99
123
|
- lib/luogu/history_queue.rb
|
100
124
|
- lib/luogu/init.rb
|
101
125
|
- lib/luogu/openai.rb
|
102
126
|
- lib/luogu/plugin.rb
|
103
127
|
- lib/luogu/prompt_parser.rb
|
104
128
|
- lib/luogu/prompt_template.rb
|
105
|
-
- lib/luogu/session.rb
|
106
129
|
- lib/luogu/templates/agent_input.md.erb
|
107
130
|
- lib/luogu/templates/agent_system.md.erb
|
108
131
|
- lib/luogu/templates/agent_tool_input.md.erb
|
132
|
+
- lib/luogu/terminal.rb
|
109
133
|
- lib/luogu/version.rb
|
110
134
|
- luogu.gemspec
|
135
|
+
- prompt.md
|
136
|
+
- prompt.plugin.rb
|
111
137
|
- sig/luogu.rbs
|
138
|
+
- sig/luogu/client.rbs
|
112
139
|
homepage: https://github.com/mjason/luogu
|
113
140
|
licenses: []
|
114
141
|
metadata:
|
data/lib/luogu/session.rb
DELETED