Tutorial zum Erzwingen der JSON-Ausgabe mit Llama.cpp oder der Gemini-API

Foto von Etienne Girardet An Unsplash

Giant Language Fashions (LLMs) sind großartig, wenn es darum geht, Textual content zu generieren, aber um strukturierte Ausgabe wie JSON zu erhalten, muss man normalerweise geschickt vorgehen und hoffen, dass das LLM es versteht. Glücklicherweise JSON-Modus wird in LLM-Frameworks und -Diensten immer häufiger verwendet. Damit können Sie das gewünschte Ausgabeschema genau definieren.

Dieser Beitrag befasst sich mit der eingeschränkten Generierung im JSON-Modus. Wir verwenden ein komplexes, verschachteltes und realistisches JSON-Schemabeispiel, um LLM-Frameworks/APIs wie Llama.cpp oder Gemini API bei der Generierung strukturierter Daten, insbesondere touristischer Standortinformationen, anzuleiten. Dies baut auf einem früheren Beitrag über die eingeschränkte Generierung mit Anleitungkonzentriert sich aber auf den weiter verbreiteten JSON-Modus.

Zwar eingeschränkter als Anleitungdie breitere Unterstützung des JSON-Modus macht ihn zugänglicher, insbesondere bei Cloud-basierten LLM-Anbietern.

Während eines persönlichen Projekts habe ich festgestellt, dass der JSON-Modus mit Llama.cpp zwar unkompliziert struggle, aber einige zusätzliche Schritte erforderlich waren, um ihn mit der Gemini-API zum Laufen zu bringen. In diesem Beitrag werden diese Lösungen vorgestellt, damit Sie den JSON-Modus effektiv nutzen können.

Unser Beispielschema stellt ein TouristLocation. Es ist eine nicht triviale Struktur mit verschachtelten Objekten, Pay attention, Enumerationen und verschiedenen Datentypen wie Zeichenfolgen und Zahlen.

Hier ist eine vereinfachte Model:

{
"identify": "string",
"location_long_lat": ("quantity", "quantity"),
"climate_type": {"kind": "string", "enum": ("tropical", "desert", "temperate", "continental", "polar")},
"activity_types": ("string"),
"attraction_list": (
{
"identify": "string",
"description": "string"
}
),
"tags": ("string"),
"description": "string",
"most_notably_known_for": "string",
"location_type": {"kind": "string", "enum": ("metropolis", "nation", "institution", "landmark", "nationwide park", "island", "area", "continent")},
"mother and father": ("string")
}

Sie können dieses Schema von Hand schreiben oder mithilfe der Pydantic-Bibliothek generieren. Hier sehen Sie anhand eines vereinfachten Beispiels, wie Sie es machen können:

from typing import Record
from pydantic import BaseModel, Area

class TouristLocation(BaseModel):
"""Mannequin for a vacationer location"""

high_season_months: Record(int) = Area(
(), description="Record of months (1-12) when the situation is most visited"
)

tags: Record(str) = Area(
...,
description="Record of tags describing the situation (e.g. accessible, sustainable, sunny, low cost, expensive)",
min_length=1,
)
description: str = Area(..., description="Textual content description of the situation")

# Instance utilization and schema output
location = TouristLocation(
high_season_months=(6, 7, 8),
tags=("seaside", "sunny", "family-friendly"),
description="A stupendous seaside with white sand and clear blue water.",
)

schema = location.model_json_schema()
print(schema)

Dieser Code definiert eine vereinfachte Model von TouristLocation Datenklasse mit Pydantic. Sie hat drei Felder:

  • high_season_months: Eine Liste von Ganzzahlen, die die Monate des Jahres (1-12) darstellen, in denen der Ort am häufigsten besucht wird. Standardmäßig ist die Liste leer.
  • tags: Eine Liste von Zeichenfolgen, die den Standort mit Tags wie „zugänglich“, „nachhaltig“ usw. beschreiben. Dieses Feld ist erforderlich (...) und muss mindestens ein Component (min_length=1).
  • description: Ein Zeichenfolgenfeld mit einer Textbeschreibung des Standorts. Dieses Feld ist ebenfalls erforderlich.

Der Code erstellt dann eine Instanz des TouristLocation Klasse und Verwendungen model_json_schema() um die JSON-Schemadarstellung des Modells zu erhalten. Dieses Schema definiert die Struktur und Typen der für diese Klasse erwarteten Daten.

model_json_schema() Rückgabewerte:

