Consejo 2: utilice resultados estructurados
Usar resultados estructurados significa obligar al LLM a generar texto JSON o YAML válido. Esto le permitirá reducir las divagaciones inútiles y obtener respuestas “directas” sobre lo que necesita del LLM. También ayudará con los siguientes consejos, ya que facilita la verificación de las respuestas del LLM.
Así es como puedes hacer esto con la API de Gemini:
import jsonimport google.generativeai as genai
from pydantic import BaseModel, Field
from document_ai_agents.schema_utils import prepare_schema_for_gemini
class Answer(BaseModel):
answer: str = Field(..., description="Your Answer.")
model = genai.GenerativeModel("gemini-1.5-flash-002")
answer_schema = prepare_schema_for_gemini(Answer)
question = "List all the reasons why LLM hallucinate"
context = (
"LLM hallucination refers to the phenomenon where large language models generate plausible-sounding but"
" factually incorrect or nonsensical information. This can occur due to various factors, including biases"
" in the training data, the inherent limitations of the model's understanding of the real world, and the "
"model's tendency to prioritize fluency and coherence over accuracy."
)
messages = (
[context]
+ [
f"Answer this question: {question}",
]
+ [
f"Use this schema for your answer: {answer_schema}",
]
)
response = model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": answer_schema,
"temperature": 0.0,
},
)
response = Answer(**json.loads(response.text))
print(f"{response.answer=}")
Donde “prepare_schema_for_gemini” es una función de utilidad que prepara el esquema para que coincida con los extraños requisitos de Gemini. Puedes encontrar su definición aquí: código.
Este código define un esquema Pydantic y envía este esquema como parte de la consulta en el campo “response_schema”. Esto obliga al LLM a seguir este esquema en su respuesta y facilita el análisis de su resultado.
Consejo 3: utilice cadenas de pensamientos y mejores indicaciones
A veces, darle al LLM el espacio para elaborar su respuesta, antes de comprometerse con una respuesta final, puede ayudar a producir respuestas de mejor calidad. Esta técnica se llama Cadena de pensamientos y se utiliza mucho porque es eficaz y muy fácil de implementar.
También podemos pedirle explícitamente al LLM que responda “N/A” si no puede encontrar suficiente contexto para producir una respuesta de calidad. Esto le dará una salida fácil en lugar de intentar responder a preguntas para las que no tiene respuesta.
Por ejemplo, analicemos esta simple pregunta y contexto:
Contexto
Thomas Jefferson (13 de abril [O.S. April 2]1743 – 4 de julio de 1826) fue un estadista, plantador, diplomático, abogado, arquitecto, filósofo y padre fundador estadounidense que sirvió como tercer presidente de los Estados Unidos de 1801 a 1809.[6] Fue el autor principal de la Declaración de Independencia. Después de la Guerra de Independencia de los Estados Unidos y antes de convertirse en presidente en 1801, Jefferson fue el primer secretario de Estado de los Estados Unidos bajo George Washington y luego el segundo vicepresidente de la nación bajo John Adams. Jefferson fue un destacado defensor de la democracia, el republicanismo y los derechos naturales, y produjo documentos y decisiones formativos a nivel estatal, nacional e internacional. (Fuente: Wikipedia)
Pregunta
¿En qué año murió davis jefferson?
Un enfoque ingenuo produce:
Respuesta
respuesta = ‘1826’
Lo cual es obviamente falso ya que Jefferson Davis ni siquiera se menciona en el contexto. Fue Thomas Jefferson quien murió en 1826.
Si cambiamos el esquema de la respuesta para usar cadena de pensamientos para:
class AnswerChainOfThoughts(BaseModel):
rationale: str = Field(
...,
description="Justification of your answer.",
)
answer: str = Field(
..., description="Your Answer. Answer with 'N/A' if answer is not found"
)
También estamos agregando más detalles sobre lo que esperamos como resultado cuando la pregunta no se puede responder usando el contexto “Responda con ‘N/A’ si no se encuentra la respuesta”.
Con este nuevo enfoque, obtenemos lo siguiente razón fundamental (recuerde, cadena de pensamiento):
El texto proporcionado analiza a Thomas Jefferson, no a Jefferson Davis. No se incluye información sobre la muerte de Jefferson Davis.
y el final respuesta:
respuesta = ‘N/A’
Excelente ! Pero, ¿podemos utilizar un enfoque más general para la detección de alucinaciones?
¡Podemos, con Agentes!
Consejo 4: utilice un enfoque agente
Construiremos un agente simple que implemente un proceso de tres pasos:
- El primer paso es incluir el contexto y hacer la pregunta al LLM para obtener la primera respuesta del candidato y el contexto relevante que utilizó para su respuesta.
- El segundo paso es reformular la pregunta y la respuesta del primer candidato como una declaración.
- El tercer paso es pedirle al LLM que verifique si el contexto relevante implica la respuesta del candidato. Se llama “Autoverificación”: https://arxiv.org/pdf/2212.09561
Para implementar esto, definimos tres nodos en LangGraph. El primer nodo hará la pregunta incluyendo el contexto, el segundo nodo la reformulará utilizando el LLM y el tercer nodo verificará la implicación de la declaración en relación con el contexto de entrada.
El primer nodo se puede definir de la siguiente manera:
def answer_question(self, state: DocumentQAState):
logger.info(f"Responding to question '{state.question}'")
assert (
state.pages_as_base64_jpeg_images or state.pages_as_text
), "Input text or images"
messages = (
[
{"mime_type": "image/jpeg", "data": base64_jpeg}
for base64_jpeg in state.pages_as_base64_jpeg_images
]
+ state.pages_as_text
+ [
f"Answer this question: {state.question}",
]
+ [
f"Use this schema for your answer: {self.answer_cot_schema}",
]
)response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.answer_cot_schema,
"temperature": 0.0,
},
)
answer_cot = AnswerChainOfThoughts(**json.loads(response.text))
return {"answer_cot": answer_cot}
Y el segundo como:
def reformulate_answer(self, state: DocumentQAState):
logger.info("Reformulating answer")
if state.answer_cot.answer == "N/A":
returnmessages = [
{
"role": "user",
"parts": [
{
"text": "Reformulate this question and its answer as a single assertion."
},
{"text": f"Question: {state.question}"},
{"text": f"Answer: {state.answer_cot.answer}"},
]
+ [
{
"text": f"Use this schema for your answer: {self.declarative_answer_schema}"
}
],
}
]
response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.declarative_answer_schema,
"temperature": 0.0,
},
)
answer_reformulation = AnswerReformulation(**json.loads(response.text))
return {"answer_reformulation": answer_reformulation}
El tercero como:
def verify_answer(self, state: DocumentQAState):
logger.info(f"Verifying answer '{state.answer_cot.answer}'")
if state.answer_cot.answer == "N/A":
return
messages = [
{
"role": "user",
"parts": [
{
"text": "Analyse the following context and the assertion and decide whether the context "
"entails the assertion or not."
},
{"text": f"Context: {state.answer_cot.relevant_context}"},
{
"text": f"Assertion: {state.answer_reformulation.declarative_answer}"
},
{
"text": f"Use this schema for your answer: {self.verification_cot_schema}. Be Factual."
},
],
}
]response = self.model.generate_content(
messages,
generation_config={
"response_mime_type": "application/json",
"response_schema": self.verification_cot_schema,
"temperature": 0.0,
},
)
verification_cot = VerificationChainOfThoughts(**json.loads(response.text))
return {"verification_cot": verification_cot}
código completo en https://github.com/CVxTz/document_ai_agents
Observe cómo cada nodo utiliza su propio esquema para la salida estructurada y su propio mensaje. Esto es posible gracias a la flexibilidad de la API de Gemini y LangGraph.
Trabajemos en este código usando el mismo ejemplo anterior ➡️
(Nota: no utilizamos la cadena de pensamiento en el primer mensaje para que se active la verificación para nuestras pruebas).
Contexto
Thomas Jefferson (13 de abril [O.S. April 2]1743 – 4 de julio de 1826) fue un estadista, plantador, diplomático, abogado, arquitecto, filósofo y padre fundador estadounidense que sirvió como tercer presidente de los Estados Unidos de 1801 a 1809.[6] Fue el autor principal de la Declaración de Independencia. Después de la Guerra de Independencia de los Estados Unidos y antes de convertirse en presidente en 1801, Jefferson fue el primer secretario de Estado de los Estados Unidos bajo George Washington y luego el segundo vicepresidente de la nación bajo John Adams. Jefferson fue un destacado defensor de la democracia, el republicanismo y los derechos naturales, y produjo documentos y decisiones formativos a nivel estatal, nacional e internacional. (Fuente: Wikipedia)
Pregunta
¿En qué año murió davis jefferson?
Resultado del primer nodo (Primera respuesta):
contexto_relevante=’Thomas Jefferson (13 de abril [O.S. April 2]1743 – 4 de julio de 1826) fue un estadista, plantador, diplomático, abogado, arquitecto, filósofo y padre fundador estadounidense que sirvió como tercer presidente de los Estados Unidos de 1801 a 1809.
respuesta = ‘1826’
Resultado del segundo nodo (Reformulación de respuesta):
respuesta_declarativa=’Davis Jefferson murió en 1826′
Resultado del tercer nodo (Verificación):
razón fundamental=’El contexto indica que Thomas Jefferson murió en 1826. La afirmación afirma que Davis Jefferson murió en 1826. El contexto no menciona a Davis Jefferson, sólo a Thomas Jefferson.’
vinculación=’No’
Entonces el paso de verificación rechazado (No hay vinculación entre los dos.) la respuesta inicial. Ahora podemos evitar devolverle una alucinación al usuario.
Consejo adicional: utilice modelos más fuertes
Este consejo no siempre es fácil de aplicar debido a limitaciones de presupuesto o latencia, pero debes saber que los LLM más fuertes son menos propensos a sufrir alucinaciones. Entonces, si es posible, opte por un LLM más potente para sus casos de uso más delicados. Puedes consultar un benchmark de alucinaciones aquí: https://github.com/vectara/hallucination-leaderboard. Podemos ver que los mejores modelos en este punto de referencia (menos alucinaciones) también se ubican en la cima de las tablas de clasificación de PNL convencional.
En este tutorial, exploramos estrategias para mejorar la confiabilidad de los resultados de LLM al reducir la tasa de alucinaciones. Las principales recomendaciones incluyen un formato cuidadoso y indicaciones para guiar las llamadas de LLM y el uso de un enfoque basado en el flujo de trabajo en el que los agentes están diseñados para verificar sus propias respuestas.
Esto implica varios pasos:
- Recuperar los elementos de contexto exactos utilizados por el LLM para generar la respuesta.
- Reformular la respuesta para una verificación más fácil (En forma declarativa).
- Instruir al LLM para que verifique la coherencia entre el contexto y la respuesta reformulada.
Si bien todos estos consejos pueden mejorar significativamente la precisión, debes recordar que ningún método es infalible. Siempre existe el riesgo de rechazar respuestas válidas si el LLM es demasiado conservador durante la verificación o pasa por alto casos reales de alucinaciones. Por lo tanto, una evaluación rigurosa de los flujos de trabajo específicos de su LLM sigue siendo esencial.
código completo en https://github.com/CVxTz/document_ai_agents