1. 背景

最近在基于ChatGPT接口写一个聊天机器人,由于官方API未提供直接进行会话上下文关联的方法,因此只能把用户近期的会话信息一股脑的传给过去,并且策略定为:用户最近5分钟的中最多10条对话。为了实现这个要求,考虑用一个环形List去存储用户近期对话信息,由于嫌弃github上看到的写的太复杂,也不想花时间到处找了索性直接让chatGPT帮我去写。通过几次来回的与ChatGPT的聊天,最终的代码如下,由于觉得ChatGPT写的挺好,因此贴出来给大家分享一下。
另外,之所以需要几次的来回的聊天是因为:
1、chatGPT写的代码不一定对(可能会有Bug),需要自己Review、测试后再让ChatGPT自己去改进或者修改。
2、需要不断的给它提需求,比如,这个线程安全的环形List,首先写的不保障线程安全,然后不支持使用Iterable循环迭代。

ConcurrentCircularList最终代码如下:

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrentCircularList<T> implements Iterable<T> {

    private Object[] elements;
    private int size;
    private int headIndex;
    private int tailIndex;
    private Lock lock = new ReentrantLock();

    public ConcurrentCircularList(int capacity) {
        if (capacity < 1) {
            throw new IllegalArgumentException("Capacity must be at least 1");
        }

        this.elements = new Object[capacity];
        this.size = 0;
        this.headIndex = 0;
        this.tailIndex = 0;
    }

    /**
     * add
     *
     * @param element
     */
    public void add(T element) {
        this.lock.lock();
        try {
            this.elements[this.tailIndex] = element;
            if (this.size == this.elements.length) {
                this.headIndex = (this.headIndex + 1) % this.elements.length;
            } else {
                this.size++;
            }

            this.tailIndex = (this.tailIndex + 1) % this.elements.length;
        } finally {
            this.lock.unlock();
        }
    }

    /**
     * get
     *
     * @param index
     * @return
     */
    public T get(int index) {
        this.lock.lock();
        try {
            if (index < 0 || index >= this.size) {
                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds");
            }

            int i = (this.headIndex + index) % this.elements.length;
            return (T) this.elements[i];
        } finally {
            this.lock.unlock();
        }
    }

    /**
     * size
     *
     * @return
     */
    public int size() {
        this.lock.lock();
        try {
            return this.size;
        } finally {
            this.lock.unlock();
        }
    }

    /**
     * isEmpty
     *
     * @return
     */
    public boolean isEmpty() {
        this.lock.lock();
        try {
            return this.size == 0;
        } finally {
            this.lock.unlock();
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new CircularListIterator();
    }

    private class CircularListIterator implements Iterator<T> {

        private int current;
        private boolean removable;
        private int remaining;

        public CircularListIterator() {
            this.current = ConcurrentCircularList.this.headIndex;
            this.removable = false;
            this.remaining = ConcurrentCircularList.this.size;
        }

        @Override
        public boolean hasNext() {
            return this.remaining > 0;
        }

        @Override
        public T next() {
            if (!this.hasNext()) {
                throw new NoSuchElementException();
            }

            T element = (T) ConcurrentCircularList.this.elements[this.current];
            this.removable = true;
            this.current = (this.current + 1) % ConcurrentCircularList.this.elements.length;
            this.remaining--;

            return element;
        }

        @Override
        public void remove() {
            if (!this.removable) {
                throw new IllegalStateException();
            }

            int deleteIndex = (this.current - 1 + ConcurrentCircularList.this.elements.length) % ConcurrentCircularList.this.elements.length;
            this.current = (this.current - 1 + ConcurrentCircularList.this.elements.length) % ConcurrentCircularList.this.elements.length;
            ConcurrentCircularList.this.elements[deleteIndex] = null;
            ConcurrentCircularList.this.headIndex = this.current;
            ConcurrentCircularList.this.size--;
            this.remaining--;
            this.removable = false;
        }
    }

    public static void main(String[] args) {
        ConcurrentCircularList<String> list = new ConcurrentCircularList<>(10);
        for (int i = 0; i < 20; i++) {
            list.add("item" + i);
        }

        System.out.println("list.size() = " + list.size);
        for (String item : list) {
            System.out.println(item);
        }
    }
}

2. 聊天记录简述

2.1 首次交流

2.2 有Bug让其自己修改

3. 聊天机器人设计要点

  • 由于关联会话需要根据用户近期对话信息,而用户近期对话属于高读高写数据,且对数据高可用性不高(聊着聊着后面新的会话信息就会把老的覆盖),因此考虑使用本地缓存。之前在《本地缓存代码实例及常见缓存淘汰策略简介》博文中对常用的本地缓存相关进行过介绍,从介绍中可以看出,使用W-TinyLFU淘汰策略的Caffeine比较合适,因此本地缓存可以直接选型为它。
  • 考虑实现一个猴版的Akka邮箱机制,目的为:1)解耦上游消息服务 -> 聊天机器人服务 -> OpenAI;2)为将来可能需要实现的双向流控(上游消息服务 -> 聊天机器人服务流控;聊天机器人服务 -> OpenAI流控)留一个统一处理的口子。3)由于使用了本地缓存,那么就一定需要保障RPC路由落点的一致性,在这个邮箱机制下可以实现本节点RPC路由不走网络(虽然AKKA本身并未实现这个能力,不过大家可以参考我之前的博客:《【JAVA版Akka】一个使用JAVA基于Actor模型现实的RPC》,我的实现中具备了同节点RPC不走网络的能力)。

4. 总结

个人认为ChatGPT目前还不具备替代程序猿的能力,因为代码检查、提出完善意见、系统设计这些工作做还是需要人去完成,把ChatGPT当成工具还是其目前真正的定位。

更多推荐

一个用聊天的方式让ChatGPT写的线程安全的环形List