Skip to main content

LangChain装饰器 ✨ (LangChain Decorators ✨)

LangChain装饰器是在LangChain之上的一层,为编写自定义LangChain提示和链提供了语法糖🍭。

反馈、问题、贡献请在这里提出: ju-bezdek/langchain-decorators

主要原则和优势:

  • 更符合Python风格的编写代码方式
  • 编写多行提示,不会因缩进而破坏代码流程
  • 利用IDE内置的提示类型检查弹出文档的支持,快速查看函数的提示、参数等信息
  • 充分利用🦜🔗 LangChain生态系统的所有功能
  • 添加对可选参数的支持
  • 通过将参数绑定到一个类,轻松共享提示之间的参数

这是一个使用LangChain装饰器 ✨编写的简单示例代码:


@llm_prompt
def write_me_short_post(topic:str, platform:str="twitter", audience:str = "developers")->str:
"""
为{platform}平台上关于{topic}的帖子写一个简短的标题。
它应该面向{audience}受众。
(最多15个单词)
"""
return

# 自然运行
write_me_short_post(topic="starwars")
# 或者
write_me_short_post(topic="starwars", platform="redit")

快速入门

安装

pip install langchain_decorators

示例

开始的好方法是查看这里的示例:

定义其他参数

在这里,我们只是将函数标记为一个带有llm_prompt装饰器的提示,将其有效地转换为LLMChain。而不是运行它...

标准的LLMChain需要比仅输入变量和提示更多的初始化参数... 这个实现细节在装饰器中隐藏起来。 它的工作原理如下:

  1. 使用全局设置
# 为所有提示定义全局设置(如果未设置,则chatGPT是当前默认设置)
from langchain_decorators import GlobalSettings

GlobalSettings.define_settings(
default_llm=ChatOpenAI(temperature=0.0), 这是默认设置...可以在这里全局更改
default_streaming_llm=ChatOpenAI(temperature=0.0,streaming=True), 这是默认设置...可以在这里全局更改,将用于流式处理
)
  1. 使用预定义的提示类型
# 您可以更改默认的提示类型
from langchain_decorators import PromptTypes, PromptTypeSettings

PromptTypes.AGENT_REASONING.llm = ChatOpenAI()

# 或者您可以定义自己的提示类型:
class MyCustomPromptTypes(PromptTypes):
GPT4=PromptTypeSettings(llm=ChatOpenAI(model="gpt-4"))

@llm_prompt(prompt_type=MyCustomPromptTypes.GPT4)
def write_a_complicated_code(app_idea:str)->str:
...

  1. 在装饰器中直接定义设置
from langchain.llms import OpenAI

@llm_prompt(
llm=OpenAI(temperature=0.7),
stop_tokens=["\nObservation"],
...
)
def creative_writer(book_title:str)->str:
...

传递内存和/或回调函数:

要传递其中任何一个,只需在函数中声明它们(或使用kwargs传递任何内容)


@llm_prompt()
async def write_me_short_post(topic:str, platform:str="twitter", memory:SimpleMemory = None):
"""
{history_key}
为{platform}平台上关于{topic}的帖子写一个简短的标题。
它应该面向{audience}受众。
(最多15个单词)
"""
pass

await write_me_short_post(topic="old movies")

简化的流式处理

如果我们想利用流式处理:

  • 我们需要将提示定义为异步函数
  • 在装饰器上打开流式处理,或者我们可以定义带有流式处理的PromptType
  • 使用StreamingContext捕获流

这样,我们只需标记哪个提示应该进行流式处理,而不需要调整我们要使用的LLM,将创建和分发流处理程序传递到我们链的特定部分... 只需在提示/提示类型上打开/关闭流式处理...

只有在流式处理上下文中调用它时,流式处理才会发生... 在那里,我们可以定义一个简单的函数来处理流

# 这个代码示例是完整的,应该可以直接运行

from langchain_decorators import StreamingContext, llm_prompt

# 这将标记提示进行流式处理(如果我们只想在应用程序中流式处理一些提示,但不想传递分发的回调处理程序)
# 请注意,只有异步函数可以进行流式处理(如果不是异步函数,将会出错)
@llm_prompt(capture_stream=True)
async def write_me_short_post(topic:str, platform:str="twitter", audience:str = "developers"):
"""
为{platform}平台上关于{topic}的帖子写一个简短的标题。
它应该面向{audience}受众。
(最多15个单词)
"""
pass



# 只是一个任意的函数,用于演示流式处理... 在真实世界中,将是一些Websockets代码
tokens=[]
def capture_stream_func(new_token:str):
tokens.append(new_token)

# 如果我们想捕获流,我们需要将执行包装在StreamingContext中...
# 这将允许我们捕获流,即使提示调用在更高级别的方法中隐藏
# 只有标记为capture_stream的提示才会在这里被捕获
with StreamingContext(stream_to_stdout=True, callback=capture_stream_func):
result = await run_prompt()
print("流处理完成...我们可以通过交替的颜色区分标记")

