用Rust实现免费调用ChatGPT的命令行工具 (三)

本文发表于入职啦(公众号: 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,也欢迎大家关注我的公众号:入职啦,一起学习和交流。

入群学习
  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值