我们把 Prompt 函数和类更进一步了

科技   其他   2024-04-09 16:31   浙江  

前言

上次我们发了一篇文章,很好的解决了 Prompt 的使用问题:

最好的Prompt管理和使用依然是 Class 和 Function - 继续让LLM和编程语言融合

相比其他方案,具有非常大的优势,完全融入到了现有的编程语言里,而不是大段的文本变量或者文件来做管理。

阅读本文前,建议大家先阅读上面的文章获得一个基础认知,再来看看我们如何进一步简化其使用。

问题

我们来看下面一段代码:

import rayimport functoolsimport inspectimport byzerllmimport pydanticfrom byzerllm.utils.client import ByzerLLM
ray.init(address="auto",namespace="default",ignore_reinit_error=True)
data = { 'name': 'Jane Doe', 'task_count': 3, 'tasks': [ {'name': 'Submit report', 'due_date': '2024-03-10'}, {'name': 'Finish project', 'due_date': '2024-03-15'}, {'name': 'Reply to emails', 'due_date': '2024-03-08'} ]}

class RAG(): def __init__(self): self.llm = ByzerLLM() self.llm.setup_template(model="sparkdesk_chat",template="auto") self.llm.setup_default_model_name("sparkdesk_chat") @byzerllm.prompt(lambda self:self.llm,render="jinja2") def generate_answer(self,name,task_count,tasks)->str: ''' Hello {{ name }},
This is a reminder that you have {{ task_count }} pending tasks: {% for task in tasks %} - Task: {{ task.name }} | Due: {{ task.due_date }} {% endfor %}
Best regards, Your Reminder System        '''
t = RAG()
response = t.generate_answer(**data)print(response)
## output:Hello! Is there anything else I can assist you with?


RAG 是一个 prompt class, 里面有个 prompt function generate_answer, 

可以看到这个方法是一个传统意义上的空方法,但是里面的 doc 则是一段jinja 模板代码。prompt function 的执行器实际上是大模型,所以这段doc 会作为代码给到大模型,然后返回一个结果。

但是这个prompt 函数有两个地方比较复杂:

  1. prompt 注解需要传一个 lambada 表达式,这个是为了获得 llm 实例。

  2. 我们用到了jinja2 的for循环等


实际用起来,我们往往还是希望能够对会绑定到 doc 里的参数做一些处理的,这样可以使得模板更简单一些。

解决方案

现在你可以这么做:

@byzerllm.prompt(lambda self:self.llm,render="jinja2")    def generate_answer(self,name,task_count,tasks)->str:        '''        Hello {{ name }},
This is a reminder that you have {{ task_count }} pending tasks: {{ tasks }}
Best regards, Your Reminder System ''' tasks_str = "\n".join([f"- Task: {task['name']} | Due: { task['due_date'] }" for task in tasks]) return {"tasks": tasks_str}

这次,我们提供了方法体,这个方法体实际上是对参数做一次预处理,然后再返回一个字典,这个新的字典会覆盖方法的参数的值,比如此时 tasks 值从原来的一个对象变成了一个字符串,可以直接放到 jinja2 doc 里渲染了。

此外,这也意味着你可以可能还可以做一些逻辑判断,来决定实际渲染到doc里的值是什么样子的,而不是在代码里做模板拼接。

这是第一个变化,prompt function 也允许有方法体,可以对入参做一些二次处理。只是你的方法体返回的值必须是一个字典。

其次,

@byzerllm.prompt(llm=lambda self:self.llm,render="jinja2")

中的 llm 目前可以接受三种值了:

  1.  lambda 表达式,获取当前对象的llm 实例。

  2. 字符串,也就是模型的名字,系统会自动构建一个llm实例用于某次调用。

  3. llm 实例, 你可以直接传递一个llm实例。


比如:

@byzerllm.prompt(llm="kimi_chat")

系统会自动寻找名字为 kimi_chat的模型。

此外,在这次新版本(0.1.58)我们将 render 默认值改成 jinja2 而非simple。

总结

Byzer-LLM 是一个比较特殊的存在,它的终极愿景是让大家更好的使用大模型,而实现这一愿景主要是通过让变成语言和大模型更好的做融合,为此我们提供了非常多的能力,包括大模型无关的function calling, response class 和 function impl 等功能,也包括 prompt function 和prompt class。 

为了能够让这些能力变得好用,Byzer-LLM 还支持主流的开源模型和SaaS模型的对接,并且能够很好的适应生产环境,可以让大家只引入一个库就解决主要问题。


祝威廉
架构/大数据/机器学习的心得和体会,也会因为爱情和生活写首诗
 最新文章