Chat Completions
POST /chat/completions ile OpenAI Chat Completions biçiminde mesaj gönderin; yanıtın sone alanı sohbetin kimliğini, durumunu ve zengin mesajları taşır.
POST https://pb.sonecentral.com/api/v1/chat/completionsOpenAI Chat Completions'ın istek ve yanıt biçimini kullanır. OpenAI SDK'ları, LangChain ve OpenAI uyumlu her istemci baseURL değiştirilerek çalışır.
İstek
{
"model": "default",
"messages": [
{ "role": "user", "content": "Bu hafta sonu açık mısınız?" }
],
"metadata": { "conversation_id": "conv_x8k2m9p4q7w1r3t" },
"stream": false
}Alan
Tür
Sohbetin kimliği birden çok yoldan gelebilir. Sonecentral ilk dolu olanı şu sırayla kullanır:
- Gövdedeki
conversation_id. - Gövdedeki
sone.conversation_id. X-Sone-Conversation-Idbaşlığı.metadata.conversation_id.- Geçmişe olduğu gibi eklediğiniz bir asistan mesajının
sone.conversation_idalanı. Birden çok varsa en sonuncusu.
Kimlik hiç gelmezse sohbet başka yollarla aranır: Sohbet sürekliliği.
Mesaj rolleri
| Rol | Ne olur |
|---|---|
user | Ziyaretçinin mesajı. content metin ya da text parçalarından oluşan bir dizi olabilir. |
assistant | Önceki asistan yanıtı. Sohbeti bulmak için okunur. Sunucu anahtarıyla yeni bir sohbet açılırken geçmiş olarak da aktarılır. |
system, developer | Sunucu anahtarıyla asistana yalnız bilgi olarak iletilir, en fazla 2.000 karakter. Tarayıcı anahtarıyla yok sayılır. Ayrıntılar: Geliştirici bağlamı. |
tool, function | Yok sayılır. |
Başka bir rol 400 invalid_role alır. Son mesaj metin taşıyan bir user mesajı değilse istek 400 missing_user_message alır. Yeni bir mesaj 4.000 karakterden uzunsa 400 message_too_long gelir.
Yeni mesajda görsel, ses ya da dosya parçası varsa istek 400 unsupported_content alır. Önceki mesajlardaki bu tür parçalar sessizce atlanır, yalnız metinleri okunur.
Yok sayılan ve desteklenmeyen alanlar
Asistanın dil modelini, üslubunu ve araçlarını Sonecentral'daki ayarlar belirler. Bu yüzden şu alanlar yok sayılır, hata vermez: temperature, top_p, max_tokens, max_completion_tokens, presence_penalty, frequency_penalty, seed, stop, logit_bias, logprobs, top_logprobs, reasoning_effort, verbosity, service_tier, store, parallel_tool_calls, tool_choice, function_call, audio, modalities. Burada sayılmayan bilinmeyen alanlar da yok sayılır.
Şunlar reddedilir:
| Alan | Hata |
|---|---|
tools, functions | 400 tools_not_supported. Asistan işletmenin kendi araçlarını kullanır. |
n, 1 dışında | 400 unsupported_parameter |
response_format, text dışında | 400 unsupported_parameter |
Yanıt
{
"id": "chatcmpl-9f2c4a7e1b8d3f6a0c5e2b9d",
"object": "chat.completion",
"created": 1760866000,
"model": "k3m9x2p7q1w8e4r",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Evet, cumartesi 10.00 ile 18.00 arası açığız. Pazar kapalıyız.",
"refusal": null,
"annotations": [],
"sone": {
"conversation_id": "conv_x8k2m9p4q7w1r3t",
"status": "answered",
"messages": [
{
"id": "msg_p4q7w1x8k2m9a5z",
"role": "assistant",
"type": "text",
"text": "Evet, cumartesi 10.00 ile 18.00 arası açığız. Pazar kapalıyız.",
"created_at": 1760866000
}
]
}
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 7, "completion_tokens": 16, "total_tokens": 23 },
"system_fingerprint": null,
"sone": { "conversation_id": "conv_x8k2m9p4q7w1r3t", "status": "answered" }
}model yanıt veren asistanın kimliğidir; "default" gönderseniz de seçilen asistanın kimliği gelir. Sohbetin kimliği ayrıca X-Sone-Conversation-Id yanıt başlığında gelir.
content, bu turda gönderilen bütün mesajların düz metnidir; birden çok mesaj araya boş satır, yani \n\n konularak birleştirilir. Yalnız content gösteren bir istemci de eksiksiz çalışır.
usage bilgi içindir; gönderilen yeni mesajların ve gelen metnin uzunluğundan, yaklaşık dört karakter bir belirteç sayılarak tahmin edilir. Faturalama bu sayılara göre yapılmaz.
sone alanı
OpenAI biçimine eklenen alan budur. Tam hali choices[0].message.sone içinde gelir. Aynı özet, messages olmadan, yanıtın üst düzeyinde de vardır.
Alan
Tür
status | Anlamı | content |
|---|---|---|
answered | Asistan yanıt verdi. | Yanıt metni |
handoff | Sohbet bu turda ekibe geçti ve ziyaretçiye bir mesaj yazıldı, genellikle devri söyleyen mesaj. | Devir mesajı |
human | Sohbet ekipte. Asistan yanıt vermedi; mesaj ekibe ulaştı. | Boş |
silent | Yanıt gerekmedi, örneğin "teşekkürler" gibi bir kapanış. | Boş |
handoff ve human durumunda ekibin yanıtlarını nasıl okuyacağınız İnsan devri ve ekip yanıtları sayfasında.
TypeScript'te bu alanları kendiniz tanımlayın:
import type { SoneMessage } from './sone-message'; // Zengin mesajlar sayfasındaki tanım
export type SoneStatus = 'answered' | 'handoff' | 'human' | 'silent';
// Akışsız yanıtta choices[0].message.sone
export type SoneTurn = {
conversation_id: string;
status: SoneStatus;
messages: SoneMessage[];
};
// Akışta bir parçanın üst düzeyindeki sone: her parçada yalnız biri dolu
export type SoneChunk = {
message?: SoneMessage; // bir mesaj tamamlandı
activity?: string; // asistan bir işe bakıyor: "Ürünlere bakılıyor"
conversation_id?: string; // son parçada
status?: SoneStatus; // son parçada
};
export const soneOfMessage = (message: unknown) => (message as { sone?: SoneTurn }).sone;
export const soneOfChunk = (chunk: unknown) => (chunk as { sone?: SoneChunk }).sone;Python'da OpenAI modelleri bilinmeyen alanları korur:
sone = completion.choices[0].message.to_dict().get("sone", {})
print(sone.get("conversation_id"), sone.get("status"))Akış
"stream": true ile yanıt text/event-stream olarak gelir. Parçalar şu sırayla gelir:
- İlk parça, anahtar, sınır ve paket kontrolleri geçip asistan yanıtlamaya başladığında gelir:
deltarolü taşır. Bu kontrollerden geçmeyen istek akış yerine sıradan bir HTTP hatası alır. - Asistan bir araç kullanırken durum parçası gelebilir:
deltaboştur, üst düzeydekisone.activity"Ürünlere bakılıyor" gibi kısa bir metindir. - Her mesajın metni kelime grupları halinde
delta.contentolarak akar. İkinci ve sonraki mesajlar\n\nile başlar. Bir mesajın son parçası ayrıca üst düzeydesone.messagetaşır: o mesajın tamamı,SoneMessagebiçiminde. Metni boş bir mesaj,delta'sı boş tek bir parçayla gelir. - Son parça:
finish_reason"stop",deltaboş, üst düzeydesoneiçindeconversation_idvestatus. stream_options.include_usageaçıksachoicesdizisi boş,usagedolu bir parça daha gelir. Bu durumda önceki bütün parçalarda"usage": nullvardır.- Akış
data: [DONE]ile biter.
sone her zaman parçanın üst düzeyindedir, choices[0].delta içinde değildir. Sohbetin kimliği akış başlarken X-Sone-Conversation-Id yanıt başlığında da gelir.
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":null}],"sone":{"activity":"Çalışma saatlerine bakılıyor"}}
: ping
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{"content":"Evet, cumartesi 10.00 ile "},"logprobs":null,"finish_reason":null}]}
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{"content":"18.00 arası açığız. Pazar "},"logprobs":null,"finish_reason":null}]}
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{"content":"kapalıyız."},"logprobs":null,"finish_reason":null}],"sone":{"message":{"id":"msg_p4q7w1x8k2m9a5z","role":"assistant","type":"text","text":"Evet, cumartesi 10.00 ile 18.00 arası açığız. Pazar kapalıyız.","created_at":1760866000}}}
data: {"id":"chatcmpl-9f2c","object":"chat.completion.chunk","created":1760866000,"model":"k3m9x2p7q1w8e4r","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"sone":{"conversation_id":"conv_x8k2m9p4q7w1r3t","status":"answered"}}
data: [DONE]Örnekteki kimlikler kısaltıldı.
import type { SoneMessage } from './sone-message';
import { soneOfChunk } from './sone';
const stream = await client.chat.completions.create({
model: 'default',
messages: [{ role: 'user', content: 'Bu hafta sonu açık mısınız?' }],
stream: true,
});
const messages: SoneMessage[] = [];
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta.content ?? '');
const sone = soneOfChunk(chunk);
if (sone?.message) messages.push(sone.message); // zengin mesajı burada çizin
if (sone?.activity) console.log(`\n${sone.activity}…`);
if (sone?.status) console.log('\nsohbet:', sone.conversation_id, sone.status);
}Akış başladıktan sonra bir hata olursa data: {"error": {…}} parçası ve ardından data: [DONE] gelir. OpenAI SDK'ları bu parçayı hata olarak fırlatır. Ayrıntılar: Hatalar.
Metnin neden kelime kelime değil mesaj mesaj ilerlediği ve bağlantının nasıl canlı tutulduğu: Akış.
Tekrar denemek
Her yeni mesajda yeni bir Idempotency-Key gönderin, en fazla 200 karakter. Ağ hatasından sonra aynı mesajı yeniden gönderirken aynı değeri koyun. Sonecentral aynı mesajı ikinci kez kaydetmez: yanıt verilmişse aynı yanıt yeniden döner, asistan iki kez yanıt vermez. Aynı anahtarı başka bir metinle gönderirseniz istek 422 idempotency_key_reused alır. Tarayıcı anahtarıyla, sohbetin kimliği olmadan başka bir ağ adresinden gelen deneme, örneğin sohbetin ilk mesajı, yeni sohbet açar: Tekrar denemek.
OpenAI SDK'ları bu başlığı kendileri eklemez. İsteğin seçeneklerinde verin; SDK'nın kendi yeniden denemeleri de aynı başlığı gönderir:
const key = crypto.randomUUID(); // mesaj başına bir kez üretin, yeniden denerken aynısını kullanın
const completion = await client.chat.completions.create(
{
model: 'default',
metadata: { conversation_id: 'conv_x8k2m9p4q7w1r3t' },
messages: [{ role: 'user', content: 'Bu hafta sonu açık mısınız?' }],
},
{ headers: { 'Idempotency-Key': key } },
);Bütün geçmişi ve sohbetin kimliğini her istekte gönderiyorsanız Sonecentral, ilk mesaj dışında, yeniden gönderilen mesajı geçmişin sonuna bakarak da tanır. Kimliği olmadan yalnız geçmişle gelen adsız bir deneme sohbeti bulmaz: yeni bir sohbet açar ve asistan yeniden yanıt verir. Sohbetin ilk mesajı, yalnız yeni mesajı gönderen ve kimliği saklamayan istemciler için tek güvenli yol anahtardır; bu yüzden her mesajda gönderin. Ayrıntılar: Tekrar denemek.
Hatalar
| Durum | code | Ne zaman |
|---|---|---|
400 | missing_user_message | Son mesaj metin taşıyan bir user mesajı değil. |
400 | invalid_role | Tanınmayan bir mesaj rolü. |
400 | invalid_content | Bir mesajın content alanı metin ya da parça dizisi değil. |
400 | unsupported_content | Yeni mesajda görsel, ses ya da dosya var. |
400 | message_too_long | Yeni bir mesaj 4.000 karakterden uzun. |
400 | too_many_new_messages | Son asistan mesajından sonra 12'den fazla user mesajı var. |
400 | tools_not_supported | tools ya da functions gönderildi. |
400 | unsupported_parameter | n 1 dışında ya da response_format text dışında. |
400 | conversation_model_mismatch | Sohbet başka bir asistanla açılmış. |
404 | model_not_found | Bu kimlikte, anahtarın çağırabildiği yayında bir asistan yok. |
404 | conversation_not_found | Gönderilen sohbet kimliği bu çalışma alanında bir API sohbeti değil. |
409 | conversation_busy | Aynı sohbette önceki mesaj hâlâ yanıtlanıyor. |
422 | idempotency_key_reused | Aynı Idempotency-Key başka bir metinle gönderildi. |
Bütün kodlar ve ne yapılacağı: Hatalar.