print("\n我们捕获了",len(tokens),"个标记🎉\n")
print("这是结果:")
print(result)

提示声明

默认情况下,提示是整个函数文档,除非您标记了您的提示

记录您的提示

我们可以指定我们的文档的哪个部分是提示定义,通过使用带有<prompt>语言标签的代码块

@llm_prompt
def write_me_short_post(topic:str, platform:str="twitter", audience:str = "developers"):
"""
这是一种很好的方法,将提示作为函数文档的一部分编写,同时为开发人员提供额外的文档。

它需要是一个代码块,标记为`<prompt>`语言
```<prompt>
为{platform}平台上关于{topic}的帖子写一个简短的标题。
它应该面向{audience}受众。
(最多15个单词)
```

现在只有上面的代码块将被用作提示,其余的文档字符串将被用作开发人员的描述。
(它还有一个好处,即IDE(如VS code)将正确显示提示(不尝试将其解析为markdown,因此无法正确显示换行符))
"""
return

聊天消息提示

对于聊天模型来说,将提示定义为一组消息模板非常有用... 这是如何做到的:

@llm_prompt
def simulate_conversation(human_input:str, agent_role:str="a pirate"):
"""
## 系统消息
- 注意在<prompt:_role_>标签中的`:system`后缀


```<prompt:system>
你是一个{agent_role}黑客。你必须像一个黑客一样行动。
你只能用代码回复,使用Python或JavaScript代码块...
例如:

... 不要用其他任何东西回复... 只用代码 - 尊重你的角色。
```

# 人类消息
(我们使用LLM强制执行的真实角色,GPT支持system、assistant、user)
``` <prompt:user>
你好,你是谁
```
一个回复:


``` <prompt:assistant>
\``` python <<- 用\转义内部代码块,它应该是提示的一部分
def hello():
print("Argh... 你好,你这个讨厌的海盗")
\```
```

我们还可以使用占位符添加一些历史记录
```<prompt:placeholder>
{history}
```
```<prompt:user>
{human_input}
```

现在只有上面的代码块将被用作提示,其余的文档字符串将被用作开发人员的描述。
(它还有一个好处,即IDE(如VS code)将正确显示提示(不尝试将其解析为markdown,因此无法正确显示换行符))
"""
pass

这里的角色是模型的本机角色(assistant、user、system用于chatGPT)

可选部分

  • 您可以定义整个提示的部分,如果所有的{value}参数都为空(None | ""),则不会渲染整个部分

这个语法如下:

@llm_prompt
def prompt_with_optional_partials():
"""
这个文本总是会被渲染,但是

{? 这个块中的任何内容只有在所有的{value}参数都不为空(None | "")时才会被渲染 ?}

你也可以将它放在单词之间
这也会被渲染{? ,但是
只有当{this_value}和{this_value}都不为空时,这个块才会被渲染?}!
"""

输出解析器

  • llm_prompt装饰器会尝试根据输出类型自动检测最佳的输出解析器(如果未设置,则返回原始字符串)
  • 列表、字典和pydantic输出也可以原生支持(自动)
# 这个代码示例是完整的,应该可以直接运行

from langchain_decorators import llm_prompt

@llm_prompt
def write_name_suggestions(company_business:str, count:int)->list:
""" 为{company_business}的公司提供{count}个好的名称建议
"""
pass

write_name_suggestions(company_business="sells cookies", count=5)

更复杂的结构

对于字典/Pydantic,您需要指定格式化说明... 这可能很繁琐,这就是为什么您可以让输出解析器根据模型(pydantic)为您生成说明的原因

from langchain_decorators import llm_prompt
from pydantic import BaseModel, Field


class TheOutputStructureWeExpect(BaseModel):
name:str = Field (description="公司的名称")
headline:str = Field( description="公司的描述(用于首页)")
employees:list[str] = Field(description="5-8个虚假员工姓名及其职位")

@llm_prompt()
def fake_company_generator(company_business:str)->TheOutputStructureWeExpect:
""" 生成一个{company_business}的虚假公司
{FORMAT_INSTRUCTIONS}
"""
return

company = fake_company_generator(company_business="sells cookies")

# 以漂亮的格式打印结果
print("公司名称:",company.name)
print("公司描述:",company.headline)
print("公司员工:",company.employees)

将提示绑定到对象

from pydantic import BaseModel
from langchain_decorators import llm_prompt

class AssistantPersonality(BaseModel):
assistant_name:str
assistant_role:str
field:str

@property
def a_property(self):
return "whatever"

def hello_world(self, function_kwarg:str=None):
"""
我们可以在我们的提示中引用任何{field}或{a_property},并将其与方法中的{function_kwarg}结合使用
"""


@llm_prompt
def introduce_your_self(self)->str:
"""
``` <prompt:system>
你是一个名为{assistant_name}的助手。
你的角色是扮演{assistant_role}
```
```<prompt:user>
介绍一下你自己(不超过20个字)
```
"""



personality = AssistantPersonality(assistant_name="John", assistant_role="a pirate")

print(personality.introduce_your_self(personality))

更多示例: