hello大家好,上篇文章我们做完了所有准备工作,成功封装了一个api_call函数,同时初步了解了多轮对话的原理。今天我们继续完善昨天我说api_call函数其实差不多完成了,实则是我要去吃午饭了……漏了一个参数temperature,在LLM中,可解释为“输出稳定性”。temperature较高,大模型的输出就会更有“创造性”,适合文学创作等“百花齐放”的场景;越低大模型的输出会更稳定,在有正确资料的情况下还能减少幻觉,适合数学问题这种有标准答案的场景。加入方法很简单:多接收一个参数temperature,随后在创建response时多传个参就行了,小修小补,故快速讲完我们肯定不可能让对话在程序打开时就消失了,也不可能让程序只支持单对话,肯定要支持多个对话。对话的存储由于要持久,所以肯定要存在硬盘里。接下来就有几个选择了:好处:结构性极强,读取和存储Python中都有现有的库json,几乎不需要多花心思处理优点:用户读取,修改对话文件方便;文件大小较小;读取和存储也方便缺点:结构纯靠分隔符硬撑着,用户但凡把分割符删了改了一切完蛋,万一AI和用户在对话中不小心输出了分隔符对话文件直接损坏优点:非常成熟,功能和可拓展性极强;读取,修改对话文件较为方便除去最后那种,剩下json和纯文本。我选择纯文本来存储,以后如果不行了再改json也来得及,毕竟我这个“项目”最核心的卖点就是可以方便地修改对话啊数据的存读分两部分。我给它们命名为load_history_from_file和dump_history_to_file。这毕竟是一个简单的项目,这两个函数肯定不能堆在main.py里了,于是新建文件:history.py历史对话我打算存储在/chats/NAME.txt中。然后是导出历史对话的函数。这两个函数都非常简单,看我写的注释绝对能懂,直接略过然后要检验这两个函数是否工作正常也很简单,我们先自己在API端和简单deepseek聊几句,把聊的内容dump进文件里,再把文件里的东西load成字典,看看前后是否内容一样,没有报错就行了新开一个文件interaction.py和public_function.py,用于存储用户交互相关代码。第一个存储核心逻辑,第二个实现具体功能都是个小项目了,总不能还用input这种原始的东西吧?连个多行输入都没法支持的“古董”,于是经过我在github,pypi,gitee的一通乱逛之后,选了个prompt_toolkit试试:pip install prompt_toolkit
它的官方仓库在这里,大家可以去点个star。这个仓库是纯Python实现的,可以说是非常了不起了https://github.com/prompt-toolkit/python-prompt-toolkit
它的基础用法非常简单:
from prompt_toolkit import promptif __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer)
当然了,我的目标是希望它直接帮我把所有管理对话的工作全部做完,所以还需要创建一个PromptSession
from prompt_toolkit import PromptSession as Sessionsession = Session() user_input = session.prompt(">>> ",multiline=True)print(f"你输入了: {user_input}")
需要提示一下:这个库不支持IDLE,请使用终端
现在它支持多行输入了,但怎么……回车也变成换行了啊……
后来问了AI,查了官方文档才知道这个库把esc+enter给搞成了提交(就是原来的enter)……这个库除了通读过一遍README之外我对它毫无了解,有时间再给大家讲吧,先用AI帮我们把快捷键改掉
from prompt_toolkit import PromptSessionfrom prompt_toolkit.key_binding import KeyBindingsbindings = KeyBindings()# Enter:提交@bindings.add('enter')def _(event): event.current_buffer.validate_and_handle()# 绑定 Ctrl+Enter:换行@bindings.add('c-j')def _(event): event.current_buffer.insert_text('\n')session = PromptSession(key_bindings=bindings)user = session.prompt( "输入消息(Enter 发送,Ctrl+Enter 换行):> ", multiline=True # 这里必须保留)input=session.prompt# 还是习惯用原来的input
这个初始化真不容易啊。不管怎么样,终于可以开始处理我们的用户交互设计了
然后第一个需求:主菜单
写到这里又懵了,README里面不是说可以选择只接受数字吗?怎么搞啊?
翻文档中。。。
结果你得导入一个Validator和ValidationError,然后专门定义一个类,这个类必须实现函数来实现只接受数字的功能……
行,为了可拓展性和“用户体验”,忍了。要写就干脆写完善点
from public_function import *...class Validator(Validator): def __init__(self, valid_values=None, min_val=None, max_val=None, message="输入格式错误,请重新输入"): self.valid_values = valid_values # 有限列表,比如 ['True','False'] self.min_val = min_val # 范围最小值 self.max_val = max_val # 范围最大值 self.message = message def validate(self, document): text = document.text if not (self.valid_values is None): if text not in self.valid_values: raise ValidationError(message=self.message) # 数值校验 if self.min_val is not None or self.max_val is not None: try: value = float(text) except ValueError: raise ValidationError(message=self.message + "\n期望一个数值") # 只有min if self.min_val is not None and self.max_val is None: if value < self.min_val: raise ValidationError(message=self.message) return # 只有max if self.min_val is None and self.max_val is not None: if value > self.max_val: raise ValidationError(message=self.message) return # 范围 if not (self.min_val <= value <= self.max_val): raise ValidationError(message=self.message)print('''1,开启新对话2,从文件加载对话3,设置全局配置''')while True: choice=int(input("请输入操作编号:",validator=_Validator(condition=['1','2','3']))) match choice: case 3: set_global_settings() print("操作执行成功!") continue
主菜单搞完了,接着干。今天三个功能是绝对实现不完了,先搞定第三个,剩下的第一个能搞多少搞多少。
我打算将配置都存在config文件夹里,全局配置命名为globals.cfg,其余对话的配置为对话名称.cfg,先创建config文件夹
然后转到public_function,定义set_global_settings。
然后我们发现,set_global_settings,还tm得用_Validator。然后一观察,这两段代码里的重复代码还有这么多:
唉,烦死了。再把这个类和这些代码都单独抽象出来吧
最后决定,把session实例,和快捷键绑定抽象到session.py里,然后validator.py里装着Validator类,最后public_function导入validator.py和session库,input在public_function.py里单独赋值,显示标明重写内置变量。为了防止实例和文件撞名导致后续麻烦,我们需要把原来的session实例改个名,我这里改的是“input_session"
成品如下:
好了,重新回到public_function里面
这时要分两种情况,一种就是已经有了配置文件,用户可能希望进行修改,另一种就是完全没有配置文件,需要用户从头编写,先写没有配置文件的情况
然后再写有配置文件的情况,这时候只需要引导用户修改希望修改的配置即可,代码如下:
最后,再加一个引导用户打开配置文件检查,今天就收工
今天这个写了真的好久好久。明天开始做用户与AI的对话,另外我发现我们这个set_global_settings其实非常全了功能,完全可以改成set_settings(filename),来在以后每个对话需要单独设置配置时一起复用,明天也顺便改掉,今天就算超常完成任务了吧
好了,那么今天文章就到这里,明天再见