Lanchchain Decorators est une couche au-dessus de Langchain qui fournit du sucre syntaxique? pour écrire des invites et des chaînes de Langchain personnalisées
Remarque: Il s'agit d'un addon non officiel à la bibliothèque de Langchain. Il n'essaie pas de rivaliser, juste pour faciliter l'utilisation. Beaucoup d'idées ici sont totalement opinionnées
Voici un exemple simple de code écrit avec Langchain Decorators
@ llm_prompt
def write_me_short_post ( topic : str , platform : str = "twitter" , audience : str = "developers" ) -> str :
"""
Write me a short header for my post about {topic} for {platform} platform.
It should be for {audience} audience.
(Max 15 words)
"""
return
# run it naturaly
write_me_short_post ( topic = "starwars" )
# or
write_me_short_post ( topic = "starwars" , platform = "redit" )Principes et avantages principaux:
pythonicDémarrage rapide
Déclarations rapides
Fonctions LLM (fonctions OpenAI)
Streaming simplifié
Sélection LLM automatique
Structures plus complexes
Lier l'invite à un objet
Définition des paramètres personnalisés
Débogage
Passer une mémoire, un rappel, un arrêt, etc.
Autre
pip install langchain_decoratorsUne bonne idée sur la façon de commencer est de revoir les exemples ici:
Par défaut, l'invite est l'ensemble des documents de fonction, sauf si vous marquez votre invite
Nous pouvons spécifier quelle partie de nos documents est la définition de l'invite, en spécifiant un bloc de code avec étiquette linguistique
@ llm_prompt
def write_me_short_post ( topic : str , platform : str = "twitter" , audience : str = "developers" ):
"""
Here is a good way to write a prompt as part of a function docstring, with additional documentation for devs.
It needs to be a code block, marked as a `<prompt>` language
```<prompt>
Write me a short header for my post about {topic} for {platform} platform.
It should be for {audience} audience.
(Max 15 words)
```
Now only the code block above will be used as a prompt, and the rest of the docstring will be used as a description for developers.
(It also has a nice benefit that IDE (like VS code) will display the prompt properly (not trying to parse it as markdown, and thus not showing new lines properly))
"""
return Pour les modèles de chat, est très utile pour définir l'invite comme un ensemble de modèles de messages ... voici comment le faire:
@ llm_prompt
def simulate_conversation ( human_input : str , agent_role : str = "a pirate" ):
"""
## System message
- note the `:system` sufix inside the <prompt:_role_> tag
```<prompt:system>
You are a {agent_role} hacker. You must act like one.
You reply always in code, using python or javascript code block...
for example:
... do not reply with anything else.. just with code - respecting your role.
```
# human message
(we are using the real role that are enforced by the LLM - GPT supports system, assistant, user)
``` <prompt:user>
Helo, who are you
```
a reply:
``` <prompt:assistant>
``` python <<- escaping inner code block with that should be part of the prompt
def hello():
print("Argh... hello you pesky pirate")
```
```
we can also add some history using placeholder
```<prompt:placeholder>
{history}
```
```<prompt:user>
{human_input}
```
Now only the code block above will be used as a prompt, and the rest of the docstring will be used as a description for developers.
(It also has a nice benefit that IDE (like VS code) will display the prompt properly (not trying to parse it as markdown, and thus not showing new lines properly))
"""
passLes rôles ici sont des rôles indigènes modèles (assistant, utilisateur, système pour chatgpt)
La syntaxe pour cela est la suivante:
@ llm_prompt
def prompt_with_optional_partials ():
"""
this text will be rendered always, but
{? anything inside this block will be rendered only if all the {value}s parameters are not empty (None | "") ?}
you can also place it in between the words
this too will be rendered{? , but
this block will be rendered only if {this_value} and {this_value}
are not empty?} !
""" # this code example is complete and should run as it is
from langchain_decorators import llm_prompt
@ llm_prompt
def write_name_suggestions ( company_business : str , count : int ) -> list :
""" Write me {count} good name suggestions for company that {company_business}
"""
pass
write_name_suggestions ( company_business = "sells cookies" , count = 5 )Actuellement pris en charge uniquement pour les derniers modèles de chat OpenAI
Tout ce que vous avez à faire est d'annoter votre fonction avec le décorateur @llm_function.
Cela analysera la description de LLM (le premier paragraphe cohérent est considéré comme une description de la fonction)
et des descriptions de paramètres ASO (les notations Google, Numpy et Spihnx sont prises en charge pour l'instant)
Par défaut, le format docstring est automatiquement résolu, mais la définition du format du docstring peut accélérer un peu les choses. - auto (par défaut): Le format est automatiquement déduit du docstring - google : le docstring est analysé en tant que marque (voir Google Docstring Format) - numpy : le docstring est analysé comme Markdown (voir Format Docstring Numpy) - sphinx Doctring Format)
La meilleure façon de définir l'énumération est de l'annotation de type à l'aide Literal :
@ llm_function
def do_magic ( spell : str , strength : Literal [ "light" , "medium" , "strong" ]):
"""
Do some kind of magic
Args:
spell (str): spall text
strength (str): the strength of the spell
""" Enum Alternative to Litteral pour annoter un argument "enum" comme, vous pouvez utiliser ce format "TypeScript": ["value_a" | "value_b"] ... si sera analysé. Ce texte fera également partie d'une description ... si vous n'en voulez pas, vous pouvez utiliser cette notation comme notation de type. Exemple:
Args:
message_type (["email" | "sms"]): type of a message / channel how to send the message
Ensuite, vous transmettez ces fonctions comme des arguments et @llm_prompt (l'argument doit être nommé functions
Voici comment l'utiliser:
from langchain . agents import load_tools
from langchian_decorators import llm_function , llm_prompt , GlobalSettings
@ llm_function
def send_message ( message : str , addressee : str = None , message_type : Literal [ "email" , "whatsapp" ] = "email" ):
""" Use this if user asks to send some message
Args:
message (str): message text to send
addressee (str): email of the addressee... in format [email protected]
message_type (str, optional): style of message by platform
"""
if message_type == "email" :
send_email ( addressee , message )
elif message_type == "whatsapp" :
send_whatsapp ( addressee , message )
# load some other tools from langchain
list_of_other_tools = load_tools (
tool_names = [...],
llm = GlobalSettings . get_current_settings (). default_llm )
@ llm_prompt
def do_what_user_asks_for ( user_input : str , functions : List [ Union [ Callable , BaseTool ]]):
"""
```<prompt:system>
Your role is to be a helpful asistant.
```
```<prompt:user>
{user_input}
```
"""
user_input = "Yo, send an email to John Smith that I will be late for the meeting"
result = do_what_user_asks_for (
user_input = user_input ,
functions = [ send_message , * list_of_other_tools ]
)
if result . is_function_call :
result . execute ()
else :
print ( result . output_text )De plus, vous pouvez également ajouter un argument
function_callà votre invite LLM pour contrôler le comportement GPT.
- Si vous définissez la valeur sur "Aucun" - il désactivera l'appel de fonction pour le moment, mais il peut toujours les voir (utile à un raisonnement / planification avant d'appeler la fonction)
- Si vous définissez la valeur sur "Auto" - GPT choisira d'utiliser ou pour utiliser les fonctions
- Si vous définissez la valeur sur un nom de fonction / ou de la fonction IT Self (les décorateurs géreront la résolution du même nom que celle utilisée dans le schéma), cela obligera GPT à utiliser cette fonction
Si vous utilisez l'argument des fonctions, la sortie sera toujours OutputWithFunctionCall
class OutputWithFunctionCall ( BaseModel ):
output_text : str
output : T
function_name : str = None
function_arguments : Union [ Dict [ str , Any ], str , None ]
function : Callable = None
function_async : Callable = None
@ property
def is_function_call ( self ):
...
@ property
def support_async ( self ):
...
@ property
def support_sync ( self ):
...
async def execute_async ( self ):
"""Executes the function asynchronously."""
...
def execute ( self ):
""" Executes the function synchronously.
If the function is async, it will be executed in a event loop.
"""
...
def to_function_message ( self , result = None ):
"""
Converts the result to a FunctionMessage...
you can override the result collected via execute with your own
"""
... Si vous voulez voir comment le schéma a été construit, vous pouvez utiliser la méthode get_function_schema qui est ajoutée à la fonction par le décorateur:
from langchain_decorators import get_function_schema
@ llm_function
def my_func ( arg1 : str ):
...
f_schema = get_function_schema ( my_func . get_function_schema )
print ( f_schema ) Afin d'ajouter le résultat à la mémoire / agent_scratchpad, vous pouvez utiliser to_function_message pour générer un fonctionnement fonctionnel que LLM interprètera comme un résultat outil / fonction
Le fournisseur de fonctions vous permet de fournir un ensemble de fonctions LLM plus dynamiquement, par exemple la liste des fonctions - basée sur l'entrée. Il vous permet également de donner un nom unique à chaque fonction pour cette exécution LLM. Cela pourrait être utile pour deux raisons:
Les schémas de fonction (et surtout leurs descriptions) sont des outils cruciaux pour guider LLM. Si vous activez la déclaration de fonction dynamique, vous pouvez (re) utiliser les mêmes attributs d'invite pour l'invite principale également dans le schéma LLM_FUNCTION:
@ llm_function ( dynamic_schema = True )
def db_search ( query_input : str ):
"""
This function is useful to search in our database.
{?Here are some examples of data available:
{closest_examples}?}
"""
@ llm_prompt
def run_agent ( query_input : str , closest_examples : str , functions ):
"""
Help user. Use a function when appropriate
"""
closest_examples = get_closest_examples ()
run_agent ( query_input , closest_examples , functions = [ db_search , ...])Ceci est juste pour l'illustration, un exemple entièrement exécutable est disponible ici, dans des exemples de code
Si nous voulons tirer parti du streaming:
De cette façon, nous marquons simplement quelle invite devrait être diffusée, sans avoir besoin de bricoler ce que nous devrions utiliser, passant autour du gestionnaire de streaming de création et de diffusion dans une partie particulière de notre chaîne ... Il suffit de tourner le streaming sur / désactiver sur un type d'invite / d'invite ...
Le streaming ne se produira que si nous l'appelons dans un contexte de streaming ... là, nous pouvons définir une fonction simple pour gérer le flux
# this code example is complete and should run as it is
from langchain_decorators import StreamingContext , llm_prompt
# this will mark the prompt for streaming (useful if we want stream just some prompts in our app... but don't want to pass distribute the callback handlers)
# note that only async functions can be streamed (will get an error if it's not)
@ llm_prompt ( capture_stream = True )
async def write_me_short_post ( topic : str , platform : str = "twitter" , audience : str = "developers" ):
"""
Write me a short header for my post about {topic} for {platform} platform.
It should be for {audience} audience.
(Max 15 words)
"""
pass
# just an arbitrary function to demonstrate the streaming... wil be some websockets code in the real world
tokens = []
def capture_stream_func ( new_token : str ):
tokens . append ( new_token )
# if we want to capture the stream, we need to wrap the execution into StreamingContext...
# this will allow us to capture the stream even if the prompt call is hidden inside higher level method
# only the prompts marked with capture_stream will be captured here
with StreamingContext ( stream_to_stdout = True , callback = capture_stream_func ):
result = await run_prompt ()
print ( "Stream finished ... we can distinguish tokens thanks to alternating colors" )
print ( " n We've captured" , len ( tokens ), "tokens? n " )
print ( "Here is the result:" )
print ( result )Dans la vraie vie, il pourrait y avoir des situations, où le contexte se développerait sur la fenêtre du modèle de base que vous utilisez (par exemple l'historique de chat long) ... mais comme cela pourrait se produire seulement quelques fois, ce serait génial, ne serait-ce que dans ce scénario, le modèle (généralement plus cher) avec une fenêtre de contexte plus grande serait utilisé, et sinon nous utiliserions le moins cher.
Maintenant, vous pouvez le faire avec LLMSelector
from langchain_decorators import LlmSelector
my_llm_selector = LlmSelector (
generation_min_tokens = 0 , # how much token at min. I for generation I want to have as a buffer
prompt_to_generation_ratio = 1 / 3 # what percentage of the prompt length should be used for generation buffer
)
. with_llm_rule ( ChatGooglePalm (), max_tokens = 512 ) # ... if you want to use LLM whose window is not defined in langchain_decorators.common.MODEL_LIMITS (only OpenAI and Anthropic are there)
. with_llm ( ChatOpenAI ( model = "gpt-3.5-turbo" )) # these models are known, therefore we can just pass them and the max window will be resolved
. with_llm ( ChatOpenAI ( model = "gpt-3.5-turbo-16k-0613" ))
. with_llm ( ChatOpenAI ( model = "claude-v1.3-100k" ))Cette classe vous permet de définir une séquence de LLMS avec une règle basée sur la longueur de l'invite et la longueur de génération attendue ... et ce n'est qu'après que le seuil sera passé, le modèle le plus cher sera utilisé automatiquement.
Vous pouvez le définir dans les GlobalSettings:
langchain_decorators . GlobalSettings . define_settings (
llm_selector = my_llm_selector # pass the selector into global settings
)Remarque: En ce qui concerne la version v0.0.10, vous y êtes le llmselector est dans les paramètres par défaut prédéfinis. Vous pouvez le remplacer en vous fournissant ou en configurant le LLM par défaut ou Streaming LLM par défaut
Ou dans un type d'invite spécifique:
from langchain_decorators import PromptTypes
class MyCustomPromptTypes ( PromptTypes ):
MY_TUBO_PROMPT = PromptTypeSettings ( llm_selector = my_llm_selector )Pour le dict / pyndantique, vous devez spécifier les instructions de mise en forme ... cela peut être fastidieux, c'est pourquoi vous pouvez laisser l'analyseur de sortie vous générer les instructions basées sur le modèle (pydante)
from langchain_decorators import llm_prompt
from pydantic import BaseModel , Field
class TheOutputStructureWeExpect ( BaseModel ):
name : str = Field ( description = "The name of the company" )
headline : str = Field ( description = "The description of the company (for landing page)" )
employees : list [ str ] = Field ( description = "5-8 fake employee names with their positions" )
@ llm_prompt ()
def fake_company_generator ( company_business : str ) -> TheOutputStructureWeExpect :
""" Generate a fake company that {company_business}
{FORMAT_INSTRUCTIONS}
"""
return
company = fake_company_generator ( company_business = "sells cookies" )
# print the result nicely formatted
print ( "Company name: " , company . name )
print ( "company headline: " , company . headline )
print ( "company employees: " , 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 ):
"""
We can reference any {field} or {a_property} inside our prompt... and combine it with {function_kwarg} in the method
"""
@ llm_prompt
def introduce_your_self ( self ) -> str :
"""
``` <prompt:system>
You are an assistant named {assistant_name}.
Your role is to act as {assistant_role}
```
```<prompt:user>
Introduce your self (in less than 20 words)
```
"""
personality = AssistantPersonality ( assistant_name = "John" , assistant_role = "a pirate" )
print ( personality . introduce_your_self ( personality )) Ici, nous ne faisons que marquer une fonction comme une invite avec llm_prompt Decorator, la transformant efficacement en un llmchain. Au lieu de l'exécuter
LLMCHAIN standard prend beaucoup plus de paramètre d'initial que simplement entrées_variables et invite ... voici ce détail d'implémentation caché dans le décorateur. Voici comment cela fonctionne:
Utilisation des paramètres globaux :
# define global settings for all prompty (if not set - chatGPT is the current default)
from langchain_decorators import GlobalSettings
GlobalSettings . define_settings (
default_llm = ChatOpenAI ( temperature = 0.0 ), this is default ... can change it here globally
default_streaming_llm = ChatOpenAI ( temperature = 0.0 , streaming = True ), this is default ... can change it here for all ... will be used for streaming
)En utilisant des types d'invites prédéfinis
#You can change the default prompt types
from langchain_decorators import PromptTypes , PromptTypeSettings
PromptTypes . AGENT_REASONING . llm = ChatOpenAI ()
# Or you can just define your own ones:
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 :
...Définissez les paramètres directement dans le décorateur
from langchain . llms import OpenAI
@ llm_prompt (
llm = OpenAI ( temperature = 0.7 ),
stop_tokens = [ " n Observation" ],
...
)
def creative_writer ( book_title : str ) -> str :
...Pour passer l'un d'eux, déclarez-les simplement dans la fonction (ou utilisez des kwargs pour passer quoi que ce soit)
(Ils n'ont pas nécessairement besoin d'être déclarés, mais c'est une bonne pratique si vous allez les utiliser)
@ llm_prompt ()
async def write_me_short_post ( topic : str , platform : str = "twitter" , memory : SimpleMemory = None ):
"""
{history_key}
Write me a short header for my post about {topic} for {platform} platform.
It should be for {audience} audience.
(Max 15 words)
"""
pass
await write_me_short_post ( topic = "old movies" ) Il existe plusieurs options pour contrôler les sorties connectées à la console. Le moyen le plus simple est de définir la variable Env: LANGCHAIN_DECORATORS_VERBOSE et de le définir sur "true"
Vous pouvez également contrôler cela par programme en définissant vos paramètres globaux comme indiqué ici
La dernière option consiste à le contrôler par chaque cas, simplement en Turing sur le mode verbeux sur l'invite:
@llm_prompt(verbose=True)
def your_prompt(param1):
...
Promptwatch IO est une plate-forme pour suivre et tracer des détails sur tout ce qui se passe dans les exécutions de Langchain. Il permet une baisse d'une seule ligne d'intégration, simplement en enveloppez votre code de point d'entrée dans
with PromptWatch():
run_your_code()
En savoir plus sur PromptWatch ici: www.promptwatch.io
les commentaires, les contributions et les relations publiques sont les bienvenus