LeoLion3
02/14/2026, 1:19 AMJames Christopher
02/14/2026, 7:38 AMLeoLion3
02/14/2026, 9:12 AMpython
@router.post("/chat/completions")
async def get_completion(request: Request) -> JSONResponse:
if request.headers.get("content-type") != "application/json":
return JSONResponse(content={"error": "Invalid content type"}, status_code=403)
auth_header = request.headers.get("Authorization")
if auth_header != f"Bearer {config.BEARER_AUTH_TOKEN}":
return JSONResponse(content={"error": "Unauthorized"}, status_code=403)
data = await request.json()
message_obj:
# Custom, internal data model I use for easier processing. Irrelevant
ModelMessage = ModelMessage.to_obj(data)
function_call_response = {
"name": "end_call_tool",
"arguments": {},
"parameters": []
}
response_content = {
"id": uuid.uuid4().hex,
"object": "chat.completion",
"created": int(uuid.uuid1().time),
"model": "gpt-4.1",
"choices": [
{
"message": {
"role": "assistant",
"content": "Hello world!",
"function_call": function_call_response
},
"finish_reason": "stop"
}
]
}
return JSONResponse(content=response_content, status_code=200)James Christopher
02/14/2026, 5:21 PMLeoLion3
02/14/2026, 6:31 PMLeoLion3
02/14/2026, 6:32 PMLeoLion3
02/14/2026, 10:59 PMpython
router.post('/completions')
async def get_completion(request: Request) -> StreamingResponse:
if request.headers.get("content-type") != "application/json":
return JSONResponse(content={"ok": True}, status_code=403)
# Read the incoming request data
data = await request.json()
# TODO Process your data
response = {
"id": str(uuid.uuid4()),
"object": "chat.completion.chunk",
"created": time.time(),
"model": "gpt-4.1",
"choices": [
{
"index": 0,
"delta": {
"role": "assistant",
"content": "Hello world",
"refusal": None,
"tool_calls": None,
"function_call": None
},
"logprobs": None,
"finish_reason": "stop"
}
]
}
sse_chunk = f"data: {json.dumps(response)}\n\n"
def event_stream():
yield sse_chunk
time.sleep(0.1)
yield "data: [DONE]\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")
Note the slight delay between the yielded chunks.Chiranjeet Mishra
02/18/2026, 1:11 AMmetadata.sendMode: "off" on the Vapi side if you need the schema reference for building responses that include tool calling).
{
"model": "your-model-name",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant..."
},
{
"role": "user",
"content": "Hello, how are you?"
},
{
"role": "assistant",
"content": "I'm doing well, thank you!"
}
],
"temperature": 0.7,
"max_tokens": 1024,
"tools": [
{
"type": "function",
"function": {
"name": "book_appointment",
"description": "Book an appointment for the user",
"parameters": {
"type": "object",
"properties": {
"date": {
"type": "string"
},
"time": {
"type": "string"
}
},
"required": [
"date",
"time"
]
}
}
}
],
"stream": true
}
This should give you the expected structure for tool definitions. From there, your response payload (especially when streaming) needs to mirror the expected chat.completion.chunk format, including delta.role, delta.content, and/or delta.tool_calls depending on whether you're returning text or invoking a function.
If you’d like, share your current response body and I can help align it exactly to what Vapi expects.