Add OpenAI-compatible chat completions endpoint with streaming support. Currently returns mock lorem ipsum responses (Ollama integration pending). Chat Router (src/chat/router.py): - POST /v1/chat/completions endpoint - Streaming and non-streaming support - SSE format with EventSourceResponse - 20-second timeout protection - OpenAI-compatible response format Chat Schemas (src/chat/schemas.py): - ChatMessage, ChatCompletionRequest - ChatCompletionResponse, ChatCompletionChoice - ChatCompletionChunk for streaming - Full OpenAI API compatibility Chat Service (src/chat/service.py): - create_chat_completion() - non-streaming - create_chat_completion_stream() - streaming word-by-word - Mock lorem ipsum responses - Token usage calculation Chat Constants (src/chat/constants.py): - OpenAI API constants for consistency - Object types, roles, finish reasons Following Best Practices: - Business logic in service layer - Router only handles HTTP concerns - Async generators for streaming - Type hints throughout Model: mistral-nemo:latest Status: Mock implementation (ready for Ollama integration) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
133 lines
3.7 KiB
Python
133 lines
3.7 KiB
Python
"""
|
|
Chat completion service.
|
|
Currently returns mock responses with lorem ipsum.
|
|
TODO: Integrate with Ollama/PydanticAI in future.
|
|
"""
|
|
import time
|
|
import uuid
|
|
from typing import AsyncGenerator
|
|
|
|
from src.chat import constants
|
|
from src.chat.schemas import (
|
|
ChatCompletionChunk,
|
|
ChatCompletionChunkChoice,
|
|
ChatCompletionChunkDelta,
|
|
ChatCompletionChoice,
|
|
ChatCompletionRequest,
|
|
ChatCompletionResponse,
|
|
ChatCompletionUsage,
|
|
ChatMessage,
|
|
)
|
|
|
|
|
|
# Mock lorem ipsum response
|
|
MOCK_RESPONSE = (
|
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
|
|
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "
|
|
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris."
|
|
)
|
|
|
|
|
|
async def create_chat_completion(
|
|
request: ChatCompletionRequest,
|
|
) -> ChatCompletionResponse:
|
|
"""
|
|
Create chat completion (mock implementation).
|
|
|
|
Args:
|
|
request: Chat completion request
|
|
|
|
Returns:
|
|
Mock chat completion response with lorem ipsum
|
|
"""
|
|
completion_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
created_at = int(time.time())
|
|
|
|
return ChatCompletionResponse(
|
|
id=completion_id,
|
|
object=constants.CHAT_COMPLETION_OBJECT,
|
|
created=created_at,
|
|
model=request.model,
|
|
choices=[
|
|
ChatCompletionChoice(
|
|
index=0,
|
|
message=ChatMessage(
|
|
role=constants.ROLE_ASSISTANT,
|
|
content=MOCK_RESPONSE,
|
|
),
|
|
finish_reason=constants.FINISH_REASON_STOP,
|
|
)
|
|
],
|
|
usage=ChatCompletionUsage(
|
|
prompt_tokens=len(" ".join(m.content for m in request.messages).split()),
|
|
completion_tokens=len(MOCK_RESPONSE.split()),
|
|
total_tokens=len(" ".join(m.content for m in request.messages).split())
|
|
+ len(MOCK_RESPONSE.split()),
|
|
),
|
|
)
|
|
|
|
|
|
async def create_chat_completion_stream(
|
|
request: ChatCompletionRequest,
|
|
) -> AsyncGenerator[ChatCompletionChunk, None]:
|
|
"""
|
|
Create streaming chat completion (mock implementation).
|
|
|
|
Args:
|
|
request: Chat completion request with stream=True
|
|
|
|
Yields:
|
|
Mock chat completion chunks with lorem ipsum
|
|
"""
|
|
completion_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
created_at = int(time.time())
|
|
|
|
# Split response into words for streaming simulation
|
|
words = MOCK_RESPONSE.split()
|
|
|
|
# First chunk with role
|
|
yield ChatCompletionChunk(
|
|
id=completion_id,
|
|
object=constants.CHAT_COMPLETION_CHUNK_OBJECT,
|
|
created=created_at,
|
|
model=request.model,
|
|
choices=[
|
|
ChatCompletionChunkChoice(
|
|
index=0,
|
|
delta=ChatCompletionChunkDelta(role=constants.ROLE_ASSISTANT),
|
|
finish_reason=None,
|
|
)
|
|
],
|
|
)
|
|
|
|
# Stream words
|
|
for word in words:
|
|
yield ChatCompletionChunk(
|
|
id=completion_id,
|
|
object=constants.CHAT_COMPLETION_CHUNK_OBJECT,
|
|
created=created_at,
|
|
model=request.model,
|
|
choices=[
|
|
ChatCompletionChunkChoice(
|
|
index=0,
|
|
delta=ChatCompletionChunkDelta(content=f"{word} "),
|
|
finish_reason=None,
|
|
)
|
|
],
|
|
)
|
|
|
|
# Final chunk with finish_reason
|
|
yield ChatCompletionChunk(
|
|
id=completion_id,
|
|
object=constants.CHAT_COMPLETION_CHUNK_OBJECT,
|
|
created=created_at,
|
|
model=request.model,
|
|
choices=[
|
|
ChatCompletionChunkChoice(
|
|
index=0,
|
|
delta=ChatCompletionChunkDelta(),
|
|
finish_reason=constants.FINISH_REASON_STOP,
|
|
)
|
|
],
|
|
)
|