{'description': 'Mannequin for a vacationer location',
'properties': {'description': {'description': 'Textual content description of the '
'location',
'title': 'Description',
'kind': 'string'},
'high_season_months': {'default': (),
'description': 'Record of months (1-12) '
'when the situation is '
'most visited',
'gadgets': {'kind': 'integer'},
'title': 'Excessive Season Months',
'kind': 'array'},
'tags': {'description': 'Record of tags describing the situation '
'(e.g. accessible, sustainable, sunny, '
'low cost, expensive)',
'gadgets': {'kind': 'string'},
'minItems': 1,
'title': 'Tags',
'kind': 'array'}},
'required': ('tags', 'description'),
'title': 'TouristLocation',
'kind': 'object'}

Nachdem wir nun unser Schema haben, schauen wir, wie wir es durchsetzen können. Zuerst in Llama.cpp mit seinem Python-Wrapper und dann mit der API von Gemini.

Llama.cpp, eine C++-Bibliothek zum lokalen Ausführen von Llama-Modellen. Sie ist anfängerfreundlich und hat eine aktive Group. Wir werden sie über ihren Python-Wrapper verwenden.

So generieren Sie TouristLocation Daten damit:

# Imports and stuff

# Mannequin init:
checkpoint = "lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF"

mannequin = Llama.from_pretrained(
repo_id=checkpoint,
n_gpu_layers=-1,
filename="*Q4_K_M.gguf",
verbose=False,
n_ctx=12_000,
)

messages = (
{
"position": "system",
"content material": "You're a useful assistant that outputs in JSON."
f"Observe this schema {TouristLocation.model_json_schema()}",
},
{"position": "person", "content material": "Generate details about Hawaii, US."},
{"position": "assistant", "content material": f"{location.model_dump_json()}"},
{"position": "person", "content material": "Generate details about Casablanca"},
)
response_format = {
"kind": "json_object",
"schema": TouristLocation.model_json_schema(),
}

begin = time.time()

outputs = mannequin.create_chat_completion(
messages=messages, max_tokens=1200, response_format=response_format
)

print(outputs("selections")(0)("message")("content material"))

print(f"Time: {time.time() - begin}")

Der Code importiert zunächst die erforderlichen Bibliotheken und initialisiert das LLM-Modell. Anschließend definiert er eine Liste von Nachrichten für eine Konversation mit dem Modell, darunter eine Systemnachricht, die das Modell anweist, gemäß einem bestimmten Schema im JSON-Format auszugeben, Benutzeranfragen nach Informationen über Hawaii und Casablanca und eine Assistentenantwort unter Verwendung des angegebenen Schemas.

Llama.cpp verwendet im Hintergrund kontextfreie Grammatiken, um die Struktur einzuschränken und gültige JSON-Ausgabe für eine neue Stadt zu generieren.

In der Ausgabe erhalten wir den folgenden generierten String:

{'activity_types': ('buying', 'meals and wine', 'cultural'),
'attraction_list': ({'description': 'One of many largest mosques on the planet '
'and a logo of Moroccan structure',
'identify': 'Hassan II Mosque'},
{'description': 'A historic walled metropolis with slim '
'streets and conventional outlets',
'identify': 'Previous Medina'},
{'description': 'A historic sq. with a phenomenal '
'fountain and surrounding buildings',
'identify': 'Mohammed V Sq.'},
{'description': 'A stupendous Catholic cathedral in-built '
'the early twentieth century',
'identify': 'Casablanca Cathedral'},
{'description': 'A scenic waterfront promenade with '
'lovely views of town and the ocean',
'identify': 'Corniche'}),
'climate_type': 'temperate',
'description': 'A big and bustling metropolis with a wealthy historical past and tradition',
'location_type': 'metropolis',
'most_notably_known_for': 'Its historic structure and cultural '
'significance',
'identify': 'Casablanca',
'mother and father': ('Morocco', 'Africa'),
'tags': ('metropolis', 'cultural', 'historic', 'costly')}

Dieses kann dann in eine Instanz unserer Pydantic-Klasse analysiert werden.

Gemini API, der von Google verwaltete LLM-Dienst, behauptet in seiner Dokumentation, dass der JSON-Modus für Gemini Flash 1.5 nur eingeschränkt unterstützt wird. Mit ein paar Anpassungen lässt sich dies jedoch zum Laufen bringen.

Hier sind die allgemeinen Anweisungen, damit es funktioniert:

schema = TouristLocation.model_json_schema()
schema = replace_value_in_dict(schema.copy(), schema.copy())
del schema("$defs")
delete_keys_recursive(schema, key_to_delete="title")
delete_keys_recursive(schema, key_to_delete="location_long_lat")
delete_keys_recursive(schema, key_to_delete="default")
delete_keys_recursive(schema, key_to_delete="default")
delete_keys_recursive(schema, key_to_delete="minItems")

