Transformers documentation
Parsing Respons
Parsing Respons
Semakin umum model chat menghasilkan output terstruktur, bukan sekadar satu string balasan. Misalnya, model penalaran dapat mengeluarkan rantai pemikiran yang berisi jejak penalarannya, sementara model tool calling dapat mengeluarkan nama fungsi dan argumennya.
Masalah dengan output terstruktur adalah bahwa output LLM tidak secara inheren terstruktur. API LLM biasanya menerima dan mengembalikan dict pesan, dengan kunci seperti role dan content dan reasoning_content, tetapi secara internal, LLM sebenarnya hanya melanjutkan satu rangkaian token. Kami menggunakan lapisan penghubung untuk menghubungkan API yang berhadapan dengan pengguna ke aliran token aktual dari model. Untuk mengubah input menjadi aliran token, kami menggunakan chat_templates, yang dibahas dalam dokumen lain. Dokumen ini membahas bagian lain dari lapisan penghubung tersebut: Response templates, yaitu sistem untuk mengubah kembali token yang dihasilkan oleh model menjadi dict respons terstruktur.
Dalam banyak hal, response templates melakukan operasi kebalikan dari chat templates. Dengan chat templates, Anda memasukkan daftar pesan, dan Anda mendapatkan token yang siap diinput ke model. Dengan response templates, Anda memasukkan token output mentah dari model, dan Anda mendapatkan pesan terstruktur. Seperti chat templates, response templates memungkinkan pengguna untuk mengabaikan detail rumit mengenai format spesifik dan token kontrol yang diharapkan oleh model, serta menggunakan API universal berupa dict pesan yang berfungsi dengan model apa pun.
Cara terbaik untuk memahami template respons adalah dengan melihatnya dalam praktik. Titik masuk utama adalah metode parse_response(), yang menerima satu urutan atau batch:
from transformers import AutoModelForCausalLM, AutoTokenizer
checkpoint = "HuggingFaceTB/SmolLM3-3B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, dtype="auto", device_map="auto")
messages = [{"role": "user", "content": "Summarize the end of the Cold War, very briefly."}]
input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt")["input_ids"].to(model.device)
outputs = model.generate(input_ids, max_new_tokens=1024)[0, input_ids.shape[1]:]
out_text = tokenizer.decode(outputs)
print(tokenizer.parse_response(out_text, prefix=input_ids[0]))
# Outputs a structured dict: {"role": "assistant", "reasoning_content": "...", "content": "..."}Ketika sebuah tokenizer memiliki response_template, metode parse_response akan mengubah pesan keluaran menjadi dict terstruktur dengan rapi, siap untuk ditambahkan ke percakapan. Perhatikan bahwa kita perlu meneruskan prefix (token prompt) ke metode ini juga. Hal ini karena banyak template percakapan memulai pesan atau membuka blok berpikir sebelum membiarkan model memulai responsnya, sehingga parser kita perlu melihat prompt untuk memahami pesan tersebut. Seluruh prefix sebelum giliran terakhir dibuang; kita hanya menganalisis satu pesan pada satu waktu. Kita hanya membutuhkan prefix untuk memastikan kita melihat pesan terakhir secara utuh, dan tidak melewatkan bidang yang sudah terisi sebelumnya!
Karena prefix yang hilang dapat menyebabkan pesan yang sudah diisi sebelumnya salah diinterpretasikan secara diam-diam, prefix wajib diisi. Mengabaikannya akan memicu error. Dalam kasus langka di mana Anda yakin bahwa generasi sudah berisi pesan lengkap dan tidak memerlukan konteks prefix, berikan prefix="" (atau daftar kosong dari token ids) untuk secara eksplisit memilih keluar.
Jika tokenizer tidak memiliki template respons yang disetel, parse_response akan memicu error. Kami sedang bekerja untuk menambahkan template ke lebih banyak model secepat mungkin!
Parsing respons streaming
Pada contoh di atas, kami mem-parsing respons model sekaligus setelah generasi selesai. Namun, seringkali kami mungkin ingin mem-parsing pesan parsial saat mereka dihasilkan, terutama dalam aplikasi yang berhadapan langsung dengan pengguna, di mana kami tidak hanya ingin menampilkan halaman statis selama satu atau dua menit hingga model selesai.
Ketika Anda ingin parsing streaming, panggil tokenizer.get_response_parser(), yang mengembalikan sebuah ResponseParser.
Sama seperti parse_response, berikan prompt chat sebagai prefix= agar parser mengetahui bagian pesan mana pun yang
telah diisi sebelumnya oleh template chat. Objek yang dikembalikan adalah parser berstatus yang dapat Anda beri teks saat model menghasilkannya:
parser = tokenizer.get_response_parser(prefix=input_ids[0])
for event in parser.initial_events:
render(event) # Display the partial message to the user however you want to
for chunk in model_output:
for event in parser.feed(chunk):
render(event)
message, final_events = parser.finalize()
for event in final_events:
render(event)Jika permintaan mencakup tools, teruskan juga (get_response_parser(..., tools=tools)).
Argumen tool-call kemudian diberi tipe dari JSON Schema tool pemanggil saat setiap region ditutup, sehingga
konsumen streaming melihat argumen bertipe schema pada region_close alih-alih hanya setelah finalize().
Lihat Typing tool-call arguments untuk detailnya.
Parser akan mengeluarkan event saat teks dari proses generasi dimasukkan. Hal ini menunjukkan region mana yang sedang digenerasi. Ketika region selesai, region tersebut akan dikeluarkan dalam event terpisah dengan konten yang telah di-parse sepenuhnya. Pada akhir generasi, metode finalize() akan mem-flush teks yang tersisa dan mengeluarkan event final, serta dict pesan lengkap.
Perhatikan bahwa meskipun parse_response dapat menerima batch, parsing yang di-stream selalu untuk satu urutan: setiap ResponseParser melacak status dari satu generasi.
Jika Anda ingin melakukan streaming beberapa generasi sekaligus, buat satu ResponseParser per urutan.
Peristiwa streaming
Setiap event parsing yang di-stream adalah dict dengan kunci type. Ada tiga jenis:
| Tipe | Deskripsi | Isi |
|---|---|---|
region_open | Menunjukkan bahwa model telah memulai region baru, seperti content atau reasoning_content. | field (str): nama field. |
region_chunk | Sebagian teks untuk wilayah saat ini. | field (str): nama field. text (str): chunk baru. dirty (bool): True jika chunk adalah teks mentah yang perlu di-parse. |
region_close | Menunjukkan bahwa sebuah region telah selesai, dan kunci tersebut kini telah difinalisasi. | field (str): nama field. value (any): nilai yang sepenuhnya di-parse untuk region |
Event region_chunk dipancarkan untuk setiap region saat byte tiba, sehingga UI streaming dapat merender progres bahkan untuk region terstruktur. Untuk region yang mirip teks (text, int, float, bool), chunk diberi flag dirty=False: setiap chunk sudah merupakan bagian dari nilai akhir (kecuali spasi di akhir yang dihapus saat penutupan). Untuk region terstruktur, seperti panggilan tool dalam format JSON, chunk diberi flag dirty=True. Ini berarti teks adalah badan mentah yang belum di-parse; aman untuk ditampilkan secara inkremental, tetapi nilai parsed (dict, list, dll.) baru tiba pada event region_close yang sesuai. Dalam kedua kasus, nilai akhir dari sebuah region selalu dibawa oleh region_close, sehingga konsumen yang tidak peduli tentang perenderan antara dapat mengabaikan event region_chunk.
Jika prefix chat menulis sesuatu ke dalam pesan (misalnya, template membuka blok berpikir, atau prefill asisten memulai respons sebelum diserahkan ke model), parser mengekspos peristiwa-peristiwa tersebut sebagai parser.initial_events, sebuah daftar yang dapat Anda putar ulang ke renderer Anda sebelum memberikan keluaran model apa pun. Wilayah yang dibuka dan ditutup di dalam prefix menghasilkan urutan lengkap region_open / region_chunk / region_close dan nilai yang diurai akan masuk ke dict keluaran, persis seolah-olah model itu sendiri yang menulisnya.
Alur peristiwa yang tipikal mungkin terlihat seperti ini:
{"type": "region_open", "field": "reasoning_content"}
{"type": "region_chunk", "field": "reasoning_content", "text": "I should ", "dirty": False}
{"type": "region_chunk", "field": "reasoning_content", "text": "greet the user", "dirty": False}
{"type": "region_close", "field": "reasoning_content", "value": "I should greet the user"}
{"type": "region_open", "field": "tool_calls"}
{"type": "region_chunk", "field": "tool_calls", "text": '{"name": "greet_user", ', "dirty": True}
{"type": "region_chunk", "field": "tool_calls", "text": '"arguments": {"greeting": "Hi!"}}', "dirty": True}
{"type": "region_close", "field": "tool_calls", "value": {"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}}Perhatikan bagaimana reasoning_content dipancarkan dengan dirty=False, karena field seperti reasoning_content dan content biasanya hanya berupa teks mentah. Ini berarti Anda dapat memperlakukan chunk sebagai “output parsial” yang valid. Namun, tool_calls ditandai sebagai dirty karena teks mentahnya memerlukan pembersihan yang signifikan - tool call sering kali perlu di-parse sebagai JSON atau format lain dan kemudian disusun ulang untuk menghasilkan dict tool call final. Akibatnya, output final untuk region-region ini sering kali terlihat sangat, sangat berbeda dari teks mentahnya. Parsing final ini hanya akan terjadi ketika region_close tercapai. Terserah Anda apa yang ingin dilakukan dengan chunk dirty sampai saat itu - Anda dapat menampilkannya apa adanya untuk menunjukkan output “mentah” kepada pengguna, atau Anda dapat menunggu sampai ada sesuatu yang bersih untuk ditampilkan.
Ini merangkum sebagian besar hal yang perlu Anda ketahui untuk menggunakan response templates. Sisa dokumen ini berfokus pada bagian internal sistem parsing dan cara menulis response templates. Hal ini terutama relevan bagi pengembang dan penulis model. Sebagian besar orang dapat dengan aman berhenti di sini!
Lanjutan: Menulis template respons
Cara terbaik untuk memahami cara menulis template respons adalah dengan memilih contoh konkret. Berikut adalah contoh balasan mentah dari SmolLM:
<think>
I should greet the user
</think>
<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>Ketika kami mengurai output ini dalam format dict pesan standar, seharusnya terlihat seperti ini:
{
"role": "assistant",
"reasoning_content": "I should greet the user",
"tool_calls": [
{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}
]
}Dan berikut adalah template yang mem-parsenya. Jangan merasa gentar - sebagian besar isinya cukup jelas!
{
"defaults": {"role": "assistant"},
"start_anchor": "<|im_start|>assistant\n",
"fields": {
"reasoning_content": {"open": "<think>", "close": "</think>", "content": "text"},
"tool_calls": {
"open": "<tool_call>",
"close": "</tool_call>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": "{content}"},
},
"content": {
"close": "<|im_end|>",
"content": "text",
},
},
}Pada dasarnya, template mendefinisikan field dan delimeter. Setiap field berkorespondensi dengan sebuah key dalam dict output. Field juga mencakup informasi untuk parsing teks di dalam delimeter mereka. Ada satu hal yang perlu diperhatikan: field content tidak memiliki open, karena pada SmolLM (dan beberapa model lainnya), field ini tidak ditandai oleh token khusus. Sebaliknya, content disimpan di ruang setelah region lainnya, tetapi sebelum akhir dari sequence. Dalam template kami, kami merepresentasikan ini sebagai field implicit / leftover yang mengambil teks apa pun yang tidak diklaim oleh region lain.
Nama field harus sesuai dengan kunci yang dibaca oleh template chat model, sehingga pesan yang diurai dapat ditambahkan ke chat dan dirender kembali. Secara khusus, beri nama field penalaran reasoning_content. Ini adalah kunci standar untuk penalaran dalam dict pesan, dan yang dibaca hampir semua template chat untuk penalaran.
Selain fields, template mendukung dua kunci tingkat atas:
defaults(opsional) — Sebuah dict berisi nilai yang sudah terisi di output (misalnya{"role": "assistant"}). Kunci di sini selalu dipertahankan dalam output yang di-parse, bahkan jika tidak ada field yang menulis ke dalamnya; kunci lainnya akan dihapus jika field yang bersangkutan tidak menangkap apa pun.start_anchor(str) /start_anchor_pattern(str regex) — Menandai di mana pesan asisten saat ini dimulai di dalam prompt chat. Ketika Anda meneruskanprefix=keparse_responseatauget_response_parser, parser akan memotong kanan prefix melewati kemunculan terakhir dari anchor ini sebelum memprosesnya, sehingga giliran sebelumnya dalam percakapan multi-turn tidak mencemari state pesan saat ini. Anchor hanya diterapkan padaprefix, tidak pernah pada respons/generasi yang Anda parse — beberapa format secara sah menerbitkannya kembali di tengah pesan (output gpt-oss harmony membuka setiap channel dengan<|start|>assistant), sehingga memotong respons melewati anchor akan menghilangkan penalaran dan panggilan tool milik model itu sendiri. Inilah mengapa generasi saja tidak pernah cukup untuk menjaga terhadap kebocoran riwayat: teruskan prompt sebagaiprefix=. Untuk model bergaya ChatML, anchor biasanya adalah"<|im_start|>assistant\n". Tepat satu daristart_anchorataustart_anchor_patternharus disetel.
Sebagai contoh, diberikan prefiks multi-turn berikut (perhatikan dua giliran asisten):
<|im_start|>user Hi<|im_end|> <|im_start|>assistant Hello!<|im_end|> <|im_start|>user Again?<|im_end|> <|im_start|>assistant
parser memotong semua konten hingga terakhir <|im_start|>assistant\n, sehingga giliran "Hello!" sebelumnya dihapus. Aturannya adalah seluruh konten selain giliran asisten terakhir selalu dibuang.
Sama seperti template chat, template respons disimpan sebagai atribut tokenizer dan disimpan bersama tokenizer. Berbeda dengan template chat, kami menyimpannya di dalam tokenizer_config.json dan bukan sebagai file terpisah, karena formatnya cocok secara alami dalam JSON, tidak seperti skrip Jinja template chat.
tokenizer.response_template = template
tokenizer.save_pretrained(...) # Written as a key in tokenizer_config.jsonLanjutan: Referensi API Field
Setiap field mendukung beberapa kunci. Kita dapat membaginya menjadi dua jenis. Pertama, ada kunci yang mendefinisikan bagaimana field harus ditangkap:
| Kunci | Tipe | Tujuan |
|---|---|---|
open | str atau list[str] | String literal yang membuka region ini. Daftar string berarti “cocokkan salah satu dari string-string ini”. |
open_pattern | str (regex) | Alternatif regex untuk open. Grup bernama menjadi variabel tangkapan yang tersedia untuk transform. |
close | str atau list[str] | String literal (atau daftar string) yang menutup region ini. Abaikan untuk menjalankan hingga akhir stream. |
close_pattern | str (regex) | Alternatif regex untuk close. Grup bernama menjadi variabel tangkapan yang tersedia untuk transform. |
repeats | bool | Jika true, field tersebut adalah daftar dan setiap kecocokan akan ditambahkan. Default false. |
join | str | Dengan repeats, gabungkan hasil ke dalam satu string dengan pemisah ini alih-alih daftar. |
optional | bool | Jika false dan region tidak pernah cocok, kami akan menaikkan error. Default true. |
Sebuah field harus memiliki satu dari open atau open_pattern, tetapi tidak keduanya, dan hal yang sama berlaku untuk close dan close_pattern.
Sebuah field dapat mengabaikan close/close_pattern sepenuhnya, di mana region tetap terbuka hingga akhir teks yang dihasilkan. Ini berguna untuk field terakhir yang berjalan hingga akhir pesan.
Field yang tidak memiliki open maupun open_pattern adalah field implisit: field ini aktif setiap kali tidak ada region eksplisit yang terbuka, sehingga menangkap sisa teks. Maksimal satu field yang dapat bersifat implisit. Hal ini paling sering digunakan ketika content tidak memiliki tag token khusus, dan hanya ditulis sebagai plaintext setelah field lainnya.
Selain pembuka dan penutup delimitator, Anda juga dapat menentukan repeats, yang menunjukkan bahwa field tersebut adalah daftar dan delimitator dapat cocok beberapa kali. Hal ini paling umum terjadi pada pemanggilan tool paralel, ketika model mengeluarkan beberapa pemanggilan tool secara bersamaan:
'<tool_call>{"name": "a", ...}</tool_call><tool_call>{"name": "b", ...}</tool_call>'
# Returns `"tool_calls": [{... "a" ...}, {... "b" ...}]` in a template with repeats: trueNamun, tidak semua field berulang harus menjadi list. Beberapa model mengeluarkan beberapa blok reasoning_content atau content dalam satu pesan, dan output alami untuk hal tersebut adalah satu string yang digabungkan. Mengatur join (string pemisah, sering kali hanya "") mengubah field repeats dari mengumpulkan list menjadi menggabungkan kecocokannya:
field = {"reasoning_content": {"open": "<think>", "close": "</think>", "repeats": True, "join": "\n"}}
input = "<think>first</think>...<think>second</think>"
# Returns: {"reasoning_content": "first\nsecond"}Setiap kecocokan dari bidang join harus dapat diurai menjadi string. Saat streaming, region_close masih membawa nilai dari masing-masing blok; pemisah hanya muncul dalam dict pesan akhir.
Terakhir, Anda dapat menentukan optional: false untuk field yang harus ada. Jika field seperti itu tidak ada,
kami akan melempar error alih-alih hanya mengembalikan dict pesan tanpa field tersebut.
Akhir dari generasi akan menutup dan memfinalisasi semua region yang masih terbuka, bahkan jika delimiter penutupnya tidak terlihat.
Menganalisis konten dari sebuah field
Setelah kita mendefinisikan cara menangkap sebuah field, kita juga perlu menentukan cara mengurai teks mentah di dalam tangkapan tersebut. Ada empat kunci yang mengontrol hal ini:
| Kunci | Tipe | Tujuan |
|---|---|---|
content | str | Tipe konten di dalam region ini. Nilainya secara default adalah "text". Setiap tipe memiliki parser-nya sendiri. |
content_args | dict | Argumen yang akan diteruskan ke parser konten untuk wilayah ini. |
transform | dict/list | Template opsional pasca-parsing yang membentuk ulang tubuh yang telah diurai (lihat Transform). |
transform_each | bool | Jika true, konten yang diurai harus berupa daftar dan transform diterapkan per elemen. |
Kunci pertama (dan terpenting) adalah content. Ini menunjukkan tipe konten dari field, yang menentukan parser yang akan digunakan untuk mengubah teks mentah yang ditangkap di field menjadi output akhir. content_args digunakan untuk mengonfigurasi parser, dan memungkinkan kami mendukung berbagai keanehan format tanpa perlu kode khusus.
Kita akan melihat setiap tipe parser dan argumennya secara berurutan.
Tipe dasar
text, int, float dan bool adalah tipe dasar. Semua tipe konten ini hanya menghapus spasi lalu melakukan konversi tipe sederhana jika diperlukan. Mereka tidak memiliki content_args, kecuali text yang mendukung argumen strip, yang menghapus spasi dari awal dan akhir teks yang ditangkap, dengan nilai default true.
field = {"count": {"open": "<n>", "close": "</n>", "content": "int"}}
input = "<n> 42 </n>"
# Returns: {"count": 42}json
Parser json memparse teks yang ditangkap sebagai JSON. Ini adalah tulang punggung untuk argumen tool-call dan
apa pun yang memiliki struktur bersarang lainnya. Parser ini menerima sejumlah content_args opsional untuk menangani
berbagai cara model merusak JSON di dunia nyata:
unquoted_keys(bool, defaultfalse): Aktifkan ketika nama kunci berupa mentah (raw) alih-alih diberi tanda kutip (mis.{name: "foo"}). Berguna untuk model yang mengeluarkan literal objek bergaya Javascript alih-alih JSON ketat.string_delims(list of[open, close]pairs, optional): untuk model yang membungkus nilai string dengan delimiter khusus alih-alih"...". Setiap pasangan memberikan penanda pembuka dan penutup.allow_non_json(bool, defaultfalse): jika parsing gagal, kembalikan teks mentah yang sudah dibersihkan alih-alih melempar error. Berguna sebagai fallback untuk field di mana model umumnya mengeluarkan JSON tetapi sesekali beralih ke teks biasa.
unquoted_keys dan string_delims keduanya ada untuk menangani model yang menghasilkan output non-standar, hampir-tapi-bukan-JSON, sehingga Anda seharusnya hanya membutuhkannya untuk segelintir model.
field = {"args": {"open": "<args>", "close": "</args>", "content": "json", "content_args": {"unquoted_keys": True}}}
input = '<args>{city: "London"}</args>'
# Returns: {"args": {"city": "London"}}xml-inline
Parser xml-inline digunakan untuk region yang terdiri dari urutan datar tag mirip XML, di mana setiap tag menjadi satu entri dalam dict. Parser ini paling sering digunakan di dalam field tool_calls untuk model yang mengeluarkan setiap argumen sebagai tag tersendiri, bukan sebagai blob JSON:
tag_pattern(str, wajib): regex yang mencocokkan satu tag. Harus berisi grup bernamakey(kunci dict yang dihasilkan) danvalue(teks mentah yang menjadi nilai dict).value_parser(dict, opsional): parser konten bersarang yang diterapkan pada setiapvalueyang ditangkap. Dict denganname(parser-nya, mis."json","int") danargsopsional (content_argsdari parser tersebut). Jika dihilangkan, nilai tetap sebagai string mentah.merge_duplicates(bool, defaultfalse): ketika kunci yang sama muncul beberapa kali, kumpulkan nilai-nilainya ke dalam daftar alih-alih membiarkan kecocokan yang lebih akhir menimpa yang sebelumnya.
Sebagai contoh, Qwen3 mengeluarkan setiap argumen tool-call sebagai tag <parameter> miliknya sendiri, dan kami memarsinya seperti ini:
"tool_calls": {
"open_pattern": r"<tool_call>\s*<function=(?P<name>\w+)>",
"close": "</tool_call>",
"repeats": True,
"content": "xml-inline",
"content_args": {
"tag_pattern": r"<parameter=(?P<key>\w+)>\s*(?P<value>.*?)\s*</parameter>",
"value_parser": {"name": "json", "args": {"allow_non_json": True}},
},
"transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
}Perhatikan value_parser yang bersarang: setiap nilai parameter juga diproses melalui parser json (dengan allow_non_json sehingga string polos tetap lolos). Memberikan field tool_calls di atas input ini:
input = "<tool_call><function=get_weather><parameter=city>London</parameter><parameter=units>celsius</parameter></function></tool_call>"
# Returns: {"tool_calls": [{"type": "function", "function": {"name": "get_weather", "arguments": {"city": "London", "units": "celsius"}}}]}kv-lines
Parser kv-lines menangani pasangan key: value yang dipisahkan baris (bayangkan metadata bergaya YAML atau file .env). Setiap baris menjadi satu entri dalam dict yang dihasilkan. Semua argumen bersifat opsional:
line_sep(str, default"\n"): pemisah antar pasangan.kv_sep(str, default":"): pemisah antara kunci dan nilainya di dalam satu baris. Hanya kemunculan pertama yang digunakan sebagai titik pemisah, sehingga nilai itu sendiri dapat memuat pemisah tersebut.strip(bool, defaulttrue): menghapus spasi di sekitar setiap kunci dan nilai.value_parser(dict, opsional): parser konten bersarang yang diterapkan ke setiap nilai, dalam format{"name": ..., "args": ...}yang sama seperti untukxml-inline. Jika diabaikan, nilai tetap sebagai string mentah.
Baris yang kosong atau tidak berisi kv_sep akan dilewati secara diam-diam, sehingga baris kosong yang tidak sengaja ada di area yang ditangkap tetap ditoleransi.
field = {"metadata": {"open": "<meta>", "close": "</meta>", "content": "kv-lines"}}
input = "<meta>name: alice\nage: 30</meta>"
# Returns: {"metadata": {"name": "alice", "age": "30"}}Catatan age tetap menyimpan "30" sebagai string; tambahkan value_parser berupa {"name": "int"} untuk mengubahnya menjadi 30.
Mengetik argumen pemanggilan alat
Kadang-kadang, parser respons dapat memparse output model dengan tipe yang salah. Misalnya,
parser mungkin memparse float 1.5 sebagai “1.50”. Hal ini dapat menyebabkan masalah pada pemanggilan tool, jika tool mengharapkan
argumen dalam satu tipe tetapi menerimanya dalam tipe lain. Untuk menghindari hal ini,
Anda dapat meneruskan tools dari permintaan ke parse_response() atau ResponseParser untuk melakukan casting menggunakan parameters JSON Schema dari masing-masing tool.
Tool diterima dalam format yang sama seperti apply_chat_template(): JSON
schema, atau fungsi Python dengan type hints dan docstring yang dikonversi secara otomatis menjadi schema.
tools = [{
"type": "function",
"function": {
"name": "set_alarm",
"parameters": {
"type": "object",
"properties": {
"hour": {"type": "integer"},
"enabled": {"type": "boolean"},
"label": {"type": "string"},
},
},
},
}]
message = parse_response(model_out, template, prefix="", tools=tools)
# message["tool_calls"][0]["function"]["arguments"] ==
# {"hour": 7, "enabled": True, "label": "wake up"}Transform
Untuk sebagian besar field, kunci transform tidak diperlukan. Kunci ini digunakan ketika body yang di-parse perlu dibentuk ulang menjadi output akhir, atau ketika informasi dari delimiter harus digabungkan ke dalam hasil. Paling sering muncul di field tool_calls, karena field ini sering memiliki struktur yang kompleks.
transform adalah sebuah template: sebuah dict (atau list) yang menjelaskan bentuk output, di mana string apa pun dengan bentuk "{name}" akan diganti dengan nilai yang sesuai. Nilai dapat diakses dari content (bagian tubuh yang telah diurai dari region ini) dan grup bernama apa pun yang ditangkap oleh open_pattern / close_pattern. Kasus penggunaan yang sangat umum adalah membungkus dict panggilan tool dalam dict luar dengan kunci function, karena ini merupakan bagian dari format panggilan tool standar kami:
"tool_calls": {
"open": "<tool_call>",
"close": "</tool_call>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": "{content}"},
},Jadi, output mentah ini:
<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>menjadi (perhatikan repeats: True membuat tool_calls menjadi daftar):
[{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}]Placeholder untuk seluruh string seperti "{content}" mengembalikan nilai yang dicari dengan tipenya tetap dipertahankan — jadi di atas, dict JSON yang di-parse langsung mengisi nilai dari function. Placeholder harus menjadi seluruh string: mencampurkan teks dan placeholder ("abc {name} def") tidak diizinkan. Mereka bukan f-string!
Placeholder juga dapat mengambil jalur bertitik seperti "{content.args}", yang mencari content lalu turun ke kunci args dari dict yang diurai. Ini adalah cara Anda membentuk ulang isi panggilan alat yang nama kuncinya tidak cocok dengan format standar kami. Misalnya, model yang mengeluarkan {"name": ..., "args": ...} perlu mengganti nama args menjadi arguments:
"tool_calls": {
"open": "<tool>",
"close": "</tool>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": {"name": "{content.name}", "arguments": "{content.args}"}},
},Path yang tidak dapat di-resolve (di sini, panggilan tool yang badannya tidak memiliki args) akan memicu error alih-alih mengeluarkan panggilan tool yang tidak valid.
transform cukup serbaguna, yang menjadi kebutuhan ketika output model memiliki format yang sangat berbeda dari API standar kami. GPT-OSS adalah contoh yang baik - ia menyematkan nama fungsi di header channel alih-alih di body JSON, sehingga kami harus menangkapnya dengan named group di open_pattern dan menggabungkannya dengan content di dalam transform. Semua named group di open_pattern dan close_pattern menjadi tersedia sebagai variabel di samping content:
"tool_calls": {
"open_pattern": r"<\|channel\|>commentary to=functions\.(?P<name>\w+).*?<\|message\|>",
"close": "<|call|>",
"repeats": True,
"content": "json",
"transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
},Nama fungsi berada di header channel, bukan di body JSON, jadi ini:
<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location": "San Francisco, CA"}<|call|>menjadi:
[{"type": "function", "function": {"name": "get_current_weather", "arguments": {"location": "San Francisco, CA"}}}]Kadang-kadang konten terparses dari sebuah field sendiri merupakan daftar record dan Anda ingin mengubah bentuk masing-masing record. Template Cohere adalah contoh yang baik: template ini mengeluarkan semua pemanggilan tool di dalam satu array JSON, sehingga kami mengatur transform_each: True untuk menerapkan transformasi per elemen. Kunci dari setiap elemen array diurai ke dalam cakupan template, sehingga "{tool_name}" mencari tool_name di elemen saat ini:
"tool_calls": {
"open": "<|START_ACTION|>",
"close": "<|END_ACTION|>",
"content": "json",
"transform_each": True,
"transform": {"type": "function", "function": {"name": "{tool_name}", "arguments": "{parameters}"}},
},Ini akan mengonversi output seperti ini:
[
{"tool_name": "greet_user", "parameters": {"greeting": "Hi!"}},
{"tool_name": "search", "parameters": {"query": "weather tomorrow"}}
]Menjadi output seperti ini, yang sesuai dengan API standar kami:
[
{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}},
{"type": "function", "function": {"name": "search", "arguments": {"query": "weather tomorrow"}}}
]Flag transform_each hanya diperlukan ketika content sudah berupa list; untuk kasus yang lebih umum di mana setiap match menyumbang satu elemen (dan repeats: True mengakumulasi mereka), maka transform akan diterapkan ke setiap elemen secara default.
Pengembang framework: Portabilitas regex
open_pattern, close_pattern, dan start_anchor_pattern adalah string regex. Bagi sebagian besar pengguna, dan bahkan bagi sebagian besar penulis model, ini seharusnya bukan masalah, tetapi jika Anda adalah pengembang yang menulis implementasi parsing respons dalam bahasa lain, Anda perlu menyadari detail implementasi kami. Bagian ini ditujukan untuk semua orang yang harus mengimplementasikan parser Jinja lengkap agar chat templating non-Python dapat berfungsi - kami berharap bahwa jika Anda mengikuti pedoman sederhana di bawah ini, maka template respons seharusnya jauh lebih tidak menyakitkan:
- Kami menggunakan modul
regexPython untuk semua regex yang digunakan dalam parsing chat. Karena semua string Python3 adalah unicode, semua kecocokan regex kami sadar unicode. Hal ini terutama memengaruhi karakter umum seperti\w. Pastikan Anda mengatur flag unicode yang relevan di engine Anda. - Kami mengompilasi semua regex dengan
re.DOTALLdiaktifkan danre.MULTILINEdinonaktifkan, sehingga.cocok dengan\ntetapi^dan$hanya cocok dengan awal/akhir seluruh input, bukan baris baru. - Kami menggunakan sintaks
(?P<name>...)untuk grup bernama. Implementasi regex lain memiliki sintaks grup tangkapan bernama yang sangat berbeda, jadi Anda mungkin perlu mencari pola ini di regex dan menulis ulang agar sesuai dengan implementasi lokal Anda. - Kami menggunakan pencocokan regex parsial untuk menentukan kapan data dapat dikeluarkan. Hal ini diperlukan karena akhir sebuah region dapat terdiri dari beberapa token, dan kami tidak ingin mengeluarkan pemisah akhir-region tersebut, sehingga kami menahan token hingga kami yakin bahwa token tersebut berada di dalam region dan bukan bagian dari batas. Ini berarti bahwa setiap kali regex akhir region memiliki pencocokan parsial, kami menahan data hingga regex tersebut cocok atau gagal. Jika mesin regex Anda tidak mendukung pencocokan parsial, Anda tetap dapat mengimplementasikan template respons, tetapi Anda mungkin perlu mencari solusi lain untuk masalah ini. Satu pendekatan sederhana adalah dengan menahan region-region ini / mengeluarkannya sebagai
dirtyhingga Anda secara definitif melihat pemisah akhir. - Mesin regex Anda mungkin (jarang) tidak mendukung lookaround seperti
(?!...). Meskipun fitur ini tidak umum digunakan dalam template respons, mereka dapat muncul dan kami memang mendukungnya! Anda mungkin perlu melempar error dalam kasus tersebut, atau secara manual mengekstrak lookaround dan menegakkannya dalam kode Anda ketika mesin regex menemukan kemungkinan kecocokan. - Fitur lanjutan lainnya seperti backreferences, atomic groups, possessive quantifiers, recursion, dan sebagainya umumnya tidak digunakan dalam template respons. Kami akan berusaha mencegah para pengembang model untuk menggunakannya, sehingga Anda dapat mengabaikannya dengan aman.