前言
上次我们发了一篇文章,很好的解决了 Prompt 的使用问题:
最好的Prompt管理和使用依然是 Class 和 Function - 继续让LLM和编程语言融合
相比其他方案,具有非常大的优势,完全融入到了现有的编程语言里,而不是大段的文本变量或者文件来做管理。
阅读本文前,建议大家先阅读上面的文章获得一个基础认知,再来看看我们如何进一步简化其使用。
问题
我们来看下面一段代码:
python
import ray
import functools
import inspect
import byzerllm
import pydantic
from 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 函数有两个地方比较复杂:
-
prompt 注解需要传一个 lambada 表达式,这个是为了获得 llm 实例。
-
我们用到了jinja2 的for循环等
实际用起来,我们往往还是希望能够对会绑定到 doc 里的参数做一些处理的,这样可以使得模板更简单一些。
解决方案
现在你可以这么做:
python
@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 目前可以接受三种值了:
-
lambda 表达式,获取当前对象的llm 实例。
-
字符串,也就是模型的名字,系统会自动构建一个llm实例用于某次调用。
-
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模型的对接,并且能够很好的适应生产环境,可以让大家只引入一个库就解决主要问题。