前言

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

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

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

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

问题

我们来看下面一段代码:

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 函数有两个地方比较复杂:

  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模型的对接,并且能够很好的适应生产环境,可以让大家只引入一个库就解决主要问题。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