print(schema)

messages = (
ContentDict(
position="person",
elements=(
"You're a useful assistant that outputs in JSON."
f"Observe this schema {TouristLocation.model_json_schema()}"
),
),
ContentDict(position="person", elements=("Generate details about Hawaii, US.")),
ContentDict(position="mannequin", elements=(f"{location.model_dump_json()}")),
ContentDict(position="person", elements=("Generate details about Casablanca")),
)

genai.configure(api_key=os.environ("GOOGLE_API_KEY"))

# Utilizing `response_mime_type` with `response_schema` requires a Gemini 1.5 Professional mannequin
mannequin = genai.GenerativeModel(
"gemini-1.5-flash",
# Set the `response_mime_type` to output JSON
# Move the schema object to the `response_schema` subject
generation_config={
"response_mime_type": "software/json",
"response_schema": schema,
},
)

response = mannequin.generate_content(messages)
print(response.textual content)

So überwinden Sie die Einschränkungen von Gemini:

  1. Ersetzen $ref mit vollständigen Definitionen: Gemini stolpert über Schemareferenzen ($ref). Diese werden verwendet, wenn Sie eine verschachtelte Objektdefinition haben. Ersetzen Sie sie durch die vollständige Definition aus Ihrem Schema.
def replace_value_in_dict(merchandise, original_schema):
# Supply: https://github.com/pydantic/pydantic/points/889
if isinstance(merchandise, checklist):
return (replace_value_in_dict(i, original_schema) for i in merchandise)
elif isinstance(merchandise, dict):
if checklist(merchandise.keys()) == ("$ref"):
definitions = merchandise("$ref")(2:).break up("/")
res = original_schema.copy()
for definition in definitions:
res = res(definition)
return res
else:
return {
key: replace_value_in_dict(i, original_schema)
for key, i in merchandise.gadgets()
}
else:
return merchandise
  1. Nicht unterstützte Schlüssel entfernen: Gemini verarbeitet noch keine Schlüssel wie „title“, „AnyOf“ oder „minItems“. Entfernen Sie diese aus Ihrem Schema. Dies hat zur Folge, dass das Schema weniger lesbar und weniger restriktiv ist, aber wir haben keine andere Wahl, wenn wir auf der Verwendung von Gemini bestehen.
def delete_keys_recursive(d, key_to_delete):
if isinstance(d, dict):
# Delete the important thing if it exists
if key_to_delete in d:
del d(key_to_delete)
# Recursively course of all gadgets within the dictionary
for ok, v in d.gadgets():
delete_keys_recursive(v, key_to_delete)
elif isinstance(d, checklist):
# Recursively course of all gadgets within the checklist
for merchandise in d:
delete_keys_recursive(merchandise, key_to_delete)
  1. One-Shot- oder Few-Shot-Eingabeaufforderung für Enums: Gemini hat manchmal Probleme mit Enums und gibt alle möglichen Werte aus, anstatt einer einzigen Auswahl. Die Werte werden außerdem durch „|” in einer einzigen Saite, wodurch sie ungültig gemäß unserem Schema. Verwenden Sie einmalige Eingabeaufforderungen und geben Sie ein korrekt formatiertes Beispiel an, um das gewünschte Verhalten zu erreichen.

Indem Sie diese Transformationen anwenden und klare Beispiele angeben, können Sie mit der Gemini-API erfolgreich strukturierte JSON-Ausgaben generieren.

Im JSON-Modus können Sie strukturierte Daten direkt aus Ihren LLMs abrufen, wodurch diese für praktische Anwendungen nützlicher werden. Während Frameworks wie Llama.cpp unkomplizierte Implementierungen bieten, können bei Cloud-Diensten wie Gemini API Probleme auftreten.

Hoffentlich hat Ihnen dieser Weblog ein besseres praktisches Verständnis dafür vermittelt, wie der JSON-Modus funktioniert und wie Sie ihn auch bei Verwendung der API von Gemini nutzen können, die bisher nur teilweise unterstützt wird.

Nachdem ich Gemini nun einigermaßen im JSON-Modus zum Laufen bringen konnte, kann ich die Implementierung meines LLM-Workflows abschließen, bei dem eine bestimmte Datenstrukturierung erforderlich ist.

Den Hauptcode dieses Beitrags finden Sie hier: https://gist.github.com/CVxTz/8eace07d9bd2c5123a89bf790b5cc39e

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert