Comment atteindre une sortie structurée dans Claude 3.7: trois approches pratiques

 Comment atteindre une sortie structurée dans Claude 3.7: trois approches pratiques


Auteur (s): Omri Eliyahu Levy

Publié à l’origine sur Vers l’IA.

Comment atteindre une sortie structurée dans Claude 3.7: trois approches pratiques

Tl; dr – Le code complet peut être trouvé ici

À Baznous construisons un agent d’examen du code d’IA qui combine une analyse statique avec LLMS pour raisonner profondément sur le code. En cours de route, la sortie structurée est devenue essentielle, en particulier lorsque vous chaîniez les sorties du modèle ou s’intégrant dans des systèmes plus grands. Dans le cadre de ce voyage, nous évaluons et intégrons constamment de nouveaux modèles pour voir comment ils fonctionnent dans des pipelines complexes et réels.

Anthropic récemment sorti Claude Sonnet 3.7 a attiré beaucoup d’attention pour son raisonnement amélioré et sa compréhension du code. Mais si vous construisez des applications Genai à l’état sauvage, il y a une prise: la sortie structurée ne fonctionne pas comme vous pouvez vous attendre lors de l’utilisation de son puissant mode «pensée étendue».

La sortie structurée est essentielle en matière de production LLM systèmes – La capacité d’obtenir des réponses cohérentes et analysables qui adhèrent à un schéma défini. Que vous achetiez la sortie LLM vers des systèmes en aval ou que vous construisiez des workflows d’agent modulaires, vous avez besoin de plus que du texte de forme libre – vous avez besoin de structure sur laquelle vous pouvez compter.

Ce post parcourt la sortie structurée, pourquoi elle est importante en production, et trois façons pratiques de l’atteindre avec Sonnet 3.7. Nous incluons des exemples de bout en bout en utilisant Lubriole et Aws Base Pour aider les autres à expédier les applications Genai à naviguer dans ce compromis.

Photo de Tamilazhagan sur Désactiver

Quelle «sortie structurée» est?

La sortie structurée fait référence à une réponse LLM qui adhère à un schéma prédéfini. Au lieu de recevoir la réponse LLM en tant que texte libre, il est renvoyé dans un schéma JSON qui est facile à analyser et à travailler avec. Pour les environnements de production, tirer parti des réponses structurées JSON est cruciale car c’est ainsi que les systèmes interagissent aujourd’hui.

Comment fonctionne la sortie structurée?

D’une manière générale, il existe trois façons différentes d’atteindre la sortie structurée:

  1. Demandez au modèle d’adhérer à un schéma prédéfini.
  2. Utilisez la génération contrainte.
  3. Utiliser la fonction de fonction (outil).

Discutons chacun.

Inciter le modèle à le faire

C’est l’approche la plus simple mais aussi la moins fiable. Vous demandez simplement au modèle de retourner sa réponse dans un format spécifique, comme JSON. Vous pouvez bien sûr ajouter quelques exemples à quelques coups, puis essayer d’analyser la réponse directement dans JSON.

L’inconvénient ici est que le modèle pourrait ne pas suivre parfaitement vos instructions, ou le schéma peut être trop complexe, nécessitant une analyse supplémentaire ou une gestion des erreurs.

Décodage contraint

Aussi parfois appelé «mode JSON». Dans cette approche, il y a une restriction sur les jetons que le modèle est autorisé à générer à un certain moment. C’est-à-dire, étant donné le schéma de sortie souhaité, il est traduit en un grammaire sans contexte Cela représente le schéma (1). Ensuite, lors de la génération du jeton n’th, le modèle ne choisira que parmi les jetons qu’il est autorisé à produire en fonction de la grammaire.

Par exemple, si le modèle a déjà produit:

{“key”: “val

Il ne sera pas autorisé à générer le } jeton car il doit fermer les citations " sur val(ou générer plus de «lettres») afin d’être un JSON valide.

Cette approche est excellente, bien que certaines recherches montrent qu’elle peut nuire aux performances et à la qualité du modèle (2). De plus, Sonnet 3.7 d’Anthropics ne prend pas en charge cette option.

Fonction / appels d’outil

Dans cette approche, les modèles sont affinés pour générer des sorties structurées qui spécifient la fonction à appeler et avec quels arguments. L’appel des fonctions est appris par un réglage fin, permettant au modèle de décider quand et comment invoquer des fonctions externes en fonction du contexte de la conversation.

Par exemple, nous pouvons informer le modèle qu’il a un web_search fonction disponible, qui prend un querycomme entrée, qui devrait être une chaîne valide. Le modèle devrait donc produire un appel de fonction valide (utilisation de l’outil) du schéma suivant:

