Una guida pratica all'integrazione di API esterne per interazioni avanzate con un'applicazione chatbot utilizzando LangChain e Chainlit

In questo tutorial vedremo come integrare un'API esterna con un'applicazione chatbot personalizzata. Nei miei articoli precedenti sulla creazione di un'applicazione chatbot personalizzata, abbiamo trattato le nozioni di base per creare un chatbot con funzionalità specifiche utilizzando LangChain E OpenAIe come creare l'applicazione web per il nostro chatbot utilizzando Catena illuminata.

Flusso di lavoro di un chatbot integrato con API: immagine dell'autore

Se sei nuovo in questa serie, ti consiglio di consultare i miei articoli precedenti per una guida dettagliata passo passo:

Questo tutorial si concentrerà sul miglioramento del nostro chatbot, Scoopsieun assistente per il gelato, collegandolo ad un'API esterna. Puoi pensare a un'API come a un modo accessibile per estrarre e condividere dati all'interno e tra programmi. Gli utenti possono effettuare richieste a un'API per recuperare o inviare dati e l'API risponde con alcune informazioni. Ci connetteremo Scoopsie a un'API per recuperare informazioni da una gelateria immaginaria e utilizzare tali risposte per fornire informazioni. Per la maggior parte delle applicazioni chatbot, collegare il tuo chatbot personalizzato a un'API esterna può essere incredibilmente utile e, in alcuni casi, persino necessario.

Ecco un breve riepilogo di dove ci eravamo lasciati: attualmente il nostro chatbot.py utilizza LLMChain per interrogare il modello GPT-3.5 di OpenAI per rispondere alle domande relative al gelato di un utente:

import chainlit as cl
from langchain_openai import OpenAI
from langchain.chains import LLMChain
from prompts import ice_cream_assistant_prompt_template
from langchain.memory.buffer import ConversationBufferMemory

from dotenv import load_dotenv

load_dotenv()

@cl.on_chat_start
def query_llm():
llm = OpenAI(model='gpt-3.5-turbo-instruct',
temperature=0)
conversation_memory = ConversationBufferMemory(memory_key="chat_history",
max_len=50,
return_messages=True,
)
llm_chain = LLMChain(llm=llm,
prompt=ice_cream_assistant_prompt_template,
memory=conversation_memory)
cl.user_session.set("llm_chain", llm_chain)

@cl.on_message
async def query_llm(message: cl.Message):
llm_chain = cl.user_session.get("llm_chain")
response = await llm_chain.acall(message.content,
callbacks=(
cl.AsyncLangchainCallbackHandler()))

await cl.Message(response("text")).send()

Se non hai impostato un file conda ambiente per il progetto, puoi procedere e crearne uno. Ricorda che Chainlit richiede python>=3.8.

conda create --name chatbot_langchain python=3.10

Attiva il tuo ambiente con:

conda activate chatbot_langchain

Per installare tutte le dipendenze, esegui:

pip install -r requirements.txt

Inizieremo creando un'API per connetterci a Scoopsie. Questa API rappresenta una gelateria immaginaria per consentire agli utenti di recuperare il menu del negozio, insieme ad altre informazioni come personalizzazioni, recensioni degli utenti e offerte speciali. Utilizzeremo Flask, un framework Python per lo sviluppo web, per codificare le informazioni di cui sopra in diversi endpoint API. Questi includono:

  1. /menu: UNGET endpoint per recuperare il menu di sapori e condimenti.
  2. /customizations: UNGET endpoint per recuperare le personalizzazioni
  3. /special-offers: UNGET endpoint per recuperare le offerte speciali.
  4. /user-reviews: UNGET endpoint per recuperare le recensioni degli utenti.

Tenere Scoopsie focalizzati sulla fornitura di informazioni piuttosto che sulla gestione delle transazioni o sull'elaborazione degli ordini, limiteremo il nostro ambito attuale a questi endpoint informativi. Tuttavia, puoi espandere questa API per includere altri endpoint, come un endpoint POST per consentire all'utente di inviare un ordine o altri endpoint GET.

Passo 1

Creiamo uno script Python denominato data_store.py per memorizzare dati statici come menu, offerte speciali, recensioni dei clienti e opzioni di personalizzazione. Ecco come possiamo strutturarlo:

# Example menu, special offers, customer reviews, and customizations
menu = {
"flavors": (
{"flavorName": "Strawberry", "count": 50},
{"flavorName": "Chocolate", "count": 75}
),
"toppings": (
{"toppingName": "Hot Fudge", "count": 50},
{"toppingName": "Sprinkles", "count": 2000},
{"toppingName": "Whipped Cream", "count": 50}
)
}
special_offers = {
"offers": (
{"offerName": "Two for Tuesday", "details": "Buy one get one free on all ice cream flavors every Tuesday."},
{"offerName": "Winter Wonderland Discount", "details": "25% off on all orders above $20 during the winter season."}
)
}
customer_reviews = {
"reviews": (
{"userName": "andrew_1", "rating": 5, "comment": "Loved the chocolate flavor!"},
{"userName": "john", "rating": 4, "comment": "Great place, but always crowded."},
{"userName": "allison", "rating": 5, "comment": "Love the ice-creams and Scoopsie is super helpful!"}
)
}
customizations = {
"options": (
{"customizationName": "Sugar-Free", "details": "Available for most flavors."},
{"customizationName": "Extra Toppings", "details": "Choose as many toppings as you want for an extra $5!"}
)
}

Puoi modificare lo script sopra per adattarlo meglio alle tue esigenze specifiche. Questi esempi mostrano i possibili attributi per ciascuna categoria. Nelle applicazioni pratiche, la memorizzazione di questi dati in un database per il recupero dinamico è più adatta.

Passo 2

Impostiamo la nostra applicazione Flask in un file denominato ice_cream_store_app.pyda dove importeremo i dati data_store.py. Possiamo iniziare importando le librerie richieste e inizializzando l'applicazione Flask:

from flask import Flask, jsonify
from data_store import menu, special_offers, customer_reviews, customizations

app = Flask(__name__)

Passaggio 3

Ora configuriamo le funzioni degli endpoint API. In Flask, queste funzioni rispondono direttamente alle richieste web senza bisogno di argomenti espliciti, grazie al meccanismo di routing di Flask. Queste funzioni sono progettate per:

  • gestire automaticamente le richieste senza passaggio di argomenti diretti, ad eccezione di quelli impliciti self per visualizzazioni basate su classi, che non utilizziamo qui.
  • restituire a tuple con due elementi:
    – UN dict convertito in formato JSON tramite jsonify()
    – un codice di stato HTTP, in genere 200 per indicare il successo.

Di seguito sono riportate le funzioni dell'endpoint:

@app.route('/menu', methods=('GET'))
def get_menu():
"""
Retrieves the menu data.
Returns:
A tuple containing the menu data as JSON and the HTTP status code.
"""
return jsonify(menu), 200

@app.route('/special-offers', methods=('GET'))
def get_special_offers():
"""
Retrieves the special offers data.
Returns:
A tuple containing the special offers data as JSON and the HTTP status code.
"""
return jsonify(special_offers), 200

@app.route('/customer-reviews', methods=('GET'))
def get_customer_reviews():
"""
Retrieves customer reviews data.
Returns:
A tuple containing the customer reviews data as JSON and the HTTP status code.
"""
return jsonify(customer_reviews), 200

@app.route('/customizations', methods=('GET'))
def get_customizations():
"""
Retrieves the customizations data.
Returns:
A tuple containing the customizations data as JSON and the HTTP status code.
"""
return jsonify(customizations), 200

Per ciascuna funzione di cui sopra, jsonify() viene utilizzato per trasformare i dizionari Python in formato JSON, che viene quindi restituito con a 200 codice di stato per le query riuscite.

Passaggio 4

Infine, aggiungiamo il seguente codice al file our ice_cream_store_app.py sceneggiatura:

if __name__ == '__main__':
app.run(debug=True)

L'API può essere avviata eseguendo il seguente comando nel terminale:

python ice_cream_store_app.py
Avvio del Flask Server per l'API della Gelateria – Immagine dell'autore

Una volta che l'applicazione è in esecuzione, l'API personalizzata di Scoopsie sarà accessibile all'indirizzo http://127.0.0.1:5000/. Per controllare i vari endpoint, puoi utilizzare strumenti come Postman o utilizzare un browser Web per visualizzare un particolare endpoint: http://127.0.0.1:5000/{endpoint_name}.

Endpoint API della gelateria: immagine dell'autore
Endpoint API Gelateria in azione – per autore

Le catene in LangChain semplificano attività complesse eseguendole come una sequenza di operazioni più semplici e connesse. Queste catene in genere incorporano elementi come LLM, PromptTemplate, parser di output o API esterne di terze parti, su cui ci concentreremo in questo tutorial. Mi immergo nella funzionalità Chain di LangChain in modo più dettagliato nel mio primo articolo sulla serie, a cui puoi accedere Qui.

In precedenza, utilizzavamo LLMChain di LangChain per interazioni dirette con LLM. Ora, per estendere Scoopsie capacità di interagire con API esterne, utilizzeremo APIChain. APIChain è un modulo LangChain progettato per formattare gli input dell'utente in richieste API. Ciò consentirà al nostro chatbot di inviare richieste e ricevere risposte da un'API esterna, ampliandone le funzionalità.

APIChain può essere configurato per gestire diversi metodi HTTP (GET, POST, PUT, DELETE e così via), impostare intestazioni di richiesta e gestire il corpo della richiesta. Supporta anche i payload JSON, comunemente utilizzati nelle comunicazioni API RESTful.

Passo 1

Importiamo prima il modulo APIChain di LangChain, insieme agli altri moduli richiesti, nel ns chatbot.py file. Questo script ospiterà tutta la nostra logica applicativa. È possibile impostare le variabili d'ambiente necessarie, come ad esempio OPENAI_API_KEY in un .env script, a cui è possibile accedere da dotenv libreria Python.

import chainlit as cl
from langchain_openai import OpenAI
from langchain.chains import LLMChain, APIChain
from langchain.memory.buffer import ConversationBufferMemory
from dotenv import load_dotenv

load_dotenv()

Passo 2

Per la classe APIChain, abbiamo bisogno della documentazione dell'API esterna in formato stringa per accedere ai dettagli dell'endpoint. Questa documentazione dovrebbe delineare gli endpoint, i metodi, i parametri e le risposte previste dell'API. Ciò aiuta LLM nella formulazione delle richieste API e nell'analisi delle risposte. È utile definire queste informazioni come dizionario e quindi convertirle in una stringa per un utilizzo successivo.

Creiamo un nuovo script Python chiamato api_docs.py e aggiungi i documenti per l'API del nostro negozio immaginario:

import json

scoopsie_api_docs = {
"base_url": "<http://127.0.0.1:5000/>",
"endpoints": {
"/menu": {
"method": "GET",
"description": "Retrieve the menu of flavors and customizations.",
"parameters": None,
"response": {
"description": "A JSON object containing available flavors
and toppings along with their counts.",
"content_type": "application/json"
}
},
"/special-offers": {
"method": "GET",
"description": "Retrieve current special offers and discounts.",
"parameters": None,
"response": {
"description": "A JSON object listing the current special
offers and discounts.",
"content_type": "application/json"
}
},
"/customer-reviews": {
"method": "GET",
"description": "Retrieve customer reviews for the ice cream store.",
"parameters": None,
"response": {
"description": "A JSON object containing customer
reviews, ratings, and comments.",
"content_type": "application/json"
}
},
"/customizations": {
"method": "GET",
"description": "Retrieve available ice cream customizations.",
"parameters": None,
"response": {
"description": "A JSON object listing available
customizations like toppings and sugar-free
options.",
"content_type": "application/json"
}
}
}
}

# Convert the dictionary to a JSON string
scoopsie_api_docs = json.dumps(scoopsie_api_docs, indent=2)

