Beranda/Agent
Agen pada API LLM Tanpa Sensor: Pemanggilan Fungsi, Kerangka Kerja, dan Guardrails
Agen adalah loop: model meminta fungsi, kode Anda menjalankannya, dan hasilnya dimasukkan kembali ke dalam percakapan. Karena API LLM Tanpa Sensor mendukung pemanggilan fungsi dalam format chat standar, Anda dapat membangun loop itu dalam empat puluh baris, lalu sambungkan endpoint yang sama ke LangChain atau LlamaIndex. Panduan ini mencakup keduanya, serta pemeriksaan yang menjaga keamanan eksekusi alat.
Diperbarui
Poin kunci
- Lewatkan alat dalam permintaan, perhatikan finish_reason sama dengan tool_calls, jalankan fungsi, lampirkan pesan alat, dan ulangi hingga model menjawab dengan teks.
- LangChain membutuhkan ChatOpenAI dengan base_url; LlamaIndex membutuhkan OpenAILike dengan api_base dan flag pemanggilan fungsi diatur.
- Model mengusulkan panggilan; kode Anda memutuskan apakah panggilan tersebut dijalankan. Daftar izinkan, validasi, batasi waktu, dan batasi loop.
- Agent melipatgandakan jumlah permintaan, jadi sesuaikan anggaran untuk batas 300 permintaan per menit dan jendela konteks 100.000 token.
Anatomi loop pemanggilan fungsi
Lepaskan framework dan setiap agent melakukan empat hal yang sama. Anda mengirim percakapan ditambah daftar tools. Model membalas dengan teks (berarti selesai) atau dengan tool_calls (berarti ingin sesuatu dieksekusi). Anda menjalankan setiap panggilan dan menambahkan satu pesan tool per panggilan, dengan mengutip id dari panggilan tersebut. Kemudian Anda meminta model lagi dengan percakapan yang lebih panjang.
Sinyal untuk cabang adalah finish_reason. Ketika sama dengan tool_calls, pesan asisten memiliki content diatur ke null dan array tool_calls, setiap entri berisi id, nama fungsi, dan string argumen JSON. Halaman referensi menunjukkan bentuk pastinya. Dua detail yang sering membuat orang bingung: argumen adalah string yang harus diuraikan, dan pesan asisten yang berisi panggilan harus tetap dalam riwayat sebelum hasil alat Anda, dalam urutan.
Loop lengkap dari awal
Skrip di bawah ini mendefinisikan satu fungsi, mendeskripsikannya dalam skema JSON, dan melakukan loop hingga model menghasilkan teks biasa. Ini menggunakan SDK Python resmi yang ditunjuk ke API. Setiap iterasi melampirkan pesan asisten, lalu pesan alat per panggilan, dan penghitung langkah menghentikan perilaku yang tidak terkendali.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.uncensoredapis.com/v1", api_key=os.environ["API_KEY"])
TICKETS = {"T-101": "open", "T-102": "closed"}
def ticket_status(ticket_id: str) -> str:
return TICKETS.get(ticket_id, "unknown ticket")
TOOLS = [{
"type": "function",
"function": {
"name": "ticket_status",
"description": "Look up the status of a support ticket by id.",
"parameters": {
"type": "object",
"properties": {"ticket_id": {"type": "string"}},
"required": ["ticket_id"],
},
},
}]
REGISTRY = {"ticket_status": ticket_status}
messages = [{"role": "user", "content": "Are tickets T-101 and T-102 still open?"}]
for step in range(6): # hard cap on loop turns
r = client.chat.completions.create(
model="uncensored", messages=messages, tools=TOOLS, max_tokens=400)
msg = r.choices[0].message
messages.append(msg.model_dump(exclude_none=True))
if r.choices[0].finish_reason != "tool_calls":
print(msg.content)
break
for call in msg.tool_calls:
fn = REGISTRY.get(call.function.name)
try:
args = json.loads(call.function.arguments)
result = fn(**args) if fn else f"error: unknown tool {call.function.name}"
except Exception as exc:
result = f"error: {exc}"
messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
else:
print("stopped: step limit reached")Perhatikan apa yang dilakukan loop saat gagal: kesalahan menjadi hasil alat sebagai string. Hal ini memungkinkan model membaca masalah dan mencoba lagi, misalnya memperbaiki id tiket yang salah, alih-alih program Anda crash. Klausul for ... else hanya dipicu jika batas tercapai tanpa jawaban akhir, yang merupakan cara Anda mendeteksi model yang terjebak dalam lingkaran.
Jaga prompt tetap fokus. Pesan sistem singkat yang menyatakan kapan memanggil alat dan kapan menjawab langsung mengurangi panggilan yang tidak perlu, dan setiap panggilan yang dilewati menghemat perjalanan bolak-balik, token, dan satu slot batas laju Anda.
Melindungi eksekusi alat
Output model adalah input yang tidak dapat dipercaya. Nama alat atau argumen yang dipilih model mungkin salah, aneh, atau dipengaruhi oleh teks yang dibaca model dari halaman web atau pengguna. Perlakukan setiap panggilan seperti permintaan yang datang dari internet publik.
- Daftar izinkan berdasarkan nama. Cari fungsi dalam registri yang Anda buat. Jangan pernah gunakan string untuk mengimpor atau mengevaluasi kode.
- Pisahkan baca dan tulis. Alat hanya baca berjalan bebas. Apa pun yang mengirim email, mengeluarkan uang, menghapus data, atau menyentuh file memerlukan persetujuan manusia secara eksplisit.
- Validasi argumen terhadap skema yang Anda umumkan: kunci wajib, tidak ada kunci tambahan, tipe, dan batas panjang.
- Batasi waktu dan output. Terapkan batas waktu, dan potong hasil sebelum dimasukkan kembali agar output alat yang besar tidak menghabiskan jendela konteks 100.000 token Anda.
- Batasi iterasi dan biaya. Batasi langkah per tugas dan token per sesi, menggunakan blok
usage. - Log setiap panggilan dengan nama, argumen, dan hasil agar Anda dapat melakukan audit perilaku.
import json
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutTimeout
ALLOWED = {"ticket_status", "lookup_invoice"} # read-only tools only
NEEDS_APPROVAL = {"refund_order"} # side effects wait for a human
POOL = ThreadPoolExecutor(max_workers=4)
def validate(args: dict, schema: dict) -> None:
props = schema["properties"]
for key in schema.get("required", []):
if key not in args:
raise ValueError(f"missing argument: {key}")
for key, val in args.items():
if key not in props:
raise ValueError(f"unexpected argument: {key}")
if props[key]["type"] == "string" and not isinstance(val, str):
raise ValueError(f"{key} must be a string")
if isinstance(val, str) and len(val) > 200:
raise ValueError(f"{key} too long")
def run_tool(name, raw_args, registry, schemas, approve=lambda n, a: False):
if name in NEEDS_APPROVAL:
args = json.loads(raw_args)
if not approve(name, args):
return "denied: this action needs human approval"
elif name not in ALLOWED:
return f"error: tool {name} is not permitted"
args = json.loads(raw_args)
validate(args, schemas[name]["parameters"])
future = POOL.submit(registry[name], **args)
try:
return str(future.result(timeout=5))[:2000] # time and size limits
except FutTimeout:
return "error: tool timed out"Perlakukan hasil alat sebagai tidak dapat dipercaya juga. Jika alat mengembalikan teks yang dipetik berisi instruksi, model mungkin mengikutinya. Bungkus konten tersebut dengan delimiter yang jelas dalam pesan alat dan nyatakan dalam prompt sistem Anda bahwa output alat adalah data, bukan perintah.
Menulis skema alat yang dapat digunakan model
Skema adalah satu-satunya dokumentasi yang dilihat model, jadi skema ini memerlukan perhatian yang sama seperti API publik. Beri nama fungsi dengan kata kerja dan kata benda yang sesuai dengan tugas, seperti ticket_status atau lookup_invoice, bukan label generik seperti run atau helper. Tulis description sebagai instruksi: apa yang dilakukan fungsi, apa yang dikembalikan, dan kapan tidak menggunakannya.
- Jaga parameter agar sedikit dan datar. Dua atau tiga bidang sederhana dipanggil dengan benar jauh lebih sering daripada objek bersarang dengan sepuluh bidang.
- Gunakan enum untuk himpunan tertutup, misalnya
"enum": ["open", "closed"], agar model tidak dapat membuat nilai baru. - Deskripsikan setiap properti dengan satuan atau contoh: "ID tiket seperti T-101" lebih baik daripada string polos.
- Kembalikan hasil yang ringkas. String atau objek JSON kecil bekerja paling baik; dump panjang menghabiskan konteks untuk sedikit manfaat.
- Utamakan beberapa alat khusus daripada satu alat serba guna dengan bendera mode.
Ketika panggilan gagal validasi, kembalikan pesan validasi sebagai hasil alat daripada melempar kesalahan. Model biasanya memperbaiki panggilan pada giliran berikutnya, karena dapat membaca apa yang salah. Jika gagal dengan cara yang sama dua kali, tulis ulang deskripsi, bukan loop.
Konfigurasi LangChain
Kelas ChatOpenAI LangChain menerima base_url, sehingga tidak perlu paket penyedia kustom. Atur model ke uncensored, masukkan kunci dari lingkungan Anda, dan ikatkan alat dengan bind_tools. Instal langchain-openai dan langchain-core.
import os
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def ticket_status(ticket_id: str) -> str:
"""Return the status of a support ticket."""
return {"T-101": "open", "T-102": "closed"}.get(ticket_id, "unknown ticket")
llm = ChatOpenAI(
model="uncensored",
base_url="https://api.uncensoredapis.com/v1",
api_key=os.environ["API_KEY"],
temperature=0.3,
max_tokens=400,
)
llm_with_tools = llm.bind_tools([ticket_status])
ai = llm_with_tools.invoke("What is the status of ticket T-101?")
for call in ai.tool_calls:
print(call["name"], call["args"], "->", ticket_status.invoke(call["args"]))Hasil dari invoke adalah AIMessage yang daftar tool_calls-nya berisi nama dan argumen yang telah diuraikan, sehingga Anda melewatkan langkah JSON manual. Untuk mendapatkan loop otonom penuh, serahkan objek llm yang sama ke konstruktor agent LangChain; endpoint hanya perlu menghormati pemanggilan fungsi, yang dilakukannya. Jika agent tampaknya mengabaikan alatnya, periksa apakah docstring dan petunjuk tipe ada, karena hal tersebut menjadi skema yang dilihat model.
Konfigurasi LlamaIndex
LlamaIndex menyediakan OpenAILike untuk server yang mengikuti format penyelesaian obrolan. Tiga pengaturan penting. api_base menunjuk ke API, is_chat_model=True memilih rute obrolan, dan is_function_calling_model=True memberi tahu framework bahwa ia dapat mengirim alat. Atur context_window ke 100000 agar pemecah teks dan buffer memorinya menyesuaikan ukuran dengan benar. Instal llama-index-llms-openai-like.
import os
from llama_index.llms.openai_like import OpenAILike
from llama_index.core.tools import FunctionTool
def ticket_status(ticket_id: str) -> str:
"""Return the status of a support ticket."""
return {"T-101": "open", "T-102": "closed"}.get(ticket_id, "unknown ticket")
llm = OpenAILike(
model="uncensored",
api_base="https://api.uncensoredapis.com/v1",
api_key=os.environ["API_KEY"],
is_chat_model=True,
is_function_calling_model=True,
context_window=100000,
max_tokens=400,
)
tool = FunctionTool.from_defaults(fn=ticket_status)
response = llm.predict_and_call([tool], "Check ticket T-102 for me.")
print(response)Dari sini Anda dapat meneruskan llm yang sama ke pekerja agent atau mesin kueri. Ingat bahwa pipeline pengambilan dapat memasukkan banyak potongan ke dalam satu prompt; dengan jendela 100.000 token yang dibagi antara prompt dan penyelesaian, batasi jumlah potongan yang diambil dan pertahankan max_tokens agar wajar.
Mengoperasikan agen dengan anggaran terbatas
Sebuah agen mengubah satu pesan pengguna menjadi beberapa permintaan, jadi rencanakan kapasitas. Tugas dengan lima putaran alat membutuhkan enam panggilan model. Sepuluh tugas simultan karenanya menghasilkan enam puluh permintaan dalam rentang waktu singkat, dan batas 300 permintaan per menit akan tercapai lebih cepat daripada aplikasi obrolan. Pasang throttle bucket-token dari panduan kesalahan dan batas laju di depan klien Anda.
Konteks bertambah setiap putaran karena setiap hasil alat ditambahkan. Ringkas atau hapus output alat lama setelah digunakan, dan pantau usage.prompt_tokens; saat mendekati 50.000, kompres riwayat. Untuk biaya, asumsikan tugas ilustratif dengan 6 panggilan yang rata-rata 3.000 token prompt dan 200 token penyelesaian: itu adalah 18.000 token input (sekitar $0,0045 pada $0,25 per juta) dan 1.200 token output (sekitar $0,0012 pada $1,00 per juta). Jumlah token tersebut adalah asumsi untuk penjumlahan, bukan pengukuran. Jika Anda baru menggunakan endpoint ini, mulai dengan dokumentasi dan kredit uji coba gratis.
Menguji dan men-debug agen
Agen gagal dengan cara yang terlewatkan oleh unit test pada fungsi tunggal, jadi uji percakapan. Rekam daftar pesan lengkap untuk setiap run, termasuk argumen dan hasil alat, dan putar ulang kegagalan dari log. Tiga kebiasaan memberikan hasil cepat.
- Palsukan model. Stub klien agar mengembalikan pesan
tool_callsyang telah diprakerja. Ini menguji registri, validasi, dan pengakhiran loop Anda tanpa jaringan dan tanpa pengeluaran. - Kunci sampling. Gunakan
temperaturerendah, seperti 0,2, untuk tugas berbasis alat. Pengacakan yang lebih rendah memberikan JSON argumen yang lebih dapat diulang, dan Anda dapat meningkatkannya nanti untuk langkah yang lebih banyak menulis. - Tegaskan pada lintasan. Untuk sejumlah kecil prompt tetap, periksa alat mana yang dipanggil dan dalam urutan apa, bukan hanya teks akhir.
Pantau finish_reason juga. Nilai length di tengah panggilan alat berarti argumennya terpotong; naikkan max_tokens (hingga 32.000) dan JSON akan selesai. Streaming juga berfungsi dengan alat, tetapi kumpulkan fragmen argumen per indeks panggilan dan parse hanya setelah stream ditutup. Untuk menyegarkan ingatan tentang potongan-potongan tersebut, lihat bagian streaming dari referensi.
Tanya jawab
Apakah API mendukung panggilan alat paralel dalam satu balasan?
Pesan membawa array tool_calls, jadi tangani setiap entri di dalamnya dan kirim satu pesan alat per id panggilan.
Mengapa loop agen saya berjalan selamanya?
Biasanya kesalahan alat tidak informatif atau prompt sistem tidak pernah mengatakan kapan harus berhenti. Tambahkan batas langkah dan kembalikan string kesalahan yang jelas.
Apakah saya perlu LangChain atau LlamaIndex?
Tidak. Loop sederhana dengan SDK OpenAI sudah cukup, dan kerangka kerja hanya menambahkan orkestrasi di atas endpoint yang sama.
Bagaimana cara menghentikan model menjalankan alat destruktif?
Jangan pernah mendaftarkannya tanpa langkah persetujuan. Hanya fungsi dalam registri Anda yang dapat berjalan, jadi simpan tindakan penulisan di balik konfirmasi manusia.
Kunci Anda hanya selangkah lagi dari satu formulir
Buat akun, salin kunci, ubah URL dasar. Itu saja seluruh pengaturannya.