Skip to content

Commit d27a14f

Browse files
committed
Initial commit
1 parent ba085a8 commit d27a14f

26 files changed

+1745
-0
lines changed

cx-agent-backend/Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use ARM64 Python 3.11 base image with uv pre-installed
2+
FROM --platform=linux/arm64 ghcr.io/astral-sh/uv:python3.11-bookworm-slim
3+
4+
# Set working directory
5+
WORKDIR /app
6+
7+
# Configure uv to install packages globally and compile bytecode for performance
8+
ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
9+
ENV UV_COMPILE_BYTECODE=1
10+
11+
# Copy dependency configuration and install Python dependencies
12+
COPY pyproject.toml .
13+
RUN uv sync
14+
15+
# Copy application source code
16+
COPY src/ ./src/
17+
18+
# Set Python path
19+
ENV PYTHONPATH="/app/src"
20+
21+
# Expose FastAPI server port
22+
EXPOSE 8080
23+
24+
# Start the CX Agent service
25+
CMD ["python", "src/run.py"]

cx-agent-backend/pyproject.toml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,45 @@
1+
[project]
2+
name = "cx-agent-backend"
3+
version = "0.1.0"
4+
description = "Clean Architecture CX Agent Backend"
5+
requires-python = ">=3.11"
6+
dependencies = [
7+
"fastapi>=0.104.0",
8+
"uvicorn[standard]>=0.24.0",
9+
"pydantic>=2.5.0",
10+
"pydantic-settings>=2.1.0",
11+
"langchain>=0.1.0",
12+
"langchain-core>=0.1.0",
13+
"langchain-openai>=0.1.0",
14+
"langchain-aws>=0.1.0",
15+
"langgraph>=0.1.0",
16+
"langfuse>=2.0.0",
17+
"boto3>=1.34.0",
18+
"structlog>=23.2.0",
19+
"dependency-injector>=4.41.0",
20+
"tavily-python>=0.3.0",
21+
]
122

