本文是接着让AI给你写代码,初体验(三)- AI加上格式化对话,实现对单个文件的修改/保存
回顾一下当时实现的需求,对AI生成代码实现保存,然后再打开,修订后再保存。 那么能不能再往前进一步,真正实现输入文字需求,逐步引导AI小助手生成代码、然后执行代码,保存代码,并且在原代码基础上,根据新增输入提示需求,修改或者新增代码实现功能的逐步集成,进而构建一个较为完整的AI辅助编码的应用(仍然使用通义千问大模型)。
我们分为上下两部分分享,上半部分将应用代码的修改,下半部分,用一个例子实现用提示最后生成代码,包含应用数据获取及绘图,这里先说上半部分。
先回顾一下AI加格式化对话实现对单个文件的修改和保存的流程:
主人: 要求打开本地指定代码
小助手: 打开本地指定代码,并反显
主人:要求结合这段代码和新需求,增加一个功能
小助手: 将这段代码和新需求提交给AI,并反显AI给出的建议
主人: 要求修改
小助手:将修改要求提交给AI,并反显AI给出的建议
主人:要求合并代码
小助手:将合并代码的要求提交AI,并反显
主人: 要求保存代码
小助手:将AI给的代码提取保持
主人:退出对话
小助手:结束对话
现在这个流程基本上可以改进为(斜体部分)
主人: 要求打开本地指定代码
小助手: 打开本地指定代码,并反显
主人:要求结合这段代码和新需求,增加/修改一个功能
小助手: 将这段代码和新需求提交给AI,并给出新增功能后的代码
主人: 要求执行代码
小助手:执行代码并反显结果
主人:要求保存代码
小助手:将AI给的代码保存到指定文件
…
主人:要求退出对话
小助手:结束对话
(执行功能目前仅限于python)
然后用相近语意识别改进格式化正则匹配,使得小助手更加智能一些,例如输入“请打开文件XXXX”,““麻烦打开XXX文件” AI小助手都可以识别到**“打开文件”操作, 输入"请保存代码",“请保存文件”,都可识别为“保存文件”**操作… 此为其一,据此小助手要做的操作可按字典定义为
operation_set = {
"打开文件": assistant.openFile,
"保存文件": assistant.saveFile,
"执行代码": assistant.executeCode,
"结束对话": assistant.endTalk
}
直接匹配到要操作的函数
这里的关键是选择相似语意匹配的工具,需要注意的必须同时满足两个要求:
1) 这几个操作关键词之间要有足够的区分度
2 ) 与这几个操作关键词不想干的语句需要有足够的区分度
需要用到中文词汇预训练模型或者其他工具,我尝试了及种
- HuggingFace的bert-base-chinese预训模型
- nltk近义词函数nltk.edit_distance
- sentence-transformers的paraphrase-multilingual-MiniLM-L12-v2预训模型
其中,HuggingFace的bert-base-chinese预训模型下载和安装可以参考在Hugging Face上下载并使用Bert-base-Chinese
bert-base-chinese模型可以下载到本地,计算语义相似度的用法如下:
from transformers import BertModel, BertTokenizer
import numpy as np
import torch
class NPLUtil:
def __init__(self):
bert_path = "/home/cfets/AI/model/"
vocab_file = bert_path + "vocab.txt"
self.tokenizer = BertTokenizer(vocab_file)
model_path = bert_path + "bert-base-chinese/"
self.model = BertModel.from_pretrained(model_path)
def text_similarity(self, text1: str, text2: str):
# 对输入文本进行tokenize和编码
inputs = self.tokenizer([text1, text2], return_tensors='pt', padding=True, truncation=True, max_length=32)
# 将输入传递给BERT模型,并获取输出
with torch.no_grad():
outputs = self.model(**inputs)
embeddings = outputs.last_hidden_state[:, 0, :].cpu().numpy()
# embeddings = outputs.last_hidden_state
print(embeddings)
embedding1 = embeddings[0]
embedding2 = embeddings[1]
t_similarity = np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
return t_similarity
if __name__ == '__main__':
textCompare = NPLUtil()
sentence1 = "保存文件"
sentence2 = "打开文件"
similarity = textCompare.text_similarity(sentence1, sentence2)
print(f"相似度:{similarity:.4f}")
HuggingFace的bert-base-chinese的问题主要是,操作关键词之间的区分度不够,比如"保存文件"和““打开文件”相似度就是 0.9359,过于相近
nltk近义词函数nltk.edit_distance函数用法:
import nltk
# 加载中文词汇
chinese_vocab = ["打开文件", "保存文件", "保存代码", "执行代码", "结束对话"]
# 输入语意
# input_semantics = "麻烦保存代码"
input_semantics = "保存文件"
# 计算相似度
similarity_scores = {}
for word in chinese_vocab:
similarity_scores[word] = nltk.edit_distance(input_semantics, word)
print(similarity_scores[word])
# 找到最相似的词汇
most_similar_word = min(similarity_scores, key=similarity_scores.get)
print("与输入语意最相似的词汇是:", most_similar_word)
计算结果
2
0
2
4
4
与输入语意最相似的词汇是: 保存文件
基本满足要求
sentence-transformers的 paraphrase-multilingual-MiniLM-L12-v2模型,下载安装和初步应用可参考 [原创]python计算中文文本相似度神器
具体应用如下
mport torch
from sentence_transformers.util import cos_sim
from sentence_transformers import SentenceTransformer as SBert
sbert_path = "/home/cfets/AI/model/paraphrase-multilingual-MiniLM-L12-v2"
model = SBert(sbert_path)
sentences1 = ['打开文件',
'保存文件',
'执行代码',
'结束对话'
]
sentences2 = ['打开文件',
'保存文件',
'执行代码',
'结束对话'
]
# Compute embedding for both lists
embeddings1 = model.encode(sentences1)
embeddings2 = model.encode(sentences2)
# Compute cosine-similarits
cosine_scores = cos_sim(embeddings2, embeddings1)
print(cosine_scores)
res = torch.max(cosine_scores, dim=1, keepdim=False)
print(res.values.numpy().tolist()[0])
print(res.indices.numpy().tolist()[0])
index=res.indices.numpy().tolist()[0]
print(sentences1[index])
计算各操作项的自相似结果矩阵如下:
tensor([[1.0000, 0.5882, 0.4500, 0.3998],
[0.5882, 1.0000, 0.3156, 0.2404],
[0.4500, 0.3156, 1.0000, 0.3043],
[0.3998, 0.2404, 0.3043, 1.0000]])
拉的是比较开的,相对效果比较好 ,当然也不是没有问题,比如"麻烦保存代码",会识别成"执行代码" (nltk.edit_distance函数也会是这个结果),解决办法在操作集中增加1个操作项,“保存代码”和“保存文件”指向的操作一致
operation_set = {
“打开文件”: assistant.openFile,
“保存文件”: assistant.saveFile,
“保存代码”: assistant.saveFile,
“执行代码”: assistant.executeCode,
“结束对话”: assistant.endTalk
}
解决了语意相似性的问题,我们看看主要的代码修改,这里只标注修改部分,原先代码还是请移步让AI给你写代码,初体验(三)
主程序:
# AI对话
def conversation_mutual():
# 初始化小助手
assistant = Assistant()
# 新增: 操作匹配字典
operation_set = {
"打开文件": assistant.openFile,
"保存文件": assistant.saveFile,
"保存代码": assistant.saveFile,
"执行代码": assistant.executeCode,
"结束对话": assistant.endTalk
}
# 初始化工具
textUtil = NPLUtil()
while True:
message = input('master:')
op_reply = ""
assistant.setMessage(message) # 向小助手提交主人的消息
# 修改为匹配操作
ops = list(operation_set.keys()) # 获得操作列表
cn_msg = remove_non_chinese(message) #对输入信息进行预处理,仅保留中文主干
similarity, operation = textUtil.text_similarity(ops, cn_msg) #与操作匹配字典进行匹配,返回相似度和匹配到的操作
if similarity > 0.7: # 相似度控制 1相等 0不想干
# 执行操作
op_reply = operation_set[operation]() #匹配执行
print('小助手:', end='')
print(op_reply, end='')
if operation == "结束对话":
break
else:
assistant.conversation_ask() # 提交给AI
print('\n')
需要注意的是,
1 因为语意匹配对两个待比较的短语长度非常敏感,需要预处理,我们这里简化,去掉英文符号(主要是文件名,路径等),保留中文主干,函数remove_non_chinese如下
def remove_non_chinese(text):
pattern = re.compile(r'[^\u4e00-\u9fa5]') # 匹配非中文字符的正则表达式
chinese_text = re.sub(pattern, '', text) # 使用正则表达式替换非中文字符为空字符串
return chinese_text
2 匹配函数就是找一个最接近的操作,为预防误操作因此要控制匹配相似度(similarity),这里选0.7 大致上可以匹配 {‘运行代码’ : ‘执行代码’ } {‘麻烦执行上述代码’ : ‘执行代码’}等等… 如果全部操作匹配不上,就向外网 AI大模型提交
3 匹配函数的归一化处理 op_reply = operation_setoperation #匹配执行 取消参数,参数输入在之前有方法执行 assistant.setMessage(message) # 向小助手提交主人的消息
然后我们看一下 AI助手程序
class Assistant:
def __init__(self):
self.message = "" # 新增本轮主人输入对话
self.last_content = "" # 上一次主人输入的内容,比如打开的文件等等
self.last_AI_message = "" # 上一次AI反馈的内容
self.python_path ...
def setMessage(self, message): #新增向小助手提交主人的消息的方法
self.message = message
...
# 打开文件
def openFile(self):
content = ""
try:
# 按正则表达式,获得全路径文件
programmingName = filepathMatch(self.message) #从主人读入的信息获得文件名
...
# 保存文件
def saveFile(self):
replyMessages = self.last_AI_message # 保存AI生成的代码
...
return res # 增加反馈
# 保存文件原子操作
def saveCode(self, programmingName, content_list):
...
# 提交AI对话
def conversation_ask(self):
...
def executeCode(self): # 新增 执行代码(来自于AI生成的代码)
_, after = self.last_AI_message.split("```python")
realcode = after.split("```")[0]
try:
exec(realcode)
return "执行成功"
except Exception as e:
return "执行失败: %s" % e
def endTalk(message: str): # 包装了结束对话
return "好的,再见"
这里需要说明的是 filepathMatch函数用了正则表达式匹配文件去掉路径后的文件名
为满足匹配需要,至少需要输入 /file.xxx 而不是 file.xxx
全部代码修改如下,下一篇会给一个完整的案例说明如何用提示输入完成代码生成,修改,增加,执行等等…