之前的内容,我们已经可以让AI逐步具备根据输入的需求,创建并完善代码,但有一个显而易见问题,例如:
我们可以要求AI给出代码获得 300001的最新价格的代码,但如果要求给出000001的最新价格就需要重新生成一次代码,且生成过程也存在一定的不稳定性,能不能利用现有的代码实际获取结果呢? 那就需要解决两个问题:
1 怎么引导AI调用本地资源
2 怎么完成参数化调用
解决问题1的基本方法,还是之前提到过语意匹配调用本地方法,解决问题2则设法采用正则匹配,例如将600XXX,300XXX直接转化为stock_code等标准化变量,基于上述思路,就可以考虑本地资源应该具备哪些特征:
1 代码需要以函数+入参的方式存在
2 代码需要包含函数说明,中文,包含摘要,入参,出参
具体解决问题的思路如下:
1 对已有代码,输入需求,由AI改造为函数方法(带函数说明,入参,出参)
2 对改造后的代码,注册为本地资源
3 对用户输入的语意,按正则表达式提取基本语义和参数
4 按语意匹配到本地函数,并注入参数
5 如果没有匹配到则提交到网络重新生成代码
为了方便,本地资源注册表暂时设计为json文件: 项目名称/config/method_config.json,包含本地资源路径(module),方法(method),函数方法描述(desc),入参(params)
{"获取本地方法":
[
{"module": "pythonRepository.get_latest_stock_price",
"method": "get_latest_stock_price",
"desc": "获取指定股票代码的最新股价。",
"params": ["stock_code: str"]
},
......
]
}
为避免冗长描述,代码只标识修改部分,其他部分请参考之前的blog文章
AI小助手的修改
主程序 testAIcode.py ,增加匹配本地资源部分
import ...
# 执行本地方法
def exec_local_func(module_name: str, func_name: str, **args):
module = __import__(module_name, fromlist=[''])
func = getattr(module, func_name)
return func(**args)
# 匹配入参
def match_param(params: list, message: str):
patterns = [{"stock_code": r"30\d{4}|60\d{4}|00\d{4}"},
{"report_date": r"\d{4}\d{2}\d{2}"}
]
args = {}
if len(params) > 0:
for param in params:
if param.startswith("stock_code"):
stock_code = match_pattern(message, patterns[0]["stock_code"])
args['stock_code']=stock_code
elif ...
return args
# AI对话
def conversation_mutual():
# 初始化小助手
assistant = Assistant()
# 初始化基本操作列表
operation_set = {
"打开文件": assistant.openFile,
"保存文件": assistant.saveFile,
"保存代码": assistant.saveFile,
"执行代码": assistant.executeCode,
"另存为本地资源": assistant.saveAsLocal, # 增加注册为本地工具
"结束对话": assistant.endTalk
}
# 初始化工具
textUtil = NPLUtil()
while True:
message = input('master:')
op_reply = ""
pytools = assistant.getConfig()["获取本地方法"] # 获取本地方法注册文件
...
cn_msg = remove_non_chinese(message) # 获得对话中中文主干部分
...
# 匹配操作
ops = list(operation_set.keys()) # 获得操作列表
...
if similarity > 0.8:
# 执行操作
...
else:
# 匹配本地module,本地方法
my_module = []
my_method = []
my_desc = []
my_params = []
if len(pytools) > 0:
for tool in pytools:
my_module.append(tool["module"])
my_method.append(tool["method"])
my_desc.append(tool["desc"])
my_params.append(tool["params"])
similarity, s_desc = textUtil.text_similarity(my_desc, cn_msg)
if similarity > 0.8:
# 执行操作
module_name = my_module[my_desc.index(s_desc)]
method_name = my_method[my_desc.index(s_desc)]
params = my_params[my_desc.index(s_desc)]
print(module_name, method_name, params)
args = match_param(params,message)
print(args)
# op_reply = exec_local_func(module_name, method_name, stock_code=code)
op_reply = exec_local_func(module_name, method_name, **args)
print('小助手:', end='')
print(op_reply, end='')
else:
assistant.conversation_ask() # 提交给AI
else:
assistant.conversation_ask() # 提交给AI
print('\n')
if __name__ == '__main__':
conversation_mutual()
主程序说明, 在操作列表里增加一个操作“另存为本地资源”,进行本地注册; 主程序增加一个匹配本地资源的判断,先对输入语句的中文主干进行匹配的本地资源函数方法描述(desc),如果相似性大于0.8 则匹配入参(match_param),并执行本地方法(exec_local_func)
修改assist.py,解决保存注册本地资源的问题
import...
class Assistant:
def __init__(self):
...
# 注册json文件
self.config_file = "/home/cfets/gitea/pyWebTest1/config/method_config.json"
# 本地资源
self.local_pythonRepository = "/home/cfets/gitea/pyWebTest1/pythonRepository/"
...
# 读取注册文件
def getConfig(self):
config = json.load(open(self.config_file))
return config
...
# 保存到本地资源
def saveAsLocal(self):
# 从content抽取函数名,函数说明
python_content = re.findall(r'```python(.*?)```', self.last_AI_message, re.DOTALL)
func_name = ""
args = []
if len(python_content) <= 0:
python_content =self.last_content
print(len(python_content))
func_name, args = extract_function_names(python_content) # 方法名和参数列表
if func_name == "":
res_content = "未找到函数名,保存失败"
else:
# 代码保存到本地仓库目录
with open(self.local_pythonRepository + func_name + ".py", 'w') as f:
f.write(python_content)
res_content = "保存成功"
# 注册到本地配置文件
desc = extract_description(python_content) # 函数描述
desc = desc.strip()
module_name = "pythonRepository." + func_name # 模块名
config = self.getConfig()
tools = config["获取本地方法"]
tools.append({"module": module_name, "method": func_name, "desc": desc, "params": args})
my_dict = {"获取本地方法": tools}
# 把tools写回config文件
with open(self.config_file, 'w') as f:
json.dump(my_dict, f, ensure_ascii=False)
return res_content
这部分基本比较清楚,需要说明程序利用extract_function_names方法会自动提取函数名和参数
# 匹配函数名称的正则表达式
def extract_function_names(code: str):
# print('code:',code)
pattern = r"def\s+(\w+)\s*\("
if re.search(pattern, code, re.DOTALL):
print(f'{code} 是一个有效的函数名称')
function_names = re.findall(pattern, code, re.DOTALL)[0]
arglist = code.split(function_names)[1].split(")")[0].split("(")[1]
# print('arglist:', arglist)
args = [arg.strip() for arg in arglist.split(",")]
print('args:', args)
else:
print(f'{code} 不是一个有效函数名称')
function_names = ""
agrs = []
return function_names, args
下面还是以“获取股票XXXXXX的最新股价” 为例,看看运行过程
AI 生成的原始代码/pyTest_2024-04-12_18:00:03_97.py
import akshare as ak
# 获取市场行情数据
stock_em_df = ak.stock_zh_a_spot_em()
# 按照指定的股票代码过滤行情数据
target_stock_df = stock_em_df[stock_em_df['代码'] == '300001']
# 提取并打印当前市场价格(最新价)
print(f"股票300001现在的市场价格是:{target_stock_df.iloc[0]['最新价']}元")
操作步骤
master: 请打开文件 /pyTest_2024-04-12_18:00:03_97.py
小助手:
import akshare as ak
# 获取市场行情数据
stock_em_df = ak.stock_zh_a_spot_em()
# 按照指定的股票代码过滤行情数据
target_stock_df = stock_em_df[stock_em_df['代码'] == '300001']
# 提取并打印当前市场价格(最新价)
print(f"股票300001现在的市场价格是:{target_stock_df.iloc[0]['最新价']}元")
master: 请结合源代码转换为函数,入参为股票代码,返回最新股价,并给出函数说明,使用中文
小助手:
fromAI:
```python
def get_latest_stock_price(stock_code: str) -> float:
"""
获取指定股票代码的最新股价。
参数:
stock_code (str): 股票代码,例如 "600029"
返回值:
float: 最新股价,单位为元
"""
# 获取所有A股实时行情数据
stock_zh_a_spot_em_df = ak.stock_zh_a_spot_em()
# 按股票代码过滤,获取对应行数据
target_stock_data = stock_zh_a_spot_em_df[stock_zh_a_spot_em_df["代码"] == stock_code]
# 返回最新价格(中文字段为"最新价")
return target_stock_data['最新价'].values[0]
# 示例使用
stock_code = "600029"
latest_price = get_latest_stock_price(stock_code)
print(f"{stock_code}股票的最新价格为:{latest_price}元")
'''
根据源代码重新生成了函数 get_latest_stock_price,入参是......
master:请另存为本地资源 #注意保存为本地资源前,请注释掉函数方法代码,给出的示例使用部分
小助手:保存成功
master:获取股票000977的最新股价
小助手:37.85
现在我们的AI小助手获得使用本地方法的能力,但还是比较笨拙,比如参数需要事先定义,再比如,语意匹配不是很丝滑, 比如“获取浪潮信息(000977)的最新股价”就不行了,还需要进一步打磨改进。