23+
[project.optional-dependencies]
24+
dev = [
25+
"pytest>=7.4.0",
26+
"pytest-asyncio>=0.21.0",
27+
"httpx>=0.25.0",
28+
"ruff>=0.1.0",
29+
"mypy>=1.7.0",
30+
]
31+
32+
[build-system]
33+
requires = ["hatchling"]
34+
build-backend = "hatchling.build"
35+
36+
[tool.hatch.build.targets.wheel]
37+
packages = ["src"]
38+
39+
[tool.ruff]
40+
target-version = "py311"
41+
line-length = 88
42+
43+
[tool.mypy]
44+
python_version = "3.11"
45+
strict = true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Domain layer
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Domain entities for conversation management."""
2+
3+
from dataclasses import dataclass
4+
from datetime import datetime
5+
from enum import Enum
6+
from typing import Any
7+
from uuid import UUID, uuid4
8+
9+
10+
class MessageRole(str, Enum):
11+
"""Message role enumeration."""
12+
13+
USER = "user"
14+
ASSISTANT = "assistant"
15+
SYSTEM = "system"
16+
17+
18+
class ConversationStatus(str, Enum):
19+
"""Conversation status enumeration."""
20+
21+
ACTIVE = "active"
22+
COMPLETED = "completed"
23+
FAILED = "failed"
24+
25+
26+
@dataclass(frozen=True)
27+
class Message:
28+
"""Immutable message entity."""
29+
30+
id: UUID
31+
content: str
32+
role: MessageRole
33+
timestamp: datetime
34+
metadata: dict[str, Any]
35+
36+
@classmethod
37+
def create_user_message(
38+
cls, content: str, metadata: dict[str, Any] | None = None
39+
) -> "Message":
40+
"""Create a user message."""
41+
return cls(
42+
id=uuid4(),
43+
content=content,
44+
role=MessageRole.USER,
45+
timestamp=datetime.utcnow(),
46+
metadata=metadata or {},
47+
)
48+
49+
@classmethod
50+
def create_assistant_message(
51+
cls, content: str, metadata: dict[str, Any] | None = None
52+
) -> "Message":
53+
"""Create an assistant message."""
54+
return cls(
55+
id=uuid4(),
56+
content=content,
57+
role=MessageRole.ASSISTANT,
58+
timestamp=datetime.utcnow(),
59+
metadata=metadata or {},
60+
)
61+
62+
63+
@dataclass
64+
class Conversation:
65+
"""Mutable conversation aggregate."""
66+
67+
id: UUID
68+
user_id: str
69+
messages: list[Message]
70+
status: ConversationStatus
71+
created_at: datetime
72+
updated_at: datetime
73+
metadata: dict[str, Any]
74+
75+
@classmethod
76+
def create(
77+
cls, user_id: str, metadata: dict[str, Any] | None = None
78+
) -> "Conversation":
79+
"""Create a new conversation."""
80+
now = datetime.utcnow()
81+
return cls(
82+
id=uuid4(),
83+
user_id=user_id,
84+
messages=[],
85+
status=ConversationStatus.ACTIVE,
86+
created_at=now,
87+
updated_at=now,
88+
metadata=metadata or {},
89+
)
90+
91+
def add_message(self, message: Message) -> None:
92+
"""Add a message to the conversation."""
93+
self.messages.append(message)
94+
self.updated_at = datetime.utcnow()
95+
96+
def complete(self) -> None:
97+
"""Mark conversation as completed."""
98+
self.status = ConversationStatus.COMPLETED
99+
self.updated_at = datetime.utcnow()
100+
101+
def fail(self) -> None:
102+
"""Mark conversation as failed."""
103+
self.status = ConversationStatus.FAILED
104+
self.updated_at = datetime.utcnow()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class SecretReader(ABC):
5+
@abstractmethod
6+
def read_secret(self, name: str) -> str:
7+
"""Get secret value by name."""
8+
pass
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Repository interfaces for conversation management."""
2+
3+
from abc import ABC, abstractmethod
4+
from uuid import UUID
5+
6+
from domain.entities.conversation import Conversation
7+
8+
9+
class ConversationRepository(ABC):
10+
"""Abstract repository for conversation persistence."""
11+
12+
@abstractmethod
13+
async def save(self, conversation: Conversation) -> None:
14+
"""Save a conversation."""
15+
pass
16+
17+
@abstractmethod
18+
async def get_by_id(self, conversation_id: UUID) -> Conversation | None:
19+
"""Get conversation by ID."""
20+
pass
21+
22+
@abstractmethod
23+
async def get_by_user_id(self, user_id: str) -> list[Conversation]:
24+
"""Get conversations by user ID."""
25+
pass
26+
27+
@abstractmethod
28+
async def delete(self, conversation_id: UUID) -> None:
29+
"""Delete a conversation."""
30+
pass
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Domain service interface for agent operations."""
2+
3+
from abc import ABC, abstractmethod
4+
from dataclasses import dataclass
5+
from enum import Enum
6+
7+
from domain.entities.conversation import Message
8+
9+
10+
class AgentType(str, Enum):
11+
"""Supported agent types."""
12+
13+
CUSTOMER_SERVICE = "customer_service"
14+
RESEARCH = "research"
15+
GENERAL = "general"
16+
17+
18+
@dataclass(frozen=True)
19+
class AgentRequest:
20+
"""Request for agent processing."""
21+
22+
messages: list[Message]
23+
agent_type: AgentType
24+
user_id: str
25+
model: str
26+
session_id: str | None = None
27+
28+
29+
@dataclass(frozen=True)
30+
class AgentResponse:
31+
("""Response from agent processing.""",)
32+
content: str
33+
agent_type: AgentType
34+
tools_used: list[str]
35+
metadata: dict[str, str]
36+
37+
38+
class AgentService(ABC):
39+
"""Abstract service for agent operations."""
40+
41+
@abstractmethod
42+
async def process_request(self, request: AgentRequest) -> AgentResponse:
43+
"""Process request through agent."""
44+
pass
45+
46+
@abstractmethod
47+
async def stream_response(self, request: AgentRequest):
48+
"""Stream response from agent."""
49+
pass

0 commit comments

Comments
 (0)