Intégrez facilement les modèles de grandes langues dans votre code Python. Utilisez simplement les décorateurs @prompt et @chatprompt pour créer des fonctions qui renvoient la sortie structurée du LLM. Mélanger les requêtes LLM et les appels de fonction avec du code Python régulier pour créer une logique complexe.
FunctionCall et ParallelFunctionCall .async def lors de la définition d'une fonction magentique.pip install magenticou en utilisant des UV
uv add magentic Configurez votre touche API OpenAI en définissant la variable d'environnement OPENAI_API_KEY . Pour configurer un autre fournisseur LLM, voir la configuration pour en savoir plus.
Le décorateur @prompt vous permet de définir un modèle pour une invite de modèle de langue grand (LLM) en tant que fonction Python. Lorsque cette fonction est appelée, les arguments sont insérés dans le modèle, cette invite est envoyée à un LLM qui génère la sortie de la fonction.
from magentic import prompt
@ prompt ( 'Add more "dude"ness to: {phrase}' )
def dudeify ( phrase : str ) -> str : ... # No function body as this is never executed
dudeify ( "Hello, how are you?" )
# "Hey, dude! What's up? How's it going, my man?" Le décorateur @prompt respectera l'annotation de type de retour de la fonction décorée. Cela peut être n'importe quel type soutenu par Pyndantic, y compris un modèle pydantic .
from magentic import prompt
from pydantic import BaseModel
class Superhero ( BaseModel ):
name : str
age : int
power : str
enemies : list [ str ]
@ prompt ( "Create a Superhero named {name}." )
def create_superhero ( name : str ) -> Superhero : ...
create_superhero ( "Garden Man" )
# Superhero(name='Garden Man', age=30, power='Control over plants', enemies=['Pollution Man', 'Concrete Woman'])Voir les sorties structurées pour en savoir plus.
Le décorateur @chatprompt fonctionne comme @prompt mais vous permet de transmettre des messages de chat comme un modèle plutôt qu'une seule invite de texte. Cela peut être utilisé pour fournir un message système ou pour une invitation à quelques tirs où vous fournissez des exemples de réponses pour guider la sortie du modèle. Les champs de format désignés par des accolades bouclées {example} seront remplis dans tous les messages (sauf FunctionResultMessage ).
from magentic import chatprompt , AssistantMessage , SystemMessage , UserMessage
from pydantic import BaseModel
class Quote ( BaseModel ):
quote : str
character : str
@ chatprompt (
SystemMessage ( "You are a movie buff." ),
UserMessage ( "What is your favorite quote from Harry Potter?" ),
AssistantMessage (
Quote (
quote = "It does not do to dwell on dreams and forget to live." ,
character = "Albus Dumbledore" ,
)
),
UserMessage ( "What is your favorite quote from {movie}?" ),
)
def get_movie_quote ( movie : str ) -> Quote : ...
get_movie_quote ( "Iron Man" )
# Quote(quote='I am Iron Man.', character='Tony Stark')Voir le chat pour plus.
Un LLM peut également décider d'appeler des fonctions. Dans ce cas, la fonction décorée @prompt renvoie un objet FunctionCall qui peut être appelé pour exécuter la fonction à l'aide des arguments fournis par le LLM.
from typing import Literal
from magentic import prompt , FunctionCall
def search_twitter ( query : str , category : Literal [ "latest" , "people" ]) -> str :
"""Searches Twitter for a query."""
print ( f"Searching Twitter for { query !r } in category { category !r } " )
return "<twitter results>"
def search_youtube ( query : str , channel : str = "all" ) -> str :
"""Searches YouTube for a query."""
print ( f"Searching YouTube for { query !r } in channel { channel !r } " )
return "<youtube results>"
@ prompt (
"Use the appropriate search function to answer: {question}" ,
functions = [ search_twitter , search_youtube ],
)
def perform_search ( question : str ) -> FunctionCall [ str ]: ...
output = perform_search ( "What is the latest news on LLMs?" )
print ( output )
# > FunctionCall(<function search_twitter at 0x10c367d00>, 'LLMs', 'latest')
output ()
# > Searching Twitter for 'Large Language Models news' in category 'latest'
# '<twitter results>'Voir la fonction en appelant plus.
Parfois, le LLM nécessite de passer un ou plusieurs appels de fonction pour générer une réponse finale. Le décorateur @prompt_chain résoudra automatiquement les objets FunctionCall et transmettra la sortie au LLM pour continuer jusqu'à ce que la réponse finale soit atteinte.
Dans l'exemple suivant, lorsque describe_weather est appelé le LLM appelle d'abord la fonction get_current_weather , utilise le résultat pour formuler sa réponse finale qui est retournée.
from magentic import prompt_chain
def get_current_weather ( location , unit = "fahrenheit" ):
"""Get the current weather in a given location"""
# Pretend to query an API
return {
"location" : location ,
"temperature" : "72" ,
"unit" : unit ,
"forecast" : [ "sunny" , "windy" ],
}
@ prompt_chain (
"What's the weather like in {city}?" ,
functions = [ get_current_weather ],
)
def describe_weather ( city : str ) -> str : ...
describe_weather ( "Boston" )
# 'The current weather in Boston is 72°F and it is sunny and windy.' Les fonctions alimentées par LLM créées à l'aide de @prompt , @chatprompt et @prompt_chain peuvent être fournies en functions d'autres décorateurs @prompt / @prompt_chain , tout comme des fonctions Python régulières. Cela permet une fonctionnalité de plus en plus complexe LLM, tout en permettant à des composants individuels d'être testés et améliorés isolément.
La classe StreamedStr (et AsyncStreamedStr ) peut être utilisée pour diffuser la sortie du LLM. Cela vous permet de traiter le texte lors de sa génération, plutôt que de recevoir la sortie entière à la fois.
from magentic import prompt , StreamedStr
@ prompt ( "Tell me about {country}" )
def describe_country ( country : str ) -> StreamedStr : ...
# Print the chunks while they are being received
for chunk in describe_country ( "Brazil" ):
print ( chunk , end = "" )
# 'Brazil, officially known as the Federative Republic of Brazil, is ...' Plusieurs StreamedStr peuvent être créés en même temps pour diffuser simultanément des sorties LLM. Dans l'exemple ci-dessous, la génération de la description de plusieurs pays prend environ le même temps que pour un seul pays.
from time import time
countries = [ "Australia" , "Brazil" , "Chile" ]
# Generate the descriptions one at a time
start_time = time ()
for country in countries :
# Converting `StreamedStr` to `str` blocks until the LLM output is fully generated
description = str ( describe_country ( country ))
print ( f" { time () - start_time :.2f } s : { country } - { len ( description ) } chars" )
# 22.72s : Australia - 2130 chars
# 41.63s : Brazil - 1884 chars
# 74.31s : Chile - 2968 chars
# Generate the descriptions concurrently by creating the StreamedStrs at the same time
start_time = time ()
streamed_strs = [ describe_country ( country ) for country in countries ]
for country , streamed_str in zip ( countries , streamed_strs ):
description = str ( streamed_str )
print ( f" { time () - start_time :.2f } s : { country } - { len ( description ) } chars" )
# 22.79s : Australia - 2147 chars
# 23.64s : Brazil - 2202 chars
# 24.67s : Chile - 2186 chars Les sorties structurées peuvent également être diffusées à partir du LLM en utilisant l'annotation de retour Iterable (ou AsyncIterable ). Cela permet de traiter chaque élément pendant que le suivant est généré.
from collections . abc import Iterable
from time import time
from magentic import prompt
from pydantic import BaseModel
class Superhero ( BaseModel ):
name : str
age : int
power : str
enemies : list [ str ]
@ prompt ( "Create a Superhero team named {name}." )
def create_superhero_team ( name : str ) -> Iterable [ Superhero ]: ...
start_time = time ()
for hero in create_superhero_team ( "The Food Dudes" ):
print ( f" { time () - start_time :.2f } s : { hero } " )
# 2.23s : name='Pizza Man' age=30 power='Can shoot pizza slices from his hands' enemies=['The Hungry Horde', 'The Junk Food Gang']
# 4.03s : name='Captain Carrot' age=35 power='Super strength and agility from eating carrots' enemies=['The Sugar Squad', 'The Greasy Gang']
# 6.05s : name='Ice Cream Girl' age=25 power='Can create ice cream out of thin air' enemies=['The Hot Sauce Squad', 'The Healthy Eaters']Voir le streaming pour plus.
Les fonctions asynchrones / coroutines peuvent être utilisées pour interroger simultanément le LLM. Cela peut augmenter considérablement la vitesse globale de la génération et permettre également à d'autres code asynchrones d'exécuter en attendant la sortie LLM. Dans l'exemple ci-dessous, le LLM génère une description pour chaque président américain alors qu'il attend le suivant de la liste. La mesure des caractères générées par seconde montre que cet exemple atteint une accélération de 7x par rapport au traitement en série.
import asyncio
from time import time
from typing import AsyncIterable
from magentic import prompt
@ prompt ( "List ten presidents of the United States" )
async def iter_presidents () -> AsyncIterable [ str ]: ...
@ prompt ( "Tell me more about {topic}" )
async def tell_me_more_about ( topic : str ) -> str : ...
# For each president listed, generate a description concurrently
start_time = time ()
tasks = []
async for president in await iter_presidents ():
# Use asyncio.create_task to schedule the coroutine for execution before awaiting it
# This way descriptions will start being generated while the list of presidents is still being generated
task = asyncio . create_task ( tell_me_more_about ( president ))
tasks . append ( task )
descriptions = await asyncio . gather ( * tasks )
# Measure the characters per second
total_chars = sum ( len ( desc ) for desc in descriptions )
time_elapsed = time () - start_time
print ( total_chars , time_elapsed , total_chars / time_elapsed )
# 24575 28.70 856.07
# Measure the characters per second to describe a single president
start_time = time ()
out = await tell_me_more_about ( "George Washington" )
time_elapsed = time () - start_time
print ( len ( out ), time_elapsed , len ( out ) / time_elapsed )
# 2206 18.72 117.78Voir asyncio pour en savoir plus.
functions sur @prompt peut contenir des fonctions asynchrones / coroutine. Lorsque les objets FunctionCall correspondants sont appelés, le résultat doit être attendu.Annotated peut être utilisée pour fournir des descriptions et autres métadonnées pour les paramètres de fonction. Voir la documentation pydantique sur l'utilisation Field pour décrire les arguments de la fonction.@prompt et @prompt_chain acceptent également un argument model . Vous pouvez passer une instance d' OpenaiChatModel pour utiliser GPT4 ou configurer une température différente. Voir ci-dessous.@prompt en suivant l'exemple de carnet pour un Pandas DataFrame. Magentic prend en charge plusieurs "backends" (fournisseurs LLM). Ce sont
openai : le backend par défaut qui utilise le package openai Python. Prend en charge toutes les fonctionnalités de Magentic. from magentic import OpenaiChatModelanthropic : utilise le package Python anthropic . Prend en charge toutes les fonctionnalités de Magentic, mais les réponses en streaming sont actuellement reçues en même temps. pip install " magentic[anthropic] " from magentic . chat_model . anthropic_chat_model import AnthropicChatModellitellm : utilise le package litellm Python pour activer les LLM de questionnement de nombreux fournisseurs différents. Remarque: Certains modèles peuvent ne pas prendre en charge toutes les caractéristiques de la fonction magentic EG appelant / sortie structurée et streaming. pip install " magentic[litellm] " from magentic . chat_model . litellm_chat_model import LitellmChatModelmistral : utilise le package openai Python avec quelques petites modifications pour rendre les requêtes API compatibles avec l'API Mistral. Prise en charge de toutes les fonctionnalités de Magentic, mais les appels d'outils (y compris les sorties structurés) ne sont pas diffusés, ils sont donc reçus en même temps. Remarque: Une future version de Magentic peut passer à l'utilisation du package mistral Python. from magentic . chat_model . mistral_chat_model import MistralChatModel Le backend et LLM ( ChatModel ) utilisés par magentic peuvent être configurés de plusieurs manières. Lorsqu'une fonction magentique est appelée, le ChatModel à utiliser suit cet ordre de préférence
ChatModel fournie comme argument model au décorateur magentiquewith MyChatModel:ChatModel global créé à partir des variables d'environnement et les paramètres par défaut dans src / magentic / settings.py from magentic import OpenaiChatModel , prompt
from magentic . chat_model . litellm_chat_model import LitellmChatModel
@ prompt ( "Say hello" )
def say_hello () -> str : ...
@ prompt (
"Say hello" ,
model = LitellmChatModel ( "ollama_chat/llama3" ),
)
def say_hello_litellm () -> str : ...
say_hello () # Uses env vars or default settings
with OpenaiChatModel ( "gpt-3.5-turbo" , temperature = 1 ):
say_hello () # Uses openai with gpt-3.5-turbo and temperature=1 due to context manager
say_hello_litellm () # Uses litellm with ollama_chat/llama3 because explicitly configuredLes variables d'environnement suivantes peuvent être définies.
| Variable d'environnement | Description | Exemple |
|---|---|---|
| Magentic_backend | Le package à utiliser comme backend LLM | anthropic / openai / litellm |
| Magentic_anthropic_model | Modèle anthropique | Claude-3-Haiku-20240307 |
| Magentic_anthropic_api_key | Clé API anthropique à utiliser par magentic | Sk -... |
| Magentic_anthropic_base_url | URL de base pour une API compatible anthropique | http: // localhost: 8080 |
| Magentic_anthropic_max_tokens | Nombre maximum de jetons générés | 1024 |
| Magentic_anthropic_Temperature | Température | 0,5 |
| Magentic_litellm_model | Modèle Litellm | claude-2 |
| Magentic_litellm_api_base | L'URL de base pour interroger | http: // localhost: 11434 |
| Magentic_litellm_max_tokens | Litellm MAX Nombre de jetons générés | 1024 |
| Magentic_litellm_Temperature | Température litellm | 0,5 |
| Magentic_mistral_model | Modèle de Mistral | Mistral-Geste |
| Magentic_mistral_api_key | Mistral API Key à utiliser par Magentic | XEG ... |
| Magentic_mistral_base_url | URL de base pour une API compatible Mistral | http: // localhost: 8080 |
| Magentic_mistral_max_tokens | Nombre maximum de jetons générés | 1024 |
| Magentic_mistral_seed | Graines pour l'échantillonnage déterministe | 42 |
| Magentic_mistral_Temperature | Température | 0,5 |
| Magentic_openai_model | Modèle openai | gpt-4 |
| Magentic_openai_api_key | Clé API Openai à utiliser par Magentic | Sk -... |
| Magentic_openai_api_type | Options autorisées: "Openai", "Azure" | azuré |
| Magentic_openai_base_url | URL de base pour une API compatible Openai | http: // localhost: 8080 |
| Magentic_openai_max_tokens | Nombre maximal d'Openai de jetons générés | 1024 |
| Magentic_openai_seed | Graines pour l'échantillonnage déterministe | 42 |
| Magentic_openai_Temperature | Température Openai | 0,5 |
Lorsque vous utilisez le backend openai , la définition de la variable d'environnement MAGENTIC_OPENAI_BASE_URL ou l'utilisation OpenaiChatModel(..., base_url="http://localhost:8080") dans le code vous permet d'utiliser magentic avec n'importe quelle API compatible Openai EG Azure Openai Service, Linellm OpenAI Proxy Server, Localai. Notez que si l'API ne prend pas en charge les appels d'outils, vous ne pourrez pas créer des fonctions d'invite qui renvoient des objets Python, mais d'autres fonctionnalités de magentic fonctionneront toujours.
Pour utiliser Azure avec le backend OpenAI, vous devrez définir la variable d'environnement MAGENTIC_OPENAI_API_TYPE sur "Azure" ou utiliser OpenaiChatModel(..., api_type="azure") , et également définir les variables d'environnement nécessaires par le package OpenAI pour accéder à Azure. Voir https://github.com/openai/openai-python#microsoft-azure-openai
De nombreux vérificateurs de type augmenteront les avertissements ou les erreurs pour les fonctions avec le décorateur @prompt en raison de la fonction sans valeur de corps ni de valeur de retour. Il existe plusieurs façons de les gérer.
empty-body . # pyproject.toml
[ tool . mypy ]
disable_error_code = [ " empty-body " ]... (cela ne satisfait pas MyPy) ou raise . @ prompt ( "Choose a color" )
def random_color () -> str : ...# type: ignore[empty-body] sur chaque fonction. Dans ce cas, vous pouvez ajouter un docstring au lieu de ... @ prompt ( "Choose a color" )
def random_color () -> str : # type: ignore[empty-body]
"""Returns a random color."""