axiom-coding-agent-setup 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,508 @@
1
+ # AI Engineering with Python — Project Conventions
2
+
3
+ Context template for Python-based AI/ML engineering projects.
4
+
5
+ ---
6
+
7
+ ## Core Stack
8
+
9
+ ### Language & Runtime
10
+ - **Python 3.11+** — Modern Python with improved performance and typing
11
+ - **uv** — Fast Python package manager (replaces pip/poetry)
12
+ - **Ruff** — Linter and formatter (replaces flake8, black, isort)
13
+ - **pyright** or **mypy** — Type checking
14
+
15
+ ### Web Framework
16
+ - **FastAPI** — Async, type-annotated, automatic OpenAPI docs
17
+ - **Pydantic v2** — Data validation and settings management
18
+ - **Uvicorn** — ASGI server with hot reload in dev
19
+
20
+ ### AI/ML Stack
21
+ - **LangChain / LangGraph** — LLM orchestration and agent workflows
22
+ - **OpenAI SDK / Anthropic SDK** — Direct LLM API access
23
+ - **LiteLLM** — Provider-agnostic LLM proxy
24
+ - **Hugging Face Transformers** — Open-source model inference
25
+ - **ChromaDB / pgvector** — Vector storage for RAG
26
+
27
+ ### Development Tools
28
+ - **pytest** — Testing framework
29
+ - **pytest-asyncio** — Async test support
30
+ - **httpx** — Async HTTP client for testing APIs
31
+ - **python-dotenv** — Environment variable management
32
+
33
+ ---
34
+
35
+ ## Project Structure
36
+
37
+ ```
38
+ project-root/
39
+ ├── src/
40
+ │ ├── api/ # FastAPI application
41
+ │ │ ├── __init__.py
42
+ │ │ ├── main.py # App entry point
43
+ │ │ ├── deps.py # Dependencies (DB, auth)
44
+ │ │ └── routers/ # API route modules
45
+ │ │ ├── __init__.py
46
+ │ │ ├── chat.py # Chat/LLM endpoints
47
+ │ │ ├── rag.py # RAG endpoints
48
+ │ │ └── health.py # Health checks
49
+ │ ├── core/ # Core business logic
50
+ │ │ ├── __init__.py
51
+ │ │ ├── config.py # Pydantic settings
52
+ │ │ ├── exceptions.py # Custom exceptions
53
+ │ │ └── security.py # Auth utilities
54
+ │ ├── services/ # Business services
55
+ │ │ ├── __init__.py
56
+ │ │ ├── llm/ # LLM-related services
57
+ │ │ │ ├── __init__.py
58
+ │ │ │ ├── client.py # LLM client wrapper
59
+ │ │ │ ├── chains.py # LangChain chains
60
+ │ │ │ └── agents.py # Agent definitions
61
+ │ │ ├── rag/ # RAG services
62
+ │ │ │ ├── __init__.py
63
+ │ │ │ ├── embeddings.py # Embedding generation
64
+ │ │ │ ├── retriever.py # Document retrieval
65
+ │ │ │ └── indexer.py # Document indexing
66
+ │ │ └── embeddings/ # Embedding model management
67
+ │ ├── models/ # Pydantic models
68
+ │ │ ├── __init__.py
69
+ │ │ ├── requests.py # API request schemas
70
+ │ │ ├── responses.py # API response schemas
71
+ │ │ └── domain.py # Domain models
72
+ │ ├── db/ # Database layer
73
+ │ │ ├── __init__.py
74
+ │ │ ├── connection.py # DB connection management
75
+ │ │ ├── models.py # SQLAlchemy/ORM models
76
+ │ │ └── repositories.py # Data access layer
77
+ │ └── utils/ # Utilities
78
+ │ ├── __init__.py
79
+ │ ├── logging.py # Logging configuration
80
+ │ └── helpers.py # Helper functions
81
+ ├── tests/
82
+ │ ├── __init__.py
83
+ │ ├── conftest.py # pytest fixtures
84
+ │ ├── test_api/ # API/integration tests
85
+ │ └── test_services/ # Unit tests
86
+ ├── scripts/ # Utility scripts
87
+ │ ├── seed_db.py
88
+ │ └── index_documents.py
89
+ ├── notebooks/ # Jupyter notebooks for exploration
90
+ ├── docs/ # Documentation
91
+ ├── .env # Environment variables (gitignored)
92
+ ├── .env.example # Example env file
93
+ ├── pyproject.toml # Project config and dependencies
94
+ ├── README.md
95
+ └── Dockerfile
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Development Guidelines
101
+
102
+ ### Configuration (Pydantic Settings)
103
+
104
+ ```python
105
+ # src/core/config.py
106
+ from pydantic_settings import BaseSettings
107
+ from functools import lru_cache
108
+
109
+ class Settings(BaseSettings):
110
+ app_name: str = "AI API"
111
+ debug: bool = False
112
+
113
+ # API Keys
114
+ openai_api_key: str | None = None
115
+ anthropic_api_key: str | None = None
116
+
117
+ # Database
118
+ database_url: str = "postgresql://localhost/aiapp"
119
+
120
+ # Vector DB
121
+ vector_db_path: str = "./chroma_db"
122
+
123
+ class Config:
124
+ env_file = ".env"
125
+
126
+ @lru_cache
127
+ def get_settings() -> Settings:
128
+ return Settings()
129
+ ```
130
+
131
+ ### FastAPI App Structure
132
+
133
+ ```python
134
+ # src/api/main.py
135
+ from fastapi import FastAPI
136
+ from contextlib import asynccontextmanager
137
+
138
+ from api.routers import chat, rag, health
139
+ from core.config import get_settings
140
+
141
+ settings = get_settings()
142
+
143
+ @asynccontextmanager
144
+ async def lifespan(app: FastAPI):
145
+ # Startup: initialize connections, load models
146
+ yield
147
+ # Shutdown: cleanup
148
+
149
+ app = FastAPI(
150
+ title=settings.app_name,
151
+ debug=settings.debug,
152
+ lifespan=lifespan
153
+ )
154
+
155
+ app.include_router(health.router, prefix="/health", tags=["health"])
156
+ app.include_router(chat.router, prefix="/api/v1/chat", tags=["chat"])
157
+ app.include_router(rag.router, prefix="/api/v1/rag", tags=["rag"])
158
+ ```
159
+
160
+ ### Router Pattern
161
+
162
+ ```python
163
+ # src/api/routers/chat.py
164
+ from fastapi import APIRouter, Depends, HTTPException
165
+ from pydantic import BaseModel
166
+
167
+ from services.llm.client import LLMClient
168
+ from models.requests import ChatRequest
169
+ from models.responses import ChatResponse
170
+
171
+ router = APIRouter()
172
+
173
+ @router.post("/", response_model=ChatResponse)
174
+ async def chat(
175
+ request: ChatRequest,
176
+ llm: LLMClient = Depends(get_llm_client)
177
+ ) -> ChatResponse:
178
+ try:
179
+ response = await llm.generate(
180
+ messages=request.messages,
181
+ model=request.model
182
+ )
183
+ return ChatResponse(content=response.content)
184
+ except Exception as e:
185
+ raise HTTPException(status_code=500, detail=str(e))
186
+ ```
187
+
188
+ ### LLM Client Wrapper
189
+
190
+ ```python
191
+ # src/services/llm/client.py
192
+ from typing import AsyncGenerator
193
+ import openai
194
+ from anthropic import AsyncAnthropic
195
+
196
+ from core.config import get_settings
197
+
198
+ class LLMClient:
199
+ def __init__(self):
200
+ settings = get_settings()
201
+ self.openai = openai.AsyncOpenAI(api_key=settings.openai_api_key)
202
+ self.anthropic = AsyncAnthropic(api_key=settings.anthropic_api_key)
203
+
204
+ async def generate(
205
+ self,
206
+ messages: list[dict],
207
+ model: str = "gpt-4",
208
+ stream: bool = False
209
+ ) -> str:
210
+ if model.startswith("claude"):
211
+ return await self._generate_anthropic(messages, model)
212
+ return await self._generate_openai(messages, model)
213
+
214
+ async def _generate_openai(self, messages, model):
215
+ response = await self.openai.chat.completions.create(
216
+ model=model,
217
+ messages=messages
218
+ )
219
+ return response.choices[0].message.content
220
+
221
+ async def stream_generate(
222
+ self,
223
+ messages: list[dict],
224
+ model: str = "gpt-4"
225
+ ) -> AsyncGenerator[str, None]:
226
+ stream = await self.openai.chat.completions.create(
227
+ model=model,
228
+ messages=messages,
229
+ stream=True
230
+ )
231
+ async for chunk in stream:
232
+ if chunk.choices[0].delta.content:
233
+ yield chunk.choices[0].delta.content
234
+
235
+ def get_llm_client() -> LLMClient:
236
+ return LLMClient()
237
+ ```
238
+
239
+ ### RAG Pattern with ChromaDB
240
+
241
+ ```python
242
+ # src/services/rag/retriever.py
243
+ import chromadb
244
+ from chromadb.config import Settings
245
+
246
+ from core.config import get_settings
247
+
248
+ class RAGRetriever:
249
+ def __init__(self):
250
+ settings = get_settings()
251
+ self.client = chromadb.PersistentClient(
252
+ path=settings.vector_db_path,
253
+ settings=Settings(anonymized_telemetry=False)
254
+ )
255
+ self.collection = self.client.get_or_create_collection("documents")
256
+
257
+ async def add_documents(
258
+ self,
259
+ documents: list[str],
260
+ embeddings: list[list[float]],
261
+ ids: list[str],
262
+ metadatas: list[dict] | None = None
263
+ ):
264
+ self.collection.add(
265
+ documents=documents,
266
+ embeddings=embeddings,
267
+ ids=ids,
268
+ metadatas=metadatas
269
+ )
270
+
271
+ async def query(
272
+ self,
273
+ query_embedding: list[float],
274
+ n_results: int = 5
275
+ ) -> list[dict]:
276
+ results = self.collection.query(
277
+ query_embeddings=[query_embedding],
278
+ n_results=n_results
279
+ )
280
+ return [
281
+ {
282
+ "document": doc,
283
+ "metadata": meta,
284
+ "distance": dist
285
+ }
286
+ for doc, meta, dist in zip(
287
+ results["documents"][0],
288
+ results["metadatas"][0],
289
+ results["distances"][0]
290
+ )
291
+ ]
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Testing Patterns
297
+
298
+ ### pytest Configuration
299
+
300
+ ```toml
301
+ # pyproject.toml
302
+ [tool.pytest.ini_options]
303
+ testpaths = ["tests"]
304
+ python_files = ["test_*.py"]
305
+ python_functions = ["test_*"]
306
+ addopts = "-v --tb=short"
307
+ asyncio_mode = "auto"
308
+ ```
309
+
310
+ ### Test Fixtures
311
+
312
+ ```python
313
+ # tests/conftest.py
314
+ import pytest
315
+ from fastapi.testclient import TestClient
316
+ from httpx import AsyncClient
317
+
318
+ from api.main import app
319
+
320
+ @pytest.fixture
321
+ def client():
322
+ return TestClient(app)
323
+
324
+ @pytest.fixture
325
+ async def async_client():
326
+ async with AsyncClient(app=app, base_url="http://test") as client:
327
+ yield client
328
+
329
+ @pytest.fixture
330
+ def mock_llm_response():
331
+ return {
332
+ "content": "Test response",
333
+ "model": "gpt-4",
334
+ "usage": {"prompt_tokens": 10, "completion_tokens": 20}
335
+ }
336
+ ```
337
+
338
+ ### API Tests
339
+
340
+ ```python
341
+ # tests/test_api/test_chat.py
342
+ import pytest
343
+ from unittest.mock import AsyncMock, patch
344
+
345
+ @pytest.mark.asyncio
346
+ async def test_chat_endpoint(async_client, mock_llm_response):
347
+ with patch("services.llm.client.LLMClient.generate", new_callable=AsyncMock) as mock:
348
+ mock.return_value = mock_llm_response
349
+
350
+ response = await async_client.post("/api/v1/chat/", json={
351
+ "messages": [{"role": "user", "content": "Hello"}],
352
+ "model": "gpt-4"
353
+ })
354
+
355
+ assert response.status_code == 200
356
+ assert response.json()["content"] == "Test response"
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Environment Setup
362
+
363
+ ### .env.example
364
+
365
+ ```bash
366
+ # App
367
+ APP_NAME="AI API"
368
+ DEBUG=true
369
+
370
+ # API Keys
371
+ OPENAI_API_KEY=sk-...
372
+ ANTHROPIC_API_KEY=sk-ant-...
373
+
374
+ # Database
375
+ DATABASE_URL=postgresql://user:pass@localhost/aiapp
376
+
377
+ # Vector DB
378
+ VECTOR_DB_PATH=./chroma_db
379
+ ```
380
+
381
+ ### pyproject.toml Dependencies
382
+
383
+ ```toml
384
+ [project]
385
+ name = "ai-engineering-api"
386
+ version = "0.1.0"
387
+ description = "AI Engineering API"
388
+ requires-python = ">=3.11"
389
+ dependencies = [
390
+ # Web
391
+ "fastapi>=0.104.0",
392
+ "uvicorn[standard]>=0.24.0",
393
+ "pydantic>=2.0.0",
394
+ "pydantic-settings>=2.0.0",
395
+
396
+ # AI/ML
397
+ "openai>=1.0.0",
398
+ "anthropic>=0.8.0",
399
+ "langchain>=0.1.0",
400
+ "langgraph>=0.0.40",
401
+ "chromadb>=0.4.0",
402
+ "sentence-transformers>=2.2.0",
403
+ "litellm>=1.0.0",
404
+
405
+ # Database
406
+ "sqlalchemy>=2.0.0",
407
+ "asyncpg>=0.29.0",
408
+ "alembic>=1.12.0",
409
+
410
+ # Utils
411
+ "python-dotenv>=1.0.0",
412
+ "structlog>=23.0.0",
413
+ "orjson>=3.9.0",
414
+ ]
415
+
416
+ [project.optional-dependencies]
417
+ dev = [
418
+ "pytest>=7.4.0",
419
+ "pytest-asyncio>=0.21.0",
420
+ "httpx>=0.25.0",
421
+ "ruff>=0.1.0",
422
+ "pyright>=1.1.0",
423
+ ]
424
+
425
+ [tool.ruff]
426
+ line-length = 88
427
+ target-version = "py311"
428
+ select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
429
+ ignore = ["E501"]
430
+
431
+ [tool.ruff.format]
432
+ quote-style = "double"
433
+ indent-style = "space"
434
+
435
+ [tool.pyright]
436
+ pythonVersion = "3.11"
437
+ strict = ["src"]
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Docker Setup
443
+
444
+ ```dockerfile
445
+ # Dockerfile
446
+ FROM python:3.11-slim
447
+
448
+ WORKDIR /app
449
+
450
+ # Install uv
451
+ RUN pip install uv
452
+
453
+ # Copy dependency files
454
+ COPY pyproject.toml ./
455
+
456
+ # Install dependencies
457
+ RUN uv pip install --system -e ".[dev]"
458
+
459
+ # Copy source
460
+ COPY src/ ./src/
461
+
462
+ # Run
463
+ CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "8000"]
464
+ ```
465
+
466
+ ---
467
+
468
+ ## Code Quality Standards
469
+
470
+ ### Ruff Configuration (in pyproject.toml)
471
+ - Line length: 88 (Black-compatible)
472
+ - Target Python: 3.11+
473
+ - Enable: E, F, I (isort), N (pep8-naming), W, UP (pyupgrade), B (flake8-bugbear)
474
+
475
+ ### Type Hints
476
+ - Use `|` union syntax (Python 3.10+): `str | None`
477
+ - Use built-in generics: `list[str]`, `dict[str, int]`
478
+ - Annotate all function signatures
479
+ - Use Pydantic models for complex data structures
480
+
481
+ ### Async Patterns
482
+ - Use `async`/`await` for all I/O operations
483
+ - Prefer `asyncio.gather()` for concurrent operations
484
+ - Use `asynccontextmanager` for resource management
485
+ - Don't use `asyncio.run()` in FastAPI handlers (already in async context)
486
+
487
+ ### Error Handling
488
+ ```python
489
+ # Custom exceptions
490
+ class LLMError(Exception):
491
+ """Base class for LLM-related errors"""
492
+ pass
493
+
494
+ class RateLimitError(LLMError):
495
+ """API rate limit exceeded"""
496
+ pass
497
+
498
+ # Usage in services
499
+ from fastapi import HTTPException
500
+
501
+ try:
502
+ result = await llm.generate(messages)
503
+ except RateLimitError as e:
504
+ raise HTTPException(status_code=429, detail="Rate limit exceeded")
505
+ except LLMError as e:
506
+ logger.error(f"LLM error: {e}")
507
+ raise HTTPException(status_code=502, detail="LLM service error")
508
+ ```