Files
tatlock/src/chat/service.py
T
jpmschweitzerandClaude cf6aa9a5e7 Implement chat completions domain with mock responses
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>
2025-12-06 10:36:57 +01:00

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,
)
],
)