{"function": "web_search", "arguments": {"query": "when did Claude Sonnet 3.7 come out?" }

Ceci est le mode pris en charge par les modèles d’Anthropics!

Maintenant, si vous y pensez, si un modèle sait produire des appels de fonction de haute qualité en adhérant à son schéma, il peut en fait être utilisé pour produire n’importe quel schéma. En effet, c’est ce que l’anthropique recommande (4):

« Les outils n’ont pas nécessairement besoin d’être des fonctions côté client – vous pouvez utiliser des outils chaque fois que vous souhaitez que le modèle renvoie la sortie JSON qui suit un schéma fourni. « 

Soit dit en passant, si vous êtes un utilisateur de Langchain, lorsque vous fournissez une sortie structurée pour les modèles anthropes, il est «traduit» dans les coulisses en «appels de fonction».

Claude Sonnet 3.7

Sonnet 3.7 est le dernier modèle d’Anthropic qui comprend des capacités de raisonnement améliorées. En fait, on peut choisir de fonctionner avec Sonnet 3.7 dans l’un des deux modes: avec ou sans réflexion étendue. Selon Anthropic (5), sans penser, il agira comme une version améliorée de 3.5 que nous connaissons et aimons, et avec la réflexion, il y aura réfléchir et réfléchir (jusqu’à un seuil de jetons que vous pouvez définir) et il suffit de répondre. Cette approche semble se révéler elle-même car elle marque très haut sur la référence Aider (6).

Ok… quelle est la prise?

Lorsque la réflexion étendue est activée – Sonnet 3.7 ne prend pas en charge certaines fonctionnalités auxquelles nous étions habitués, en particulier les appels à outils forcés. Ou en d’autres termes (rappelez-vous notre intro), il ne prend pas en charge la sortie structurée.

En effet, et citant de l’emballage de Langchain à Sourcecode des anthropiques (7)

« La sortie structurée anthropique repose sur l’appel d’outils forcé, qui n’est pas pris en charge lorsque «Thinking» est activé».

Pour être précis, comme l’état des documents anthropiques, en plus de «l’utilisation d’outils forcés», la température, Top_P ou Top_K ne sont pas prises en charge.

Que pouvons-nous faire?

Je vais présenter 3 façons différentes d’atteindre la sortie structurée avec Sonnet 3.7.

Afin d’explorer différentes manières, j’utiliserai Langchain avec des modèles anthropes via le substratum rocheux AWS – mais cela fonctionnera de la même manière avec anthropique que le fournisseur.

Supposons que nous voulons que le modèle puisse créer une nouvelle sur un sujet fourni et déterminer également son genre.

from typing import Any
from dotenv import load_dotenv
from langchain_aws import ChatBedrockConverse
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from pydantic import BaseModel, Field

load_dotenv('.env')

class Story(BaseModel):
"""A short story with its genre"""
content: str = Field(description='A very short story')
genre: str = Field(description='The genre of the story')

Mode «sans réflexion»

Dans cette approche, nous allons pas Utilisez le mode de réflexion étendu de Sonnet 3.7. Sans réfléchir, nous pouvons obtenir des sorties structurées fiables via l’appel d’outils.

def no_thinking_mode() -> Story:
"""
Example of structured output without extended thinking mode.
This approach disables Claude's extended thinking capabilities but allows
for direct structured output via forced tool calling.
"""

prompt = PromptTemplate.from_template('Create a very short story about: {topic} and determine its genre')

llm = ChatBedrockConverse(
model_id='us.anthropic.claude-3-7-sonnet-20250219-v1:0',
region_name='us-east-2',
additional_model_request_fields={'thinking': {'type': 'disabled'}},
)
structured_llm = llm.with_structured_output(Story)
chain = prompt | structured_llm

res = chain.invoke({'topic': 'Harry Potter'})
assert isinstance(res, Story)
return resp

N’oubliez pas que Langchain fait ici le «travail» pour nous et «traduit» en un appel de fonction avec le schéma souhaité.

Mode « j’espère structuré »

Dans cette approche, nous utiliserons le mode de réflexion et «demander bien» la sortie structurée.

Cette approche exploite le raisonnement amélioré mais repose sur une incitation minutieuse à obtenir des résultats structurés. Ensuite, essayez de l’analyser dans le schéma requis.

Dans l’exemple suivant, Langchain le fera pour nous, mais augmentera OutputParserExceptionSi cela ne le fait pas.

def hopefully_structured_mode() -> Story:
"""
Example of attempting structured output with extended thinking enabled.
It'll not use forced tool calling and will try to parse the response into the provided schema.
Will raise `OutputParserException` if it fails.
"""

prompt = PromptTemplate.from_template(
"""Create a very short story about: {topic} and determine its genre.
IMPORTANT: Your response must be formatted as valid JSON with two fields:
1. content: That is, the story content
2. genre: The genre of the story
"""

)
llm = ChatBedrockConverse(
model_id='us.anthropic.claude-3-7-sonnet-20250219-v1:0',
region_name='us-east-2',
additional_model_request_fields={'thinking': {'type': 'enabled', 'budget_tokens': 2000}},
)
structured_llm = llm.with_structured_output(Story) # will try to parse the result according to the provided schema
chain = prompt | structured_llm

res = chain.invoke({'topic': 'Harry Potter'})
assert isinstance(res, Story)
return res

Mode «Raison et structure»

Dans cette approche, nous avons laissé Sonnet 3.7 faire ce qu’il est bon, raisonnement, puis utiliser un autre LLM (disons Haïku) pour structurer sa sortie. Ce processus en deux étapes vous donne le meilleur des deux mondes mais au prix d’une complexité et d’une latence supplémentaires.

def reason_and_structure_mode(inputs: dict(str, Any) = None) -> Story:
"""
Example of a two-stage approach: reasoning with Sonnet-3.7 followed by structuring with Haiku.
This approach leverages Sonnet's extended thinking for content generation, then
uses Haiku to transform the output into a structured format.
"""

reasoning_prompt = PromptTemplate.from_template('Create a very short story about: {topic}')
reasoning_llm = ChatBedrockConverse(
model_id='us.anthropic.claude-3-7-sonnet-20250219-v1:0',
region_name='us-east-2',
additional_model_request_fields={'thinking': {'type': 'enabled', 'budget_tokens': 2000}},
)
reasoning_chain = reasoning_prompt | reasoning_llm

structuring_prompt = PromptTemplate.from_template(
'Structure the provided story into the requested schema and assign "genre" to be {genre}. Story: {reasoning_output}'
)
structuring_llm = ChatBedrockConverse(
model_id='us.anthropic.claude-3-5-haiku-20241022-v1:0',
region_name='us-east-2',
)
structuring_llm = structuring_llm.with_structured_output(Story)
structuring_chain = structuring_prompt | structuring_llm

# Sometimes, we'll want to pass some of the inputs params directly to the "structuring model", not only the output of the reasoning model.
# In order to support that, we'll create a "dummy" function, that just gets the inputs and returns them.
# Then, we can run both the reasoning chain and the dummy function in parallel, and feed the structuring llm both:
#
# /-> reasoning_chain -> reasoning_output
# input_params -> merge_inputs -> structuring_llm
# -> dummy_function -> original_params /
reason_then_structure_chain = (
RunnableParallel(
reasoning_output=reasoning_chain,
original_inputs=RunnablePassthrough(),
)
| RunnableLambda(lambda x: prepare_structuring_inputs(x('original_inputs'), x('reasoning_output')))
| structuring_chain
)

inputs = {'topic': 'Harry Potter', 'genre': 'fantasy'}
res = reason_then_structure_chain.invoke(inputs)
assert isinstance(res, Story)
return res

def prepare_structuring_inputs(original_inputs: dict(str, Any), reasoning_output: str) -> dict(str, Any):
"""
Prepares inputs for the structuring model by combining original inputs with reasoning output.
"""

return {
**original_inputs, # Pass original inputs as-is
'reasoning_output': reasoning_output, # Add reasoning chain output
}

Le code complet peut être trouvé ici.

En résumé, bien que Sonnet 3.7 offre de solides capacités de raisonnement, la production de production structurée fiable en production peut être délicate, en particulier avec le mode de réflexion étendu. En utilisant des approches comme le mode de réflexion, une incitation minutieuse ou une combinaison de raisonnement avec un modèle de structuration distinct, vous pouvez efficacement contourner ces limitations et intégrer des sorties structurées dans vos applications Genai. Ces méthodes fournissent un moyen pratique d’assurer une intégration transparente dans les pipelines du monde réel.

Références

(1) https://openai.com/index/introducing-structure-sorts-in-the-api/
(2) Permettez-moi de parler librement? Une étude sur l’impact des restrictions de format sur les performances des modèles de grands langues: https://arxiv.org/abs/2408.02442
(3) https://huggingface.co/learn/agents-course/bonus-unit1/what-is-function-calling
(4) https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview#json-utput
(5) https://www.anthropic.com/news/claude-3-7-sonnet
(6) https://aider.chat/docs/leaderboards/
(7)https://github.com/langchain-ai/langchain/blob/47ded80b64fa8bd5d3d2f8cab0fe17fd6668 9019 / libs / partenaires / anthropic / langchain_anthropic / chat_models.py # l1187

Publié via Vers l’IA



Source link

Related post