Ho formattato la documentazione della nostra API personalizzata in un dizionario Python chiamato scoopsie_api_docs. Questo dizionario include l'URL di base dell'API e descrive in dettaglio i nostri quattro endpoint sotto il file endpoints chiave. Ogni endpoint elenca il proprio metodo HTTP (all GET per noi), una descrizione concisa, parametri accettati (nessuno per questi endpoint) e il formato di risposta previsto: un oggetto JSON con dati rilevanti. Il dizionario viene quindi trasformato in una stringa JSON utilizzando json.dumpsrientrato di 2 spazi per leggibilità.

Importiamo questa documentazione API nel nostro chatbot.py sceneggiatura:

from api_docs import scoopsie_api_docs

Passaggio 3

APIChain richiede due prompt: uno per selezionare l'endpoint API corretto e un altro per creare una risposta concisa alla query dell'utente in base a tale endpoint. Questi prompt hanno valori predefiniti, tuttavia, creeremo i nostri prompt per garantire un'interazione personalizzata. Possiamo aggiungere i seguenti nuovi prompt nel nostro prompts.py file:

api_url_template = """
Given the following API Documentation for Scoopsie's official
ice cream store API: {api_docs}
Your task is to construct the most efficient API URL to answer
the user's question, ensuring the
call is optimized to include only necessary information.
Question: {question}
API URL:
"""
api_url_prompt = PromptTemplate(input_variables=('api_docs', 'question'),
template=api_url_template)

api_response_template = """"
With the API Documentation for Scoopsie's official API: {api_docs}
and the specific user question: {question} in mind,
and given this API URL: {api_url} for querying, here is the
response from Scoopsie's API: {api_response}.
Please provide a summary that directly addresses the user's question,
omitting technical details like response format, and
focusing on delivering the answer with clarity and conciseness,
as if Scoopsie itself is providing this information.
Summary:
"""
api_response_prompt = PromptTemplate(input_variables=('api_docs',
'question',
'api_url',
'api_response'),
template=api_response_template)

Ecco, il api_url_prompt genera l'URL API esatto per le query utilizzando la documentazione API fornita (api_docs). Dopo aver identificato l'endpoint corretto con api_url_promptl'APIChain utilizza il file api_response_prompt per riepilogare la risposta dell'API alla query dell'utente. Importiamo questi prompt nel nostro chatbot.py sceneggiatura:

from prompts import api_response_prompt, api_url_prompt

Passaggio 4

Impostiamo APIChain per connetterci con l'API della nostra gelateria immaginaria creata in precedenza. Il modulo APIChain di LangChain fornisce il file from_llm_and_api_docs() metodo, che ci consente di caricare una catena solo da un LLM e dai documenti API definiti in precedenza. Continueremo a utilizzare il gpt-3.5-turbo-instruct modello da OpenAI per il nostro LLM.

# Initialize your LLM
llm = OpenAI(model='gpt-3.5-turbo-instruct',
temperature=0)

api_chain = APIChain.from_llm_and_api_docs(
llm=llm,
api_docs=scoopsie_api_docs,
api_url_prompt=api_url_prompt,
api_response_prompt=api_response_prompt,
verbose=True,
limit_to_domains=("<http://127.0.0.1:5000/>")
)

Il parametro limit_to_domains nel codice sopra limita i domini a cui è possibile accedere da APIChain. Secondo la documentazione ufficiale di LangChain, il valore predefinito è una tupla vuota. Ciò significa che per impostazione predefinita non sono consentiti domini. In base alla progettazione, ciò genererà un errore durante l'istanziazione. Puoi passare None se desideri consentire tutti i domini per impostazione predefinita. Tuttavia, ciò non è consigliato per motivi di sicurezza, poiché consentirebbe agli utenti malintenzionati di effettuare richieste a URL arbitrari, comprese le API interne accessibili dal server. Per consentire l'API del nostro negozio, possiamo specificare il suo URL; ciò garantirebbe che la nostra catena operi in un ambiente controllato.

Passaggio 5

Nei tutorial precedenti abbiamo configurato una LLMChain per gestire query generali relative al gelato. Vorremmo comunque mantenere questa funzionalità, poiché Scoopsie è un utile compagno di conversazione, incorporando anche l'accesso al menu del nostro negozio immaginario e alle opzioni di personalizzazione tramite APIChain. Per combinare queste funzionalità, utilizzeremo il file llm_chain per domande generali e api_chain per accedere all'API del negozio. Ciò richiede la modifica della nostra configurazione di Chainlit per supportare più catene dall'inizio di una sessione utente. Ecco come possiamo adattare il @cl.on_chat_start decoratore:

@cl.on_chat_start
def setup_multiple_chains():
llm = OpenAI(model='gpt-3.5-turbo-instruct',
temperature=0)
conversation_memory = ConversationBufferMemory(memory_key="chat_history",
max_len=200,
return_messages=True,
)
llm_chain = LLMChain(llm=llm, prompt=ice_cream_assistant_prompt,
memory=conversation_memory)
cl.user_session.set("llm_chain", llm_chain)

api_chain = APIChain.from_llm_and_api_docs(
llm=llm,
api_docs=scoopsie_api_docs,
api_url_prompt=api_url_prompt,
api_response_prompt=api_response_prompt,
verbose=True,
limit_to_domains=("<http://127.0.0.1:5000/>")
)
cl.user_session.set("api_chain", api_chain)

All'avvio di una nuova sessione utente, questa configurazione crea un'istanza di entrambi llm_chain E api_chaingarantendo Scoopsie è attrezzato per gestire un'ampia gamma di query. Ogni catena viene archiviata nella sessione utente per un facile recupero. Per informazioni sulla configurazione di llm_chainpuoi vedere il mio articolo precedente.

Passaggio 6

Definiamo ora la funzione wrapper attorno al file @cl.on_message decoratore:

@cl.on_message
async def handle_message(message: cl.Message):
user_message = message.content.lower()
llm_chain = cl.user_session.get("llm_chain")
api_chain = cl.user_session.get("api_chain")

if any(keyword in user_message for keyword in ("menu", "customization",
"offer", "review")):
# If any of the keywords are in the user_message, use api_chain
response = await api_chain.acall(user_message,
callbacks=(cl.AsyncLangchainCallbackHandler()))
else:
# Default to llm_chain for handling general queries
response = await llm_chain.acall(user_message,
callbacks=(cl.AsyncLangchainCallbackHandler()))
response_key = "output" if "output" in response else "text"
await cl.Message(response.get(response_key, "")).send()

In questa configurazione, recuperiamo sia il file llm_chain E api_chain oggetti. Se il messaggio dell'utente include una parola chiave che riflette un endpoint dell'API del nostro negozio immaginario, l'applicazione attiverà APIChain. In caso contrario, presupponiamo che si tratti di una query generale relativa al gelato e attiviamo LLMChain. Questo è un caso d'uso semplice, ma per casi d'uso più complessi potrebbe essere necessario scrivere una logica più elaborata per garantire che venga attivata la catena corretta. Per ulteriori dettagli sui decoratori Chainlit e su come utilizzarli efficacemente, fare riferimento al mio articolo precedente dove approfondisco ampiamente questi argomenti.

Passaggio 7

Ora che il codice della nostra applicazione è pronto, possiamo avviare il nostro chatbot. Apri un terminale nella directory del tuo progetto ed esegui il seguente comando:

chainlit run chatbot.py -w --port 8000

Puoi accedere al chatbot navigando su http://localhost:8000 nel tuo browser web.

Scoopsie interfaccia dell'applicazione ora è pronto! Ecco una demo che mostra il chatbot in azione:

Demo di Scoopsie Chatbot: assistente interattivo per il gelato in azione – per autore

Abbiamo creato con successo un'API per una gelateria immaginaria e l'abbiamo integrata con il nostro chatbot. Come dimostrato sopra, puoi accedere all'applicazione web del tuo chatbot utilizzando Chainlit, dove è possibile accedere sia alle query generali che agli endpoint API del negozio fittizio.

Puoi trovare il codice per questo tutorial in questo GitHub pronti contro termine. IL Punto di controllo GitHub per questo tutorial conterrà tutto il codice sviluppato fino a questo punto.

Puoi seguimi mentre condivido demo funzionanti, spiegazioni e interessanti progetti collaterali su cose nello spazio dell'intelligenza artificiale. Vieni a salutarci LinkedIn E X! 👋

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *