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();}}@Overridepublic 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;}@Overridepublic boolean hasNext() {return this.remaining > 0;}@Overridepublic 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;}@Overridepublic 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聊天机器人服务的设计与实现》https://blog.csdn.net/camelials/article/details/130064321