本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。
代码已经开源:🚀 fgpt 欢迎大家star⭐和fork 👏
先看看今天达到的效果: 具体效果可以点击查看 原图效果
接着上一篇文章,我们继续实现fgpt
命令行工具,之前已经实现了根据命令行参数返回ChatGPT的结果,这次我们要实现更多的功能,比如支持文件输入、交互式输入等。
Shell的管道输入
Unix/Linux 的Shell是非常强大的,它支持管道输入,比如下面的命令:
git diff | fgpt "Write a git commit bief with follow diff"
这个命令,相当于把git diff
的输出作为输入,传递给fgpt
,然后fgpt
返回一个git commit
的描述。
根据之前的代码实现,是支持多段消息输入的方式, 从程序的数据流程上规划了多段消息的组成:
- 第一部分: 作为系统提示, 在后续的
Code
模式上可以起作用,就是给ChatGPT
设计一个角色 - 第二部分: 作为用户输入, 主要是是命令行的输入, 也就是
Write a git commit bief ...
这段文本 - 第三部分: 作为文件或者管道输入, 也就是
git diff
的输出
对应就是要拼出一个这样的数据结构:
[
{
"role": "system",
"message": "Your are a developer", // 根据参数来确定
},
{
"role": "user",
"message": "Write a git commit bief with follow diff" // 从命令行输入
},
{
"role": "user",
"message": "......" // 从文件或者管道输入
}
]
Code模式的实现
在fgpt
中,我们可以通过-c
或者--code
参数来指定Code
模式,这个模式下,fgpt
返回的是一段代码,而不是一段文本。
为了实现这个效果,我们需要在会话的第一部分添加一个Prompt
, 具体可以见src/role.code.prompt
的内容,这个文件会通过include_str!
这个宏编译到代码中:
let mut messages = vec![];
if state.code {
messages.push(Message {
role: "system".to_string(),
content: include_str!("./role.code.prompt").to_string(),
content_type: "text".to_string(),
});
}
这样可以确保在Code
模式下,会话的第一部分是一个角色设定,加上用户的输入,就可以返回一段代码了:
mpi@mpis-Mac-mini fgpt % fgpt -c "Write python code to reverse a string"
def reverse_string(s):
return s[::-1]
# Example usage:
# original_string = "hello"
# reversed_string = reverse_string(original_string)
# print(reversed_string)
管道和文件输入
Linux的程序在运行过程中,要识别输入的来源,是来自于文件、管道还是终端,这个是通过stdin
来实现的,可以通过std::io::stdin().is_terminal()
来判断当前是管道还是终端
如果是管道输入,我们需要读取stdin
的内容作为第三部分的输入:
if !std::io::stdin().is_terminal() {
// it may be a pipe or a file
let mut content = String::new();
std::io::stdin().read_to_string(&mut content)?;
messages.push(Message {
role: "user".to_string(),
content,
content_type: "text".to_string(),
});
}
交互式输入
交互式最典型的就是Python的REPL
模式,用户可以输入一段代码,然后返回结果,这个模式在fgpt
中也是支持的,只需要不带任何参数就可以进入交互式模式。
要实现这个效果,那么就需要依赖一个readline
类似的库:rustyline
, 这个库可以非常方便的实现交互式输入,比如下面的代码:
let mut rl = rustyline::Editor::<()>::new();
loop {
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
if line.is_empty() {
continue;
}
rl.add_history_entry(line.as_str());
messages.push(Message {
role: "user".to_string(),
content: line,
content_type: "text".to_string(),
});
}
Err(_) => break,
}
}
实现对话效果
根据Web API的协议规范,在同个device_id
下,接最后一条消息的conversation_id
和message_id
就可以完成对话的效果, 只需要记录最后一条消息的conversation_id
和message_id
就可以了。
let mut conversation_id = None;
let mut message_id = None;
for message in messages.iter() {
conversation_id = Some(message.conversation_id.clone());
message_id = Some(message.message_id.clone());
}
// next request
let req = CompletionRequest {
conversation_id,
parent_message_id: message_id,
....
};
总结
当前这个版本已经能支持对话和交互式输入了,下一步我们要实现一个Reverse Proxy
的功能
但是还有很多优化空间,特别是配色的支持,还有要支持输出实时预览的,可以将输出的结果带颜色的输出到终端
我是一个写了20多年代码的老程序员,如果大家想学习编程,可以关注公众号:入职啦,或者加入下面的实战项目交流群,我会分享更多的编程实战经验。