本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。
代码已经开源:🚀 fgpt 欢迎大家star⭐和fork 👏
在之前我们已经实现了一个简单的fgpt命令行工具,支持命令行的管道输入和交互式聊天界面(REPL)。 接下来我们实现反向代理,用Web API实现OpenAPI的功能。
Web API与OpenAPI的不同
Web API是面向Web的API,提供的API与OpenAPI不同,虽然协议内容上大同小异,但是还是需要做一个转换,这样确保原始的OpenAI的SDK也可以通过fgpt去调用。
OpenAPI的方式之前是需要申请一个OpenAPI,这个是需要非大陆的手机号和信用卡,并且收费的。
所以通过这个方式,大家可以免费的用OpenAPI实现学习和测试。
请不要使用在生产环境,如果用在商业环境可以用官方的OpenAPI,大陆可以用Azure的OpenAI的服务
架构设计
这次我们基于Axum这个库来实现Web API,这个库是基于tokio的异步框架,非常适合用来实现Web API。
只实现一个标准的POST接口,接收一个JSON数据,返回一个JSON数据,这样就可以实现OpenAPI的功能。 这个接口支持stream,也就是每次返回的是一个delta,这样可以打字机的效果。
实现
let app = Router::new()
.nest(
&state.prefix,
Router::new().route("/chat/completions", post(proxy_completions)),
)
.with_state(state.clone());
我们把大部分的逻辑写在proxy_completions, 这个函数需要处理SSE和和普通的JSON请求,这个函数的实现可以查看src/proxy.rs。
Axum 的设计是所有的返回只有能支持IntoResponse这个trait即可,所以我们根据请求是否带有stream参数进行分别处理
json的处理比较简单
// 将请求封装成JSON即可
let resp = Response::new(body.to_string());
let (mut parts, body) = resp.into_parts();
parts.status = axum::http::StatusCode::OK;
parts.headers.insert(
"content-type",
axum::http::HeaderValue::from_static("application/json"),
);
return Response::from_parts(parts, body.into());
sse的处理
SSE是需要启动一个sse::Event的stream(sse_stream), 然后再启动一个协程,去执行execute_plain获取结果,然后将结果发送到sse_stream即可:
let (tx, rx) = unbounded_channel::<Result<Event, Infallible>>();
let sse_stream = UnboundedReceiverStream::new(rx);
tokio::spawn(async move {
let req_stream = CompletionRequest::new(....);
while let Some(chunk) = req_stream.next().await {
match chunk {
....
let event = Event::default().data(chunk.into());
tx.send(Ok(event));
}
}
});
return Sse::new(sse_stream).into_response();
通过这个方式就可以实现将WebAPI的请求转发给OpenAPI,达到桥接的效果。
总结
fgpt 从构思到实现,写代码花了3天总共写了1100行代码,尽量复用成熟的库,减少重复造轮子的时间,这样可以更快的实现一个功能完整的工具。
一些经验:
- 尽量使用成熟的库,减少重复造轮子,比如axum, reqwest, serde_json, rustyline等库
- 尽量使用async/await的方式,这样可以更好的利用tokio的异步特性
- 尽量使用stream的方式,stream确实对代码的要求更高,需要花更多是时间去理解,但是一旦理解了,代码会更简洁,更高效
- 代码要发布到crates.io,这样可以方便别人使用,也可以方便自己在其他项目中使用
通过实现这个工具,对Rust的理解更加深入了,也对命令行工具的实现积攒了更多经验,能提升自己的编程水平。
我是一个Rust的爱好者,也是一个Rust的布道者,希望通过这个系列的文章,能让更多的人了解Rust,也能让更多的人喜欢Rust。
如果大家想通过提升项目能力,可以访问入职啦学习更多的编程实战。也可以添加我的微信进行交流:jinti2000
最后,欢迎大家使用fgpt,欢迎PR和Issue,也欢迎大家关注我的公众号:入职啦,一起学习和交流。