Use when long-lived agents, project memories, system prompts, or playbooks are repeatedly summarized, rewritten, or optimized and risk losing domain detail, provenance, or execution learnings.
日本語の概要は準備中です。原文の説明を表示しています。
Also use for async Python patterns, Pydantic schemas for LLM structured outputs, retry strategies for LLM calls, background AI jobs, or any service combining FastAPI and LLMs. If Python + AI backend is in scope — use this skill
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Building AI-powered Python backends — the patterns that make them reliable, fast, and maintainable.
backend/
├── main.py # FastAPI app factory
├── routers/
│ ├── generate.py # AI generation endpoints
│ ├── jobs.py # Background job management
│ └── health.py # Health + readiness checks
├── services/
│ ├── llm.py # LLM client abstraction
│ ├── generator.py # Business logic for generation
│ └── storage.py # File / DB persistence
├── models/
│ ├── requests.py # Pydantic input models
│ └── responses.py # Pydantic output models
├── core/
│ ├── config.py # Settings (pydantic-settings)
│ ├── logging.py # Structured logging
│ └── exceptions.py # Custom exceptions + handlers
├── tests/
└── requirements.txt
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from core.config import settings
from routers import generate, jobs, health
@asynccontextmanager
async def lifespan(app: FastAPI):
# startup: initialize clients, warm caches
yield
# shutdown: close connections
app = FastAPI(title="AI Backend", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.allowed_origins,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(generate.router, prefix="/api/v1/generate", tags=["generate"])
app.include_router(jobs.router, prefix="/api/v1/jobs", tags=["jobs"])
app.include_router(health.router, tags=["health"])
# core/config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
# LLM
anthropic_api_key: str = ""
openai_api_key: str = ""
default_model: str = "claude-sonnet-4-6"
max_tokens: int = 8096
# App
allowed_origins: list[str] = ["http://localhost:5173"]
debug: bool = False
class Config:
env_file = ".env"
case_sensitive = False
@lru_cache
def get_settings() -> Settings:
return Settings()
settings = get_settings()
LiteLLM gives one unified API across Anthropic, OpenAI, Azure, Bedrock, etc.
# services/llm.py
import litellm
from litellm import acompletion, completion
from core.config import settings
litellm.drop_params = True # ignore unsupported params per provider
async def call_llm(
messages: list[dict],
model: str | None = None,
response_format: type | None = None, # Pydantic model for structured output
temperature: float = 0.2,
max_tokens: int | None = None,
) -> str:
response = await acompletion(
model=model or settings.default_model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens or settings.max_tokens,
response_format=response_format,
)
return response.choices[0].message.content
from pydantic import BaseModel, Field
from typing import Literal
class DbtModel(BaseModel):
model_name: str = Field(description="snake_case dbt model name")
materialization: Literal["view", "table", "incremental"] = "view"
sql: str = Field(description="The full SQL content of the model")
description: str
class GeneratedProject(BaseModel):
models: list[DbtModel]
sources_yaml: str
schema_yaml: str
issues: list[str] = Field(default_factory=list)
async def generate_dbt_project(mapping_json: dict) -> GeneratedProject:
response = await call_llm(
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Convert this mapping:\n{mapping_json}"}
],
response_format=GeneratedProject, # LiteLLM handles JSON schema injection
)
return GeneratedProject.model_validate_json(response)
import anthropic
from core.config import settings
client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key)
async def call_claude(
system: str,
user_message: str,
model: str = "claude-sonnet-4-6",
) -> str:
message = await client.messages.create(
model=model,
max_tokens=8096,
system=system,
messages=[{"role": "user", "content": user_message}],
)
return message.content[0].text
# Tool use (function calling)
async def call_claude_with_tools(
messages: list[dict],
tools: list[dict], # Anthropic tool schema format
) -> anthropic.types.Message:
return await client.messages.create(
model="claude-sonnet-4-6",
max_tokens=8096,
tools=tools,
messages=messages,
)
# routers/generate.py
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
import anthropic
import json
router = APIRouter()
client = anthropic.AsyncAnthropic()
@router.post("/stream")
async def generate_stream(request: GenerateRequest):
async def event_generator():
async with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=8096,
messages=[{"role": "user", "content": request.prompt}],
) as stream:
async for text in stream.text_stream:
yield f"data: {json.dumps({'text': text})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
# routers/generate.py
from fastapi import UploadFile, File, HTTPException
import json
@router.post("/from-mapping")
async def generate_from_mapping(file: UploadFile = File(...)):
if not file.filename.endswith((".json", ".xml")):
raise HTTPException(400, "Only .json or .xml mapping files supported")
content = await file.read()
try:
mapping = json.loads(content)
except json.JSONDecodeError:
mapping = content.decode("utf-8") # XML — parse downstream
result = await generator_service.process(mapping)
return result
# For generation tasks that take > 30s, use background jobs
from fastapi import BackgroundTasks
import uuid
from services.storage import job_store
@router.post("/async-generate")
async def start_generation(request: GenerateRequest, background_tasks: BackgroundTasks):
job_id = str(uuid.uuid4())
await job_store.create(job_id, status="pending")
background_tasks.add_task(run_generation, job_id, request)
return {"job_id": job_id, "status": "pending"}
@router.get("/jobs/{job_id}")
async def get_job_status(job_id: str):
job = await job_store.get(job_id)
if not job:
raise HTTPException(404, "Job not found")
return job
async def run_generation(job_id: str, request: GenerateRequest):
try:
await job_store.update(job_id, status="running")
result = await generator_service.process(request)
await job_store.update(job_id, status="done", result=result)
except Exception as e:
await job_store.update(job_id, status="error", error=str(e))
# services/llm.py
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
import litellm
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((litellm.RateLimitError, litellm.APIConnectionError)),
reraise=True,
)
async def call_llm_with_retry(messages: list[dict], **kwargs) -> str:
return await call_llm(messages, **kwargs)
# core/exceptions.py
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
import litellm
async def llm_exception_handler(request: Request, exc: litellm.APIError):
return JSONResponse(
status_code=502,
content={"error": "LLM provider error", "detail": str(exc)},
)
# In main.py:
app.add_exception_handler(litellm.APIError, llm_exception_handler)
# services/llm_monitor.py
import time
from dataclasses import dataclass, field
from datetime import datetime
import json
@dataclass
class LLMCall:
model: str
prompt_tokens: int = 0
completion_tokens: int = 0
latency_ms: float = 0
success: bool = True
error: str | None = None
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
class LLMMonitor:
def __init__(self, job_id: str):
self.job_id = job_id
self.calls: list[LLMCall] = []
async def track(self, coro, model: str) -> str:
start = time.monotonic()
try:
result = await coro
elapsed = (time.monotonic() - start) * 1000
# extract token counts if available
self.calls.append(LLMCall(model=model, latency_ms=elapsed))
return result
except Exception as e:
elapsed = (time.monotonic() - start) * 1000
self.calls.append(LLMCall(model=model, latency_ms=elapsed, success=False, error=str(e)))
raise
def summary(self) -> dict:
return {
"job_id": self.job_id,
"total_calls": len(self.calls),
"total_latency_ms": sum(c.latency_ms for c in self.calls),
"success_rate": sum(c.success for c in self.calls) / len(self.calls) if self.calls else 0,
}
System prompt structure — be explicit about output format:
SYSTEM_PROMPT = """
You are a dbt model generator. Given an Informatica mapping, produce a valid dbt project.
ALWAYS respond with valid JSON matching this schema:
{schema_json}
Rules:
- Use snake_case for all model names
- Always use ref() and source() — never hardcode schema/table
- Cast all columns to their final types in staging models
"""
Few-shot examples — include in the user message, not the system prompt:
def build_user_message(mapping: dict, examples: list[dict]) -> str:
parts = []
for ex in examples[:2]: # 2 examples is usually enough
parts.append(f"Input:\n{json.dumps(ex['input'])}\n\nOutput:\n{json.dumps(ex['output'])}")
parts.append(f"Input:\n{json.dumps(mapping)}\n\nOutput:")
return "\n\n---\n\n".join(parts)
Chain of thought for complex transformations:
# Add a "thinking" step before the final JSON output
messages = [
{"role": "user", "content": "Analyze this mapping and convert it..."},
# Let Claude reason freely first, then constrain the output
]
# Use extended thinking (Anthropic) or separate reasoning step
# tests/test_generate.py
import pytest
from httpx import AsyncClient
from main import app
@pytest.mark.asyncio
async def test_generate_from_mapping():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post(
"/api/v1/generate/from-mapping",
files={"file": ("mapping.json", b'{"sources": [...]}', "application/json")},
)
assert response.status_code == 200
result = response.json()
assert "models" in result
assert len(result["models"]) > 0
# Mock LLM calls in tests to avoid costs:
@pytest.fixture(autouse=True)
def mock_llm(monkeypatch):
async def fake_call_llm(messages, **kwargs):
return '{"models": [], "sources_yaml": "", "schema_yaml": ""}'
monkeypatch.setattr("services.llm.call_llm", fake_call_llm)
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml (dev)
services:
api:
build: .
ports: ["8000:8000"]
env_file: .env
volumes:
- .:/app # hot-reload in dev
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
fastapi>=0.115
uvicorn[standard]>=0.30
pydantic>=2.0
pydantic-settings>=2.0
litellm>=1.40 # multi-provider LLM unified API
anthropic>=0.40 # direct Anthropic SDK
tenacity>=8.0 # retry logic
httpx>=0.27 # async HTTP client
python-multipart # file uploads
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use when long-lived agents, project memories, system prompts, or playbooks are repeatedly summarized, rewritten, or optimized and risk losing domain detail, provenance, or execution learnings.
日本語の概要は準備中です。原文の説明を表示しています。
Use this skill when the user explicitly asks to map, document, or onboard into an existing codebase. Trigger for prompts like "map this codebase", "document this architecture", "onboard me to this repo", or "create codebase docs". Do not trigger for routine feature implementation, bug fixes, or narrow code edits unless the user asks for repository-level discovery.
日本語の概要は準備中です。原文の説明を表示しています。
Adds educational comments to a code file so it becomes a learning resource, asking for the file (with a list of close matches) if none is given. Use when preparing code for learners, onboarding, or teaching.
日本語の概要は準備中です。原文の説明を表示しています。
Write, debug, and optimize Adobe Illustrator automation scripts using ExtendScript (JavaScript/JSX). Use when creating or modifying scripts that manipulate documents, layers, paths, text frames, colors, symbols, artboards, or any Illustrator DOM objects. Covers the complete JavaScript object model, coordinate system, measurement units, export workflows, and scripting best practices.
日本語の概要は準備中です。原文の説明を表示しています。
Patterns and techniques for adding governance, safety, and trust controls to AI agent systems. Use this skill when: - Building AI agents that call external tools (APIs, databases, file systems) - Implementing policy-based access controls for agent tool usage - Adding semantic intent classification to detect dangerous prompts - Creating trust scoring systems for multi-agent workflows - Building audit trails for agent actions and decisions - Enforcing rate limits, content filters, or tool restrictions on agents - Working with any agent framework (PydanticAI, CrewAI, OpenAI Agents, LangChain, AutoGen)
日本語の概要は準備中です。原文の説明を表示しています。
Check any AI agent codebase against the OWASP Agentic Security Initiative (ASI) Top 10 risks. Use this skill when: - Evaluating an agent system's security posture before production deployment - Running a compliance check against OWASP ASI 2026 standards - Mapping existing security controls to the 10 agentic risks - Generating a compliance report for security review or audit - Comparing agent framework security features against the standard - Any request like "is my agent OWASP compliant?", "check ASI compliance", or "agentic security audit"
日本語の概要は準備中です。原文の説明を表示しています。