Commit ·
4aed5f4
0
Parent(s):
feat: initial release of Multiverse AI Studio
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .agents/AGENTS.md +84 -0
- .gitignore +22 -0
- Dockerfile +35 -0
- README.md +148 -0
- REPORT.md +116 -0
- TRAE-Skills/LICENSE +64 -0
- TRAE-Skills/README.md +240 -0
- TRAE-Skills/ai_engineering/AI_Agent_Design_Patterns.md +99 -0
- TRAE-Skills/ai_engineering/AI_Agents_LangGraph.md +232 -0
- TRAE-Skills/ai_engineering/AI_Experiment_Tracking.md +640 -0
- TRAE-Skills/ai_engineering/AI_Model_Evaluation.md +107 -0
- TRAE-Skills/ai_engineering/AI_Model_Serving.md +503 -0
- TRAE-Skills/ai_engineering/AI_Monitoring_Observability.md +536 -0
- TRAE-Skills/ai_engineering/AI_Pipeline_Automation.md +562 -0
- TRAE-Skills/ai_engineering/AI_Safety_Ethics.md +379 -0
- TRAE-Skills/ai_engineering/AI_Testing_Evaluation.md +400 -0
- TRAE-Skills/ai_engineering/Chain_of_Thought_Prompting.md +169 -0
- TRAE-Skills/ai_engineering/Computer_Vision_Object_Detection.md +95 -0
- TRAE-Skills/ai_engineering/Data_Drift_Detection.md +84 -0
- TRAE-Skills/ai_engineering/Distributed_Training_Horovod.md +82 -0
- TRAE-Skills/ai_engineering/Embedding_Techniques.md +108 -0
- TRAE-Skills/ai_engineering/Federated_Learning.md +387 -0
- TRAE-Skills/ai_engineering/Fine_Tuning_Custom_Models.md +237 -0
- TRAE-Skills/ai_engineering/Fine_tuning_Basics.md +85 -0
- TRAE-Skills/ai_engineering/Function_Calling.md +280 -0
- TRAE-Skills/ai_engineering/Generative_AI_Image_Synthesis.md +96 -0
- TRAE-Skills/ai_engineering/LLM_Caching_Strategies.md +259 -0
- TRAE-Skills/ai_engineering/LLM_Function_Calling_Advanced.md +234 -0
- TRAE-Skills/ai_engineering/LLM_Operations.md +507 -0
- TRAE-Skills/ai_engineering/LangChain_Basics.md +103 -0
- TRAE-Skills/ai_engineering/Local_LLM_Running_Ollama.md +94 -0
- TRAE-Skills/ai_engineering/ML_Model_Quantization.md +85 -0
- TRAE-Skills/ai_engineering/MultiModal_AI.md +333 -0
- TRAE-Skills/ai_engineering/Natural_Language_to_SQL.md +152 -0
- TRAE-Skills/ai_engineering/OpenAI_API_Integration.md +142 -0
- TRAE-Skills/ai_engineering/Prompt_Engineering_Basics.md +130 -0
- TRAE-Skills/ai_engineering/RAG_System_Architecture.md +110 -0
- TRAE-Skills/ai_engineering/Recommender_Systems_Collaborative_Filtering.md +198 -0
- TRAE-Skills/ai_engineering/Reinforcement_Learning_Basics.md +335 -0
- TRAE-Skills/ai_engineering/Speech_to_Text_Whisper.md +88 -0
- TRAE-Skills/ai_engineering/Stream_Responses.md +321 -0
- TRAE-Skills/ai_engineering/Structured_Output_Parsing.md +301 -0
- TRAE-Skills/ai_engineering/Time_Series_Forecasting.md +94 -0
- TRAE-Skills/ai_engineering/Time_Series_Forecasting_LSTM.md +233 -0
- TRAE-Skills/ai_engineering/Token_Optimization.md +245 -0
- TRAE-Skills/ai_engineering/Vector_Database_Setup.md +97 -0
- TRAE-Skills/ai_engineering/Vector_Databases_Pinecone_Weaviate.md +220 -0
- TRAE-Skills/architecture/API_Gateway_Pattern.md +488 -0
- TRAE-Skills/architecture/Adapter_Pattern_TypeScript.md +87 -0
- TRAE-Skills/architecture/Authentication_Strategy_Selection.md +12 -0
.agents/AGENTS.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multiverse AI Studio — Agent Rules
|
| 2 |
+
|
| 3 |
+
## User Learning Style
|
| 4 |
+
|
| 5 |
+
This project is a **learning-first** environment. The user is building AND learning simultaneously.
|
| 6 |
+
These rules apply to every interaction in this workspace.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
### Rule 1 — Always Write Explanatory Comments
|
| 11 |
+
|
| 12 |
+
Every piece of code written must include comments that explain:
|
| 13 |
+
- **What** the code does (the obvious)
|
| 14 |
+
- **Why** it does it that way (the reasoning)
|
| 15 |
+
- **How** it fits into the larger pipeline (the context)
|
| 16 |
+
|
| 17 |
+
Do not write bare code. If a newcomer cannot understand the code from the comments alone, add more.
|
| 18 |
+
|
| 19 |
+
Example style:
|
| 20 |
+
```python
|
| 21 |
+
# We use ThreadPoolExecutor here because model inference is CPU/GPU-bound
|
| 22 |
+
# (blocking), not I/O-bound. asyncio alone can't parallelize blocking work —
|
| 23 |
+
# it needs a thread pool to offload heavy computation without blocking the
|
| 24 |
+
# FastAPI event loop.
|
| 25 |
+
executor = ThreadPoolExecutor(max_workers=2)
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
### Rule 2 — Pause and Explain at Every Phase Step
|
| 31 |
+
|
| 32 |
+
When implementing any phase step:
|
| 33 |
+
1. **Before coding** — briefly explain what is about to be built and why it exists in the pipeline.
|
| 34 |
+
2. **After coding** — explain what was just built, point to the key lines, and explain any non-obvious decisions.
|
| 35 |
+
3. **Invite doubts** — always end with an open invitation: *"Any questions before we move on?"* or similar.
|
| 36 |
+
|
| 37 |
+
Do not chain multiple phase steps together without pausing.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
### Rule 3 — Take and Resolve Doubts Before Proceeding
|
| 42 |
+
|
| 43 |
+
If the user asks a question mid-phase:
|
| 44 |
+
- Stop what you are doing.
|
| 45 |
+
- Answer the question fully.
|
| 46 |
+
- Check if the answer raised new questions.
|
| 47 |
+
- Only resume coding once the user signals they are ready.
|
| 48 |
+
|
| 49 |
+
Never skip past a doubt to "keep momentum."
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
### Rule 4 — User Workflow (READ THIS CAREFULLY)
|
| 54 |
+
|
| 55 |
+
The user's development workflow is:
|
| 56 |
+
|
| 57 |
+
```
|
| 58 |
+
AI Studio (Google)
|
| 59 |
+
↓ generates initial code scaffold
|
| 60 |
+
TRAE
|
| 61 |
+
↓ organizes, structures, and assembles the codebase
|
| 62 |
+
Antigravity (this agent)
|
| 63 |
+
↓ reviews, compares against implementation plan, checks quality
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
**Antigravity's role in this project is a REVIEWER and TEACHER — not the primary code generator.**
|
| 67 |
+
|
| 68 |
+
When the user brings code for review:
|
| 69 |
+
- Compare it against the implementation plan phase by phase.
|
| 70 |
+
- Check that the model wrapper interface (`initialize/generate/cleanup`) is respected.
|
| 71 |
+
- Check that Stop & Review gate criteria are met.
|
| 72 |
+
- Explain what is correct, what could be improved, and why.
|
| 73 |
+
- Never silently fix things — always explain what was wrong and what the fix does.
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
### Rule 5 — Explain Tradeoffs, Not Just Solutions
|
| 78 |
+
|
| 79 |
+
Whenever a technical decision is made (or reviewed), explain:
|
| 80 |
+
- What alternatives existed
|
| 81 |
+
- Why this choice was made
|
| 82 |
+
- What the tradeoff is
|
| 83 |
+
|
| 84 |
+
This prepares the user to defend decisions in interviews and discussions.
|
.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated AI outputs - these are large, binary, and regenerated on demand
|
| 2 |
+
generated_assets/
|
| 3 |
+
|
| 4 |
+
# Python cache files - automatically generated, not source code
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.pyc
|
| 7 |
+
|
| 8 |
+
# Environment secrets - NEVER commit API keys or tokens to git
|
| 9 |
+
.env
|
| 10 |
+
|
| 11 |
+
# Node.js dependencies - large directory, installed via npm install
|
| 12 |
+
node_modules/
|
| 13 |
+
|
| 14 |
+
# Next.js build artifacts - generated automatically on build
|
| 15 |
+
.next/
|
| 16 |
+
|
| 17 |
+
# macOS system files - OS-specific, not part of the project
|
| 18 |
+
.DS_Store
|
| 19 |
+
|
| 20 |
+
# Python package metadata - generated during packaging
|
| 21 |
+
*.egg-info/
|
| 22 |
+
test_apple.png
|
Dockerfile
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 1: Build the React frontend
|
| 2 |
+
FROM node:18-alpine AS frontend-builder
|
| 3 |
+
WORKDIR /app/frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm install
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
RUN npm run build
|
| 8 |
+
|
| 9 |
+
# Stage 2: Serve using Python FastAPI
|
| 10 |
+
FROM python:3.10-slim
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Install system dependencies
|
| 14 |
+
# - git: needed for certain pip dependencies
|
| 15 |
+
# - libsndfile1: needed for SciPy audio file writes
|
| 16 |
+
# - ffmpeg: needed for imageio video compilations
|
| 17 |
+
RUN apt-get update && apt-get install -y \
|
| 18 |
+
git \
|
| 19 |
+
libsndfile1 \
|
| 20 |
+
ffmpeg \
|
| 21 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
+
|
| 23 |
+
# Copy backend requirements first to leverage Docker build cache
|
| 24 |
+
COPY backend/requirements.txt ./backend/requirements.txt
|
| 25 |
+
RUN pip install --no-cache-dir -r backend/requirements.txt
|
| 26 |
+
|
| 27 |
+
# Copy source code and frontend build output
|
| 28 |
+
COPY backend ./backend
|
| 29 |
+
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
| 30 |
+
|
| 31 |
+
# Expose port 7860 (Hugging Face Spaces default container port)
|
| 32 |
+
EXPOSE 7860
|
| 33 |
+
|
| 34 |
+
# Start uvicorn server on port 7860, binding to all interfaces
|
| 35 |
+
CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multiverse AI Studio
|
| 2 |
+
|
| 3 |
+
A portfolio-grade, full-stack generative AI application that chains five Hugging Face models into a unified, coherent multimedia pipeline. A single human prompt is expanded into specialized instructions to generate a **base visual scene**, estimate its **3D depth geometry**, synthesize an **ambient background soundscape**, and render a **cinematic video** anchoring the visual track.
|
| 4 |
+
|
| 5 |
+
Designed with a strict focus on system architecture, event-loop safety, memory management, and smooth user experience.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 🏗️ System Architecture
|
| 10 |
+
|
| 11 |
+
### 1. Model Pipeline Flow
|
| 12 |
+
Every stage in the pipeline consumes something meaningful from the previous step. The visual assets (depth and video) are conditioned on the generated image, ensuring visual cohesion.
|
| 13 |
+
|
| 14 |
+
```mermaid
|
| 15 |
+
graph TD
|
| 16 |
+
A[User Prompt] -->|POST /api/generate| B(Prompt Expansion LLM)
|
| 17 |
+
B -->|image_prompt| C(Image Generation Service)
|
| 18 |
+
B -->|audio_prompt| D(Audio Generation Service)
|
| 19 |
+
C -->|RGB Image| E(Depth Estimation Service)
|
| 20 |
+
C -->|RGB Image| F(Video Generation Service)
|
| 21 |
+
E -->|Depth Map| F
|
| 22 |
+
D -->|Audio Track| G[Final Coherent Scene]
|
| 23 |
+
F -->|MP4 Stream| G
|
| 24 |
+
|
| 25 |
+
style C fill:#4f46e5,stroke:#333,stroke-width:2px,color:#fff
|
| 26 |
+
style E fill:#ec4899,stroke:#333,stroke-width:2px,color:#fff
|
| 27 |
+
style F fill:#db2777,stroke:#333,stroke-width:2px,color:#fff
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### 2. Event-Loop & Threading Execution Flow
|
| 31 |
+
ML model inference is CPU/GPU-bound and blocking. To prevent blocking FastAPI's main asynchronous event loop, all inferences are offloaded to a background thread pool.
|
| 32 |
+
|
| 33 |
+
```
|
| 34 |
+
Browser FastAPI Route Background Worker Job Store
|
| 35 |
+
│ │ │ │
|
| 36 |
+
│── POST /generate ──────>│ │ │
|
| 37 |
+
│ (prompt payload) │── Create Job (UUID) ───────────────────────────────>│ (QUEUED)
|
| 38 |
+
│ │── Schedule run_pipeline ──>│ │
|
| 39 |
+
│<── Return job_id ───────│ │ │
|
| 40 |
+
│ │ │── Update Stage ────────>│ (EXPANDING...)
|
| 41 |
+
│ │ │── Run Prompt LLM │
|
| 42 |
+
│ │ │── Run Image Gen │
|
| 43 |
+
│ │ │── Update Asset (img) ──>│ (Image URL)
|
| 44 |
+
│ │ │── Run Depth Est │
|
| 45 |
+
│ │ │── Update Asset (depth) ─>│ (Depth URL)
|
| 46 |
+
│ │ │── Run Audio/Video │
|
| 47 |
+
│ │ │── Update final state ──>│ (COMPLETED)
|
| 48 |
+
│ │ │ │
|
| 49 |
+
│── GET /result/{id} ────>│────────────────────────────────────────────────────>│
|
| 50 |
+
│<── Returns assets ──────│<────────────────────────────────────────────────────│
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## ⚡ Key Features
|
| 56 |
+
|
| 57 |
+
* **Progressive Polling & Rendering**: The frontend polls `/api/result/{job_id}`. Completed assets (like the base image) are rendered on the screen *immediately* while downstream stages are still processing.
|
| 58 |
+
* **Granular Memory/VRAM Management**: Chaining 5 heavy models sequentially can cause VRAM Out-of-Memory (OOM) crashes. Each model wrapper implements a strict `cleanup()` method that deletes pipeline instances, runs garbage collection (`gc.collect()`), and flushes PyTorch's CUDA memory cache (`torch.cuda.empty_cache()`) before loading the next stage.
|
| 59 |
+
* **Stage Error Isolation**: Wrap-around try/except boundaries guarantee that a single failed stage (e.g., depth map or audio timeout) does not crash the entire pipeline. The server flags a `PARTIAL_FAILURE` and delivers all other successfully compiled assets.
|
| 60 |
+
* **Interactive CSS Depth Slider**: Features a custom swipable comparison slider built using CSS `clip-path` polygon slicing for smooth 60fps comparisons between the visual base image and its calculated depth map.
|
| 61 |
+
* **Custom Media Players**: Custom glassmorphic React components for audio and video playback, including a dynamic pulsing audio waveform visualizer.
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## ⚙️ Project Setup
|
| 66 |
+
|
| 67 |
+
### Prerequisites
|
| 68 |
+
* Python 3.10+
|
| 69 |
+
* Node.js 18+
|
| 70 |
+
* Hugging Face Access Token (for gated model downloads and Inference API)
|
| 71 |
+
|
| 72 |
+
### 1. Environment Configuration
|
| 73 |
+
Create a `.env` file in the project root:
|
| 74 |
+
```env
|
| 75 |
+
HF_TOKEN=your_huggingface_access_token_here
|
| 76 |
+
MOCK_INFERENCE=False
|
| 77 |
+
FORCE_CPU_INFERENCE=False
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
#### Environment Variables Explained:
|
| 81 |
+
* `HF_TOKEN`: Your Hugging Face user access token (required for querying the cloud image generation API and downloading gated local models).
|
| 82 |
+
* `MOCK_INFERENCE`:
|
| 83 |
+
* `True` (Default DX): Bypasses all local and cloud model execution, returning mock visual, audio, and video assets in 1 second. Useful for testing UI components on any computer.
|
| 84 |
+
* `False` (Hybrid Production): Connects to the cloud and local machine learning models for real generation.
|
| 85 |
+
* `FORCE_CPU_INFERENCE`:
|
| 86 |
+
* `False` (Safe Fallback): If running `MOCK_INFERENCE=False` on a **CPU-only machine**, the backend will run cloud image generation and local depth maps, but will automatically bypass the heavy `MusicGen` and `i2vgen-xl` local models to prevent RAM exhaustion.
|
| 87 |
+
* `True` (Force CPU): Forces the backend to download, load, and execute the full MusicGen and Video models locally on your CPU. *Warning: MusicGen takes 2–5 minutes, and i2vgen-xl takes 20–45 minutes on CPU.*
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## 🚀 GPU & Production Execution Setup
|
| 92 |
+
If you want to run the **complete, real local PyTorch pipelines** (audio and video) on a GPU-enabled developer machine:
|
| 93 |
+
|
| 94 |
+
1. **Install CUDA-enabled PyTorch**: Ensure your virtual environment is using a GPU-compiled version of PyTorch:
|
| 95 |
+
```bash
|
| 96 |
+
pip install torch --index-url https://download.pytorch.org/whl/cu121
|
| 97 |
+
```
|
| 98 |
+
2. **Configure environment**: Open your `.env` file and set:
|
| 99 |
+
```env
|
| 100 |
+
HF_TOKEN=your_real_huggingface_token
|
| 101 |
+
MOCK_INFERENCE=False
|
| 102 |
+
FORCE_CPU_INFERENCE=False
|
| 103 |
+
```
|
| 104 |
+
3. **Launch**: Run uvicorn. The backend will automatically detect the GPU (`cuda`), log the status, and run the real local Hugging Face and Diffusers pipelines at maximum speed.
|
| 105 |
+
|
| 106 |
+
### 2. Backend Installation
|
| 107 |
+
```bash
|
| 108 |
+
# Navigate to the backend directory
|
| 109 |
+
cd backend
|
| 110 |
+
|
| 111 |
+
# Create a virtual environment
|
| 112 |
+
python -m venv venv
|
| 113 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 114 |
+
|
| 115 |
+
# Install dependencies
|
| 116 |
+
pip install -r requirements.txt
|
| 117 |
+
|
| 118 |
+
# Start the development server
|
| 119 |
+
python -m uvicorn main:app --host 127.0.0.1 --port 8000 --reload
|
| 120 |
+
```
|
| 121 |
+
The backend health check is available at `http://127.0.0.1:8000/api/health`.
|
| 122 |
+
|
| 123 |
+
### 3. Frontend Installation
|
| 124 |
+
```bash
|
| 125 |
+
# Navigate to the frontend directory
|
| 126 |
+
cd frontend
|
| 127 |
+
|
| 128 |
+
# Install packages
|
| 129 |
+
npm install
|
| 130 |
+
|
| 131 |
+
# Start the Vite React development server
|
| 132 |
+
npm run dev
|
| 133 |
+
```
|
| 134 |
+
Open `http://localhost:3000` to access the Multiverse AI Studio interface.
|
| 135 |
+
|
| 136 |
+
---
|
| 137 |
+
|
| 138 |
+
## 📈 Engineering Decisions & Tradeoffs
|
| 139 |
+
|
| 140 |
+
For a detailed analysis of our engineering decisions (such as choosing client polling over WebSockets, utilising an in-memory job store instead of Celery/Redis, and enforcing the BaseModel wrapper abstraction), please refer to the dedicated **[Tradeoffs and Decisions Report](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\docs\decisions\tradeoffs.md)**.
|
| 141 |
+
|
| 142 |
+
---
|
| 143 |
+
|
| 144 |
+
## 🚀 Future Roadmap
|
| 145 |
+
|
| 146 |
+
* **Server-Sent Events (SSE)**: Migrate the progressive rendering polling system to standard Server-Sent Events to push updates in real-time without client request overhead.
|
| 147 |
+
* **Persistent Database storage**: Replace the volatile in-memory dictionary with SQLite or PostgreSQL to keep user history across restarts.
|
| 148 |
+
* **Muxed Video Audio**: Integrate system FFmpeg binaries to merge (mux) the Stage 4 ambient soundscape directly into the Stage 5 MP4 video container.
|
REPORT.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multiverse AI Studio - Development Report
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
This report summarizes all the work done on the Multiverse AI Studio project so far.
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## Table of Contents
|
| 9 |
+
1. [Backend Improvements](#backend-improvements)
|
| 10 |
+
2. [Frontend Updates](#frontend-updates)
|
| 11 |
+
3. [Files Modified](#files-modified)
|
| 12 |
+
4. [New Files Created](#new-files-created)
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Backend Improvements
|
| 17 |
+
|
| 18 |
+
### 1. Fixed Module Import Issues
|
| 19 |
+
All Python files in the backend were updated to use relative imports to avoid `ModuleNotFoundError` when running the server.
|
| 20 |
+
- Example changes:
|
| 21 |
+
- `from models.base import BaseModel` → `from .base import BaseModel`
|
| 22 |
+
- `from config import HF_TOKEN` → `from ..config import HF_TOKEN`
|
| 23 |
+
|
| 24 |
+
### 2. Added Error Handling to Model Methods
|
| 25 |
+
Every model wrapper's `generate()` and `initialize()` methods now include try/except blocks to gracefully handle failures (like Out-of-Memory errors, API failures, etc.).
|
| 26 |
+
- Models updated:
|
| 27 |
+
- `PromptExpander` (already had good error handling)
|
| 28 |
+
- `ImageGenerator`
|
| 29 |
+
- `DepthEstimator`
|
| 30 |
+
- `AudioGenerator`
|
| 31 |
+
- `VideoGenerator`
|
| 32 |
+
- `execute_model_sync` in `pipeline.py` now also handles initialization errors
|
| 33 |
+
|
| 34 |
+
### 3. Added Scene Description Support
|
| 35 |
+
- Added `set_job_scene_description` to `job_store.py`
|
| 36 |
+
- Updated `pipeline.py` to save the expanded scene description
|
| 37 |
+
- Updated `/api/result/{job_id}` in `routes.py` to return `scene_description`
|
| 38 |
+
- Updated backend `config.py` (no changes needed, but it's there)
|
| 39 |
+
- Added `scene_description` to `JobResult` in frontend API client
|
| 40 |
+
|
| 41 |
+
### 4. Improved Job Store
|
| 42 |
+
- Added `scene_description` field to job state
|
| 43 |
+
- Added `error_at` timestamp for better error tracking
|
| 44 |
+
|
| 45 |
+
### 5. Backend Startup Fixes
|
| 46 |
+
- `main.py` now loads dotenv before importing config
|
| 47 |
+
- `main.py` creates `OUTPUT_DIR` before mounting static files to avoid startup crashes
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## Frontend Updates
|
| 52 |
+
|
| 53 |
+
### 1. Centralized API Client
|
| 54 |
+
Created `frontend/src/lib/api.ts` to handle all API calls with:
|
| 55 |
+
- Proper base URL (`http://localhost:8000/api`)
|
| 56 |
+
- Type definitions for API responses
|
| 57 |
+
- Asset URL fixing (prepends backend base URL to relative paths)
|
| 58 |
+
|
| 59 |
+
### 2. Updated Pages
|
| 60 |
+
- **Home.tsx**: Now uses `generateAssets` from API client instead of direct fetch
|
| 61 |
+
- **Studio.tsx**:
|
| 62 |
+
- Now uses `getJobStatus` and `getJobResult` from API client
|
| 63 |
+
- Added collapsible panel to show the scene description
|
| 64 |
+
- Added error state for pipeline stages
|
| 65 |
+
- Added red error indicator and error message display
|
| 66 |
+
|
| 67 |
+
### 3. Added Types
|
| 68 |
+
- `JobStatus` interface
|
| 69 |
+
- `JobResult` interface (includes `scene_description`)
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## Files Modified
|
| 74 |
+
|
| 75 |
+
### Backend
|
| 76 |
+
- [`backend/main.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\main.py)
|
| 77 |
+
- [`backend/config.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\config.py)
|
| 78 |
+
- [`backend/api/routes.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\api\routes.py)
|
| 79 |
+
- [`backend/services/pipeline.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\services\pipeline.py)
|
| 80 |
+
- [`backend/utils/job_store.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\utils\job_store.py)
|
| 81 |
+
- [`backend/utils/file_manager.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\utils\file_manager.py)
|
| 82 |
+
- [`backend/models/prompt_expander.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\prompt_expander.py)
|
| 83 |
+
- [`backend/models/image_generator.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\image_generator.py)
|
| 84 |
+
- [`backend/models/depth_estimator.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\depth_estimator.py)
|
| 85 |
+
- [`backend/models/audio_generator.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\audio_generator.py)
|
| 86 |
+
- [`backend/models/video_generator.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\video_generator.py)
|
| 87 |
+
|
| 88 |
+
### Frontend
|
| 89 |
+
- [`frontend/src/pages/Home.tsx`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\frontend\src\pages\Home.tsx)
|
| 90 |
+
- [`frontend/src/pages/Studio.tsx`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\frontend\src\pages\Studio.tsx)
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## New Files Created
|
| 95 |
+
|
| 96 |
+
### Backend
|
| 97 |
+
- [`backend/api/__init__.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\api\__init__.py) (empty)
|
| 98 |
+
- [`backend/services/__init__.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\services\__init__.py) (empty)
|
| 99 |
+
- [`backend/utils/__init__.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\utils\__init__.py) (empty)
|
| 100 |
+
- [`backend/models/__init__.py`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\backend\models\__init__.py) (empty)
|
| 101 |
+
|
| 102 |
+
### Frontend
|
| 103 |
+
- [`frontend/src/lib/api.ts`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\frontend\src\lib\api.ts)
|
| 104 |
+
|
| 105 |
+
### Project Root
|
| 106 |
+
- [`.gitignore`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\.gitignore)
|
| 107 |
+
- [`TRAE-Skills/`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\TRAE-Skills) (cloned repo)
|
| 108 |
+
- [`REPORT.md`](file:///c:\AI Native founder\AI_Engineering\Projects\Multiverse_AI_Studio\REPORT.md) (this file!)
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Next Steps
|
| 113 |
+
- Test the full pipeline end-to-end
|
| 114 |
+
- Add model download caching
|
| 115 |
+
- Add more comprehensive error handling in frontend
|
| 116 |
+
- Deploy to production
|
TRAE-Skills/LICENSE
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
TRAE Skills License Agreement
|
| 2 |
+
Version 1.0
|
| 3 |
+
|
| 4 |
+
Copyright (c) 2026 HighMark-IT
|
| 5 |
+
|
| 6 |
+
1. GRANT OF RIGHTS
|
| 7 |
+
|
| 8 |
+
You are granted a non-exclusive, royalty-free right to use, copy, modify, and distribute this software and its associated documentation files (the "Software") for personal, educational, and commercial purposes, subject to the conditions outlined in this License.
|
| 9 |
+
|
| 10 |
+
2. CONDITIONS AND RESTRICTIONS
|
| 11 |
+
|
| 12 |
+
A. Proper Attribution Required
|
| 13 |
+
- You MUST include clear attribution to this repository (https://github.com/HighMark-31/TRAE-Agents) and its author (HighMark-IT) in any derivative work, product, or project that incorporates or uses the Software or its agent structures.
|
| 14 |
+
- Attribution must be visible and accessible to end users.
|
| 15 |
+
|
| 16 |
+
B. No Republication of Original Agents
|
| 17 |
+
- You MAY NOT republish, redistribute, or present the original agents or substantially unmodified versions of them as your own public collection, product suite, or template library.
|
| 18 |
+
- You cannot claim ownership of the "TRAE Agents" collection, the specific agent names, or the overall ecosystem as originally created in this repository.
|
| 19 |
+
- Minor modifications (e.g., parameter tweaks, prompt rewording) do not constitute a new original work and remain subject to this restriction.
|
| 20 |
+
|
| 21 |
+
C. Prohibited Agent Rebranding
|
| 22 |
+
- You MAY NOT use the names "TRAE Agents", "TRAE-Agents", or the specific agent names (e.g., "General Coordinator", "Code Optimizer", "Security Sentinel") as if they were your original creation or branding.
|
| 23 |
+
- You must create distinctly different naming and branding if you publish agent collections based on this work.
|
| 24 |
+
|
| 25 |
+
D. Original Works Permitted
|
| 26 |
+
- Creating your own agents or agent frameworks INSPIRED BY this project is permitted, provided they are:
|
| 27 |
+
1. Materially different in structure, purpose, or implementation
|
| 28 |
+
2. Given original names and branding
|
| 29 |
+
3. Properly attributed to this repository as inspiration
|
| 30 |
+
|
| 31 |
+
3. COMMERCIAL USE
|
| 32 |
+
|
| 33 |
+
You are permitted to use the Software in commercial products and services, provided that:
|
| 34 |
+
- You maintain proper attribution to this repository
|
| 35 |
+
- You do not republish the original agent collection as your own
|
| 36 |
+
- You do not claim ownership of the TRAE Agents ecosystem
|
| 37 |
+
|
| 38 |
+
4. MODIFICATIONS AND DERIVATIVES
|
| 39 |
+
|
| 40 |
+
You may modify the Software for your own use. If you distribute modified versions, you must:
|
| 41 |
+
- Clearly mark modifications as your own
|
| 42 |
+
- Maintain attribution to the original work
|
| 43 |
+
- Not present near-identical versions as original creations
|
| 44 |
+
|
| 45 |
+
5. NO WARRANTY
|
| 46 |
+
|
| 47 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 48 |
+
|
| 49 |
+
6. ENFORCEMENT
|
| 50 |
+
|
| 51 |
+
Breach of conditions outlined in Section 2 may result in:
|
| 52 |
+
- Cease and desist notices
|
| 53 |
+
- Requests for content removal from public platforms
|
| 54 |
+
- Legal action to enforce compliance with this License
|
| 55 |
+
|
| 56 |
+
7. SEVERABILITY
|
| 57 |
+
|
| 58 |
+
If any provision of this License is found to be unenforceable, the remaining provisions shall remain in full force and effect.
|
| 59 |
+
|
| 60 |
+
8. GOVERNING LAW
|
| 61 |
+
|
| 62 |
+
This License is governed by applicable copyright and intellectual property laws. The Copyright holder reserves the right to modify these terms with notice.
|
| 63 |
+
|
| 64 |
+
By using this Software, you acknowledge that you have read, understood, and agree to be bound by all terms of this License.
|
TRAE-Skills/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
|
| 3 |
+
### ✨ The Ultimate AI Skill Library for Modern Development ✨
|
| 4 |
+
|
| 5 |
+
</div>
|
| 6 |
+
|
| 7 |
+
# [TRAE](https://www.trae.ai/) Skills Collection
|
| 8 |
+
|
| 9 |
+
**_The Largest, Most Complete, and Popular Collection of TRAE Skills._**
|
| 10 |
+
|
| 11 |
+
A powerful collection of **expert-level TRAE Skills**, designed to standardize and elevate every stage of modern software development.
|
| 12 |
+
These skills act as a knowledge base for agents, enabling them to generate production-ready code, follow best practices, and handle complex architectural tasks across multiple stacks.
|
| 13 |
+
|
| 14 |
+
Perfect for developers, engineers, and teams seeking **consistency, quality, and expert-level output** in every interaction.
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
[](https://github.com/HighMark-31/TRAE-Skills/stargazers) 
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## 📍 Skill List
|
| 24 |
+
|
| 25 |
+
| Category | Skill Name | Info | View |
|
| 26 |
+
| :--- | :--- | :--- | :--- |
|
| 27 |
+
| **🔹 ️ Frontend** | Accessibility Audit | [ℹ️](./frontend/Accessibility_Audit.md) | [👆 View](./frontend/Accessibility_Audit.md) |
|
| 28 |
+
| | API Data Fetching (TanStack) | [ℹ️](./frontend/API_Data_Fetching_TanStack.md) | [👆 View](./frontend/API_Data_Fetching_TanStack.md) |
|
| 29 |
+
| | Authentication Strategy Selection | [ℹ️](./architecture/Authentication_Strategy_Selection.md) | [👆 View](./architecture/Authentication_Strategy_Selection.md) |
|
| 30 |
+
| | BFF Pattern Implementation | [ℹ️](./architecture/BFF_Pattern_Implementation.md) | [👆 View](./architecture/BFF_Pattern_Implementation.md) |
|
| 31 |
+
| | Code Review Security Checklist | [ℹ️](./security/Code_Review_Security.md) | [👆 View](./security/Code_Review_Security.md) |
|
| 32 |
+
| | Custom React Hook Creation | [ℹ️](./frontend/Custom_React_Hook_Creation.md) | [👆 View](./frontend/Custom_React_Hook_Creation.md) |
|
| 33 |
+
| | Database Selection (SQL vs NoSQL) | [ℹ️](./architecture/Database_Selection_SQL_vs_NoSQL.md) | [👆 View](./architecture/Database_Selection_SQL_vs_NoSQL.md) |
|
| 34 |
+
| | E2E Testing (Playwright) | [ℹ️](./testing/E2E_Testing_Playwright.md) | [👆 View](./testing/E2E_Testing_Playwright.md) |
|
| 35 |
+
| | Event Driven Architecture | [ℹ️](./architecture/Event_Driven_Architecture_Basics.md) | [👆 View](./architecture/Event_Driven_Architecture_Basics.md) |
|
| 36 |
+
| | Form Handling (ReactHookForm) | [ℹ️](./frontend/Form_Handling_ReactHookForm.md) | [👆 View](./frontend/Form_Handling_ReactHookForm.md) |
|
| 37 |
+
| | Frontend Error Boundary | [ℹ️](./frontend/Frontend_Error_Boundary.md) | [👆 View](./frontend/Frontend_Error_Boundary.md) |
|
| 38 |
+
| | Frontend-Backend Communication | [ℹ️](./architecture/Frontend_Backend_Communication_Patterns.md) | [👆 View](./architecture/Frontend_Backend_Communication_Patterns.md) |
|
| 39 |
+
| | Global State (Redux Toolkit) | [ℹ️](./frontend/Global_State_Management_Redux.md) | [👆 View](./frontend/Global_State_Management_Redux.md) |
|
| 40 |
+
| | Input Validation (Zod) | [ℹ️](./security/Input_Validation_Zod.md) | [👆 View](./security/Input_Validation_Zod.md) |
|
| 41 |
+
| | Internationalization (i18n) | [ℹ️](./frontend/Internationalization_i18n.md) | [👆 View](./frontend/Internationalization_i18n.md) |
|
| 42 |
+
| | JWT Authentication | [ℹ️](./security/JWT_Authentication.md) | [👆 View](./security/JWT_Authentication.md) |
|
| 43 |
+
| | Microservices vs Monolith | [ℹ️](./architecture/Microservices_vs_Monolith_Decision.md) | [👆 View](./architecture/Microservices_vs_Monolith_Decision.md) |
|
| 44 |
+
| | Multi-Tenancy Architecture | [ℹ️](./architecture/Multi_Tenancy_Architecture.md) | [👆 View](./architecture/Multi_Tenancy_Architecture.md) |
|
| 45 |
+
| | Rate Limiting (Redis) | [ℹ️](./security/Rate_Limiting_Redis.md) | [👆 View](./security/Rate_Limiting_Redis.md) |
|
| 46 |
+
| | RBAC Implementation | [ℹ️](./security/RBAC_Implementation.md) | [👆 View](./security/RBAC_Implementation.md) |
|
| 47 |
+
| | React Component Optimization | [ℹ️](./frontend/React_Component_Optimization.md) | [👆 View](./frontend/React_Component_Optimization.md) |
|
| 48 |
+
| | Responsive UI (Tailwind) | [ℹ️](./frontend/Responsive_UI_Design_Tailwind.md) | [👆 View](./frontend/Responsive_UI_Design_Tailwind.md) |
|
| 49 |
+
| | REST vs GraphQL Selection | [ℹ️](./architecture/REST_vs_GraphQL_Selection.md) | [👆 View](./architecture/REST_vs_GraphQL_Selection.md) |
|
| 50 |
+
| | Route Protection (React Router) | [ℹ️](./frontend/Route_Protection_React_Router.md) | [👆 View](./frontend/Route_Protection_React_Router.md) |
|
| 51 |
+
| | Scaling Strategies | [ℹ️](./architecture/Scaling_Strategies_Horizontal_vs_Vertical.md) | [👆 View](./architecture/Scaling_Strategies_Horizontal_vs_Vertical.md) |
|
| 52 |
+
| | Secure Env Var Handling | [ℹ️](./security/Secure_Env_Var_Handling.md) | [👆 View](./security/Secure_Env_Var_Handling.md) |
|
| 53 |
+
| | Serverless Architecture | [ℹ️](./architecture/Serverless_Architecture_Considerations.md) | [👆 View](./architecture/Serverless_Architecture_Considerations.md) |
|
| 54 |
+
| | SSR vs CSR Decision | [ℹ️](./architecture/SSR_vs_CSR_Decision_Matrix.md) | [👆 View](./architecture/SSR_vs_CSR_Decision_Matrix.md) |
|
| 55 |
+
| | Stack Selection Criteria | [ℹ️](./architecture/Stack_Selection_Criteria.md) | [👆 View](./architecture/Stack_Selection_Criteria.md) |
|
| 56 |
+
| | Unit Test Generation (Jest) | [ℹ️](./testing/Unit_Test_Generation_Jest.md) | [👆 View](./testing/Unit_Test_Generation_Jest.md) |
|
| 57 |
+
| **💻 Backend** | API REST Endpoint Design | [ℹ️](./backend/API_REST_Endpoint_Design.md) | [👆 View](./backend/API_REST_Endpoint_Design.md) |
|
| 58 |
+
| | API Versioning Strategies | [ℹ️](./backend/API_Versioning_Strategies.md) | [👆 View](./backend/API_Versioning_Strategies.md) |
|
| 59 |
+
| | Background Jobs with BullMQ | [ℹ️](./backend/Background_Jobs_BullMQ.md) | [👆 View](./backend/Background_Jobs_BullMQ.md) |
|
| 60 |
+
| | Caching Strategy (Redis) | [ℹ️](./backend/Caching_Strategy_Redis.md) | [👆 View](./backend/Caching_Strategy_Redis.md) |
|
| 61 |
+
| | Database Locking Strategies | [ℹ️](./backend/Database_Locking_Strategies.md) | [👆 View](./backend/Database_Locking_Strategies.md) |
|
| 62 |
+
| | Database Schema Migration | [ℹ️](./backend/Database_Schema_Migration.md) | [👆 View](./backend/Database_Schema_Migration.md) |
|
| 63 |
+
| | Database Seeding | [ℹ️](./backend/Database_Seeding.md) | [👆 View](./backend/Database_Seeding.md) |
|
| 64 |
+
| | Error Handling (Express) | [ℹ️](./backend/Error_Handling_Express.md) | [👆 View](./backend/Error_Handling_Express.md) |
|
| 65 |
+
| | Express Middleware Creation | [ℹ️](./backend/Express_Middleware_Creation.md) | [👆 View](./backend/Express_Middleware_Creation.md) |
|
| 66 |
+
| | Feature Flag Implementation | [ℹ️](./backend/Feature_Flag_Implementation.md) | [👆 View](./backend/Feature_Flag_Implementation.md) |
|
| 67 |
+
| | File Storage (S3/MinIO) | [ℹ️](./backend/File_Storage_S3_MinIO.md) | [👆 View](./backend/File_Storage_S3_MinIO.md) |
|
| 68 |
+
| | File Upload Handling | [ℹ️](./backend/File_Upload_Handling.md) | [👆 View](./backend/File_Upload_Handling.md) |
|
| 69 |
+
| | GraphQL Schema Design | [ℹ️](./backend/GraphQL_Schema_Design.md) | [👆 View](./backend/GraphQL_Schema_Design.md) |
|
| 70 |
+
| | gRPC Service Implementation | [ℹ️](./backend/gRPC_Service_Implementation.md) | [👆 View](./backend/gRPC_Service_Implementation.md) |
|
| 71 |
+
| | Handling Distributed Transactions (Sagas) | [ℹ️](./backend/Distributed_Transactions_Sagas.md) | [👆 View](./backend/Distributed_Transactions_Sagas.md) |
|
| 72 |
+
| | Health Check Endpoint | [ℹ️](./backend/Health_Check_Endpoint.md) | [👆 View](./backend/Health_Check_Endpoint.md) |
|
| 73 |
+
| | Implementing OAuth2 Providers | [ℹ️](./backend/OAuth2_Provider_Implementation.md) | [👆 View](./backend/OAuth2_Provider_Implementation.md) |
|
| 74 |
+
| | Implementing Search (Elasticsearch) | [ℹ️](./backend/Elasticsearch_Integration.md) | [👆 View](./backend/Elasticsearch_Integration.md) |
|
| 75 |
+
| | Logger Configuration (Winston) | [ℹ️](./backend/Logger_Configuration_Winston.md) | [👆 View](./backend/Logger_Configuration_Winston.md) |
|
| 76 |
+
| | Message Queue Implementation | [ℹ️](./backend/Message_Queue_Implementation.md) | [👆 View](./backend/Message_Queue_Implementation.md) |
|
| 77 |
+
| | Microservice Communication | [ℹ️](./backend/Microservice_Communication.md) | [👆 View](./backend/Microservice_Communication.md) |
|
| 78 |
+
| | MongoDB Aggregation Pipeline | [ℹ️](./backend/MongoDB_Aggregation.md) | [👆 View](./backend/MongoDB_Aggregation.md) |
|
| 79 |
+
| | Multi-factor Authentication (MFA) | [ℹ️](./backend/MFA_Implementation.md) | [👆 View](./backend/MFA_Implementation.md) |
|
| 80 |
+
| | Node.js Stream Processing | [ℹ️](./backend/Nodejs_Streams.md) | [👆 View](./backend/Nodejs_Streams.md) |
|
| 81 |
+
| | Pagination Implementation | [ℹ️](./backend/Pagination_Implementation.md) | [👆 View](./backend/Pagination_Implementation.md) |
|
| 82 |
+
| | PostgreSQL Indexing Strategies | [ℹ️](./backend/PostgreSQL_Indexing.md) | [👆 View](./backend/PostgreSQL_Indexing.md) |
|
| 83 |
+
| | Prisma Schema Design | [ℹ️](./backend/Prisma_Schema_Design.md) | [👆 View](./backend/Prisma_Schema_Design.md) |
|
| 84 |
+
| | Redis Data Structures | [ℹ️](./backend/Redis_Data_Structures.md) | [👆 View](./backend/Redis_Data_Structures.md) |
|
| 85 |
+
| | Refactoring Legacy Controller | [ℹ️](./backend/Refactoring_Legacy_Controller.md) | [👆 View](./backend/Refactoring_Legacy_Controller.md) |
|
| 86 |
+
| | Role-Based Access Control (Advanced) | [ℹ️](./backend/Advanced_RBAC.md) | [👆 View](./backend/Advanced_RBAC.md) |
|
| 87 |
+
| | Session Management Best Practices | [ℹ️](./backend/Session_Management_Best_Practices.md) | [👆 View](./backend/Session_Management_Best_Practices.md) |
|
| 88 |
+
| | Soft Delete Implementation | [ℹ️](./backend/Soft_Delete_Implementation.md) | [👆 View](./backend/Soft_Delete_Implementation.md) |
|
| 89 |
+
| | SQL Query Optimization | [ℹ️](./backend/SQL_Query_Optimization.md) | [👆 View](./backend/SQL_Query_Optimization.md) |
|
| 90 |
+
| | Swagger Documentation | [ℹ️](./backend/Swagger_Documentation.md) | [👆 View](./backend/Swagger_Documentation.md) |
|
| 91 |
+
| | Transaction Management | [ℹ️](./backend/Transaction_Management.md) | [👆 View](./backend/Transaction_Management.md) |
|
| 92 |
+
| | TypeORM Entity Relations | [ℹ️](./backend/TypeORM_Relations.md) | [👆 View](./backend/TypeORM_Relations.md) |
|
| 93 |
+
| | Webhooks Implementation | [ℹ️](./backend/Webhooks_Implementation.md) | [👆 View](./backend/Webhooks_Implementation.md) |
|
| 94 |
+
| | WebSocket Implementation | [ℹ️](./backend/WebSocket_Implementation.md) | [👆 View](./backend/WebSocket_Implementation.md) |
|
| 95 |
+
| | Real-time Data Processing (Kafka) | [ℹ️](./backend/Real-time_Data_Processing_Kafka.md) | [👆 View](./backend/Real-time_Data_Processing_Kafka.md) |
|
| 96 |
+
| | Real-time GraphQL Subscriptions | [ℹ️](./backend/GraphQL_Subscriptions_Realtime.md) | [👆 View](./backend/GraphQL_Subscriptions_Realtime.md) |
|
| 97 |
+
| | WebSocket Scalability (Socket.io) | [ℹ️](./backend/WebSocket_Scalability_SocketIO.md) | [👆 View](./backend/WebSocket_Scalability_SocketIO.md) |
|
| 98 |
+
| | Type-Safe APIs with tRPC | [ℹ️](./backend/Type_Safe_APIs_tRPC.md) | [👆 View](./backend/Type_Safe_APIs_tRPC.md) |
|
| 99 |
+
| | Serverless Streaming & Webhooks at Scale | [ℹ️](./backend/Serverless_Streaming_Webhooks.md) | [👆 View](./backend/Serverless_Streaming_Webhooks.md) |
|
| 100 |
+
| **🖥️ Frontend** | Browser Storage (LocalStorage/IndexedDB) | [ℹ️](./frontend/Browser_Storage.md) | [👆 View](./frontend/Browser_Storage.md) |
|
| 101 |
+
| | Canvas/WebGL Basics (Three.js) | [ℹ️](./frontend/Canvas_Threejs_Basics.md) | [👆 View](./frontend/Canvas_Threejs_Basics.md) |
|
| 102 |
+
| | CSS Grid vs Flexbox Guide | [ℹ️](./frontend/CSS_Grid_vs_Flexbox.md) | [👆 View](./frontend/CSS_Grid_vs_Flexbox.md) |
|
| 103 |
+
| | Dark Mode Implementation | [ℹ️](./frontend/Dark_Mode_Implementation.md) | [👆 View](./frontend/Dark_Mode_Implementation.md) |
|
| 104 |
+
| | Handling Large Lists (Virtualization) | [ℹ️](./frontend/Handling_Large_Lists_Virtualization.md) | [👆 View](./frontend/Handling_Large_Lists_Virtualization.md) |
|
| 105 |
+
| | Mobile-First Design Principles | [ℹ️](./frontend/Mobile_First_Design.md) | [👆 View](./frontend/Mobile_First_Design.md) |
|
| 106 |
+
| | Next.js App Router Migration | [ℹ️](./frontend/Nextjs_App_Router.md) | [👆 View](./frontend/Nextjs_App_Router.md) |
|
| 107 |
+
| | Optimizing Web Vitals | [ℹ️](./frontend/Web_Vitals_Optimization.md) | [👆 View](./frontend/Web_Vitals_Optimization.md) |
|
| 108 |
+
| | PWA Implementation | [ℹ️](./frontend/PWA_Implementation.md) | [👆 View](./frontend/PWA_Implementation.md) |
|
| 109 |
+
| | React Context vs Zustand | [ℹ️](./frontend/React_Context_vs_Zustand.md) | [👆 View](./frontend/React_Context_vs_Zustand.md) |
|
| 110 |
+
| | Storybook Component Documentation | [ℹ️](./frontend/Storybook_Component_Documentation.md) | [👆 View](./frontend/Storybook_Component_Documentation.md) |
|
| 111 |
+
| | SVG Animation Techniques | [ℹ️](./frontend/SVG_Animation_Techniques.md) | [👆 View](./frontend/SVG_Animation_Techniques.md) |
|
| 112 |
+
| | Testing React Components (RTL) | [ℹ️](./frontend/React_Testing_Library.md) | [👆 View](./frontend/React_Testing_Library.md) |
|
| 113 |
+
| | Web Workers for Heavy Computation | [ℹ️](./frontend/Web_Workers.md) | [👆 View](./frontend/Web_Workers.md) |
|
| 114 |
+
| | Advanced WebGL with Three.js | [ℹ️](./frontend/WebGL_Advanced_Threejs.md) | [👆 View](./frontend/WebGL_Advanced_Threejs.md) |
|
| 115 |
+
| | Advanced Web Animations with Framer Motion | [ℹ️](./frontend/Web_Animations_Framer_Motion.md) | [👆 View](./frontend/Web_Animations_Framer_Motion.md) |
|
| 116 |
+
| **📱 Mobile** | App Store Deployment Guide | [ℹ️](./mobile/App_Store_Deployment_Guide.md) | [👆 View](./mobile/App_Store_Deployment_Guide.md) |
|
| 117 |
+
| | Mobile Device Features | [ℹ️](./mobile/Mobile_Device_Features.md) | [👆 View](./mobile/Mobile_Device_Features.md) |
|
| 118 |
+
| | Mobile UI Styling (NativeWind) | [ℹ️](./mobile/Mobile_UI_Styling_NativeWind.md) | [👆 View](./mobile/Mobile_UI_Styling_NativeWind.md) |
|
| 119 |
+
| | Offline-First Mobile Architecture | [ℹ️](./mobile/Offline_First_Mobile_Architecture.md) | [👆 View](./mobile/Offline_First_Mobile_Architecture.md) |
|
| 120 |
+
| | Push Notifications Setup | [ℹ️](./mobile/Push_Notifications_Setup.md) | [👆 View](./mobile/Push_Notifications_Setup.md) |
|
| 121 |
+
| | React Native Navigation | [ℹ️](./mobile/React_Native_Navigation.md) | [👆 View](./mobile/React_Native_Navigation.md) |
|
| 122 |
+
| | React Native Reanimated | [ℹ️](./mobile/React_Native_Reanimated.md) | [👆 View](./mobile/React_Native_Reanimated.md) |
|
| 123 |
+
| | React Native Setup (Expo) | [ℹ️](./mobile/React_Native_Setup_Expo.md) | [👆 View](./mobile/React_Native_Setup_Expo.md) |
|
| 124 |
+
| | Biometric Authentication (Expo) | [ℹ️](./mobile/Biometric_Authentication_Expo.md) | [👆 View](./mobile/Biometric_Authentication_Expo.md) |
|
| 125 |
+
| | Deep Linking (React Navigation) | [ℹ️](./mobile/Deep_Linking_React_Navigation.md) | [👆 View](./mobile/Deep_Linking_React_Navigation.md) |
|
| 126 |
+
| | Flutter Advanced State Management | [ℹ️](./mobile/Flutter_Advanced_State_Management.md) | [👆 View](./mobile/Flutter_Advanced_State_Management.md) |
|
| 127 |
+
| | React Native Performance Optimization | [ℹ️](./mobile/React_Native_Performance_Optimization.md) | [👆 View](./mobile/React_Native_Performance_Optimization.md) |
|
| 128 |
+
| **🔧 DevOps** | Ansible Playbook Creation | [ℹ️](./devops/Ansible_Playbook_Creation.md) | [👆 View](./devops/Ansible_Playbook_Creation.md) |
|
| 129 |
+
| | Automated Database Backups | [ℹ️](./devops/Automated_Database_Backups.md) | [👆 View](./devops/Automated_Database_Backups.md) |
|
| 130 |
+
| | AWS Lambda Function Design | [ℹ️](./devops/AWS_Lambda_Function_Design.md) | [👆 View](./devops/AWS_Lambda_Function_Design.md) |
|
| 131 |
+
| | AWS Secrets Manager Integration | [ℹ️](./devops/AWS_Secrets_Manager_Integration.md) | [👆 View](./devops/AWS_Secrets_Manager_Integration.md) |
|
| 132 |
+
| | Azure Functions Basics | [ℹ️](./devops/Azure_Functions_Basics.md) | [👆 View](./devops/Azure_Functions_Basics.md) |
|
| 133 |
+
| | Blue/Green Deployment | [ℹ️](./devops/Blue_Green_Deployment_Strategy.md) | [👆 View](./devops/Blue_Green_Deployment_Strategy.md) |
|
| 134 |
+
| | Chaos Engineering Basics | [ℹ️](./devops/Chaos_Engineering_Basics.md) | [👆 View](./devops/Chaos_Engineering_Basics.md) |
|
| 135 |
+
| | CI Pipeline (GitHub Actions) | [ℹ️](./devops/CI_Pipeline_GitHub_Actions.md) | [👆 View](./devops/CI_Pipeline_GitHub_Actions.md) |
|
| 136 |
+
| | Cost Optimization (AWS/Cloud) | [ℹ️](./devops/Cost_Optimization_Cloud.md) | [👆 View](./devops/Cost_Optimization_Cloud.md) |
|
| 137 |
+
| | Dependency Update Audit | [ℹ️](./devops/Dependency_Update_Audit.md) | [👆 View](./devops/Dependency_Update_Audit.md) |
|
| 138 |
+
| | Docker Containerization (Node) | [ℹ️](./devops/Docker_Containerization_Node.md) | [👆 View](./devops/Docker_Containerization_Node.md) |
|
| 139 |
+
| | Git Branching Strategy | [ℹ️](./devops/Git_Branching_Strategy.md) | [👆 View](./devops/Git_Branching_Strategy.md) |
|
| 140 |
+
| | Google Cloud Run Deployment | [ℹ️](./devops/Google_Cloud_Run_Deployment.md) | [👆 View](./devops/Google_Cloud_Run_Deployment.md) |
|
| 141 |
+
| | Infrastructure as Code (Terraform) | [ℹ️](./devops/Infrastructure_as_Code_Terraform.md) | [👆 View](./devops/Infrastructure_as_Code_Terraform.md) |
|
| 142 |
+
| | K8s Deployment Manifests | [ℹ️](./devops/Kubernetes_Deployment_Manifests.md) | [👆 View](./devops/Kubernetes_Deployment_Manifests.md) |
|
| 143 |
+
| | Log Aggregation (ELK) | [ℹ️](./devops/Log_Aggregation_ELK.md) | [👆 View](./devops/Log_Aggregation_ELK.md) |
|
| 144 |
+
| | Monitoring with Datadog | [ℹ️](./devops/Monitoring_with_Datadog.md) | [👆 View](./devops/Monitoring_with_Datadog.md) |
|
| 145 |
+
| | Nginx Reverse Proxy Setup | [ℹ️](./devops/Nginx_Reverse_Proxy_Setup.md) | [👆 View](./devops/Nginx_Reverse_Proxy_Setup.md) |
|
| 146 |
+
| | Prometheus & Grafana Monitoring | [ℹ️](./devops/Prometheus_Grafana_Monitoring.md) | [👆 View](./devops/Prometheus_Grafana_Monitoring.md) |
|
| 147 |
+
| | Sentry Error Tracking Setup | [ℹ️](./devops/Sentry_Error_Tracking.md) | [👆 View](./devops/Sentry_Error_Tracking.md) |
|
| 148 |
+
| | Serverless Framework Setup | [ℹ️](./devops/Serverless_Framework_Setup.md) | [👆 View](./devops/Serverless_Framework_Setup.md) |
|
| 149 |
+
| | SSL/TLS Setup (Certbot) | [ℹ️](./devops/SSL_TLS_Certbot_Setup.md) | [👆 View](./devops/SSL_TLS_Certbot_Setup.md) |
|
| 150 |
+
| | Terraform Best Practices (Advanced) | [ℹ️](./devops/Terraform_Advanced.md) | [👆 View](./devops/Terraform_Advanced.md) |
|
| 151 |
+
| | Docker Swarm Orchestration | [ℹ️](./devops/Docker_Swarm_Orchestration.md) | [👆 View](./devops/Docker_Swarm_Orchestration.md) |
|
| 152 |
+
| | Kubernetes Helm Charts | [ℹ️](./devops/Kubernetes_Helm_Charts.md) | [👆 View](./devops/Kubernetes_Helm_Charts.md) |
|
| 153 |
+
| | Edge Computing with Vercel & Cloudflare Workers | [ℹ️](./devops/Edge_Computing_Vercel_Cloudflare.md) | [👆 View](./devops/Edge_Computing_Vercel_Cloudflare.md) |
|
| 154 |
+
| **🛡️ Security** | Content Security Policy (CSP) Setup | [ℹ️](./security/Content_Security_Policy_CSP.md) | [👆 View](./security/Content_Security_Policy_CSP.md) |
|
| 155 |
+
| | CSRF Protection Strategies | [ℹ️](./security/CSRF_Protection_Strategies.md) | [👆 View](./security/CSRF_Protection_Strategies.md) |
|
| 156 |
+
| | Dependency Vulnerability Scanning | [ℹ️](./security/Dependency_Vulnerability_Scanning.md) | [👆 View](./security/Dependency_Vulnerability_Scanning.md) |
|
| 157 |
+
| | OWASP Top 10 Mitigation | [ℹ️](./security/OWASP_Top_10_Mitigation.md) | [👆 View](./security/OWASP_Top_10_Mitigation.md) |
|
| 158 |
+
| | Secret Scanning in CI/CD | [ℹ️](./security/Secret_Scanning_CI_CD.md) | [👆 View](./security/Secret_Scanning_CI_CD.md) |
|
| 159 |
+
| | XSS Prevention Guide | [ℹ️](./security/XSS_Prevention_Guide.md) | [👆 View](./security/XSS_Prevention_Guide.md) |
|
| 160 |
+
| | CORS Configuration Best Practices | [ℹ️](./security/CORS_Configuration_Best_Practices.md) | [👆 View](./security/CORS_Configuration_Best_Practices.md) |
|
| 161 |
+
| | SQL Injection Prevention | [ℹ️](./security/SQL_Injection_Prevention.md) | [👆 View](./security/SQL_Injection_Prevention.md) |
|
| 162 |
+
| | OAuth2 & OIDC Implementation | [ℹ️](./security/OAuth2_OIDC_Implementation.md) | [👆 View](./security/OAuth2_OIDC_Implementation.md) |
|
| 163 |
+
| | Password Hashing Best Practices | [ℹ️](./security/Password_Hashing_Best_Practices.md) | [👆 View](./security/Password_Hashing_Best_Practices.md) |
|
| 164 |
+
| | Multi-factor Authentication (MFA) | [ℹ️](./backend/MFA_Implementation.md) | [👆 View](./backend/MFA_Implementation.md) |
|
| 165 |
+
| | API Security Penetration Testing | [ℹ️](./security/API_Security_Penetration_Testing.md) | [👆 View](./security/API_Security_Penetration_Testing.md) |
|
| 166 |
+
| | Zero Trust Architecture Implementation | [ℹ️](./security/Zero_Trust_Architecture.md) | [👆 View](./security/Zero_Trust_Architecture.md) |
|
| 167 |
+
| **🧪 Testing** | API Integration Testing (Supertest) | [ℹ️](./testing/API_Integration_Testing_Supertest.md) | [👆 View](./testing/API_Integration_Testing_Supertest.md) |
|
| 168 |
+
| | Visual Regression Testing (Playwright) | [ℹ️](./testing/Visual_Regression_Testing_Playwright.md) | [👆 View](./testing/Visual_Regression_Testing_Playwright.md) |
|
| 169 |
+
| | Load & Performance Testing (k6) | [ℹ️](./testing/Load_Testing_k6.md) | [👆 View](./testing/Load_Testing_k6.md) |
|
| 170 |
+
| | Contract Testing (Pact) | [ℹ️](./testing/Consumer_Driven_Contract_Testing_Pact.md) | [👆 View](./testing/Consumer_Driven_Contract_Testing_Pact.md) |
|
| 171 |
+
| | Mutation Testing (Stryker) | [ℹ️](./testing/Mutation_Testing_Stryker.md) | [👆 View](./testing/Mutation_Testing_Stryker.md) |
|
| 172 |
+
| | Automated Accessibility Testing (Axe-core) | [ℹ️](./testing/Automated_Accessibility_Testing_Axe.md) | [👆 View](./testing/Automated_Accessibility_Testing_Axe.md) |
|
| 173 |
+
| | Snapshot Testing (Jest) | [ℹ️](./testing/Snapshot_Testing_Jest.md) | [👆 View](./testing/Snapshot_Testing_Jest.md) |
|
| 174 |
+
| | Test-Driven Development (TDD) | [ℹ️](./testing/Test_Driven_Development_TDD.md) | [👆 View](./testing/Test_Driven_Development_TDD.md) |
|
| 175 |
+
| | Component Testing (RTL) | [ℹ️](./testing/Component_Testing_React_Testing_Library.md) | [👆 View](./testing/Component_Testing_React_Testing_Library.md) |
|
| 176 |
+
| | Mocking External Services (Jest) | [ℹ️](./testing/Mocking_External_Services_Jest.md) | [👆 View](./testing/Mocking_External_Services_Jest.md) |
|
| 177 |
+
| | Test Coverage & Quality (Istanbul) | [ℹ️](./testing/Test_Coverage_Quality_Istanbul.md) | [👆 View](./testing/Test_Coverage_Quality_Istanbul.md) |
|
| 178 |
+
| | Performance Testing with Lighthouse | [ℹ️](./testing/Performance_Testing_Lighthouse.md) | [👆 View](./testing/Performance_Testing_Lighthouse.md) |
|
| 179 |
+
| | Advanced End-to-End Testing with Playwright | [ℹ️](./testing/End-to-End_Testing_Playwright_Advanced.md) | [👆 View](./testing/End-to-End_Testing_Playwright_Advanced.md) |
|
| 180 |
+
| **🤖 AI Engineering** | AI Agent Design Patterns | [ℹ️](./ai_engineering/AI_Agent_Design_Patterns.md) | [👆 View](./ai_engineering/AI_Agent_Design_Patterns.md) |
|
| 181 |
+
| | AI Model Evaluation | [ℹ️](./ai_engineering/AI_Model_Evaluation.md) | [👆 View](./ai_engineering/AI_Model_Evaluation.md) |
|
| 182 |
+
| | Fine-tuning Basics | [ℹ️](./ai_engineering/Fine_tuning_Basics.md) | [👆 View](./ai_engineering/Fine_tuning_Basics.md) |
|
| 183 |
+
| | LangChain Basics | [ℹ️](./ai_engineering/LangChain_Basics.md) | [👆 View](./ai_engineering/LangChain_Basics.md) |
|
| 184 |
+
| | Local LLM Running (Ollama) | [ℹ️](./ai_engineering/Local_LLM_Running_Ollama.md) | [👆 View](./ai_engineering/Local_LLM_Running_Ollama.md) |
|
| 185 |
+
| | OpenAI API Integration | [ℹ️](./ai_engineering/OpenAI_API_Integration.md) | [👆 View](./ai_engineering/OpenAI_API_Integration.md) |
|
| 186 |
+
| | Prompt Engineering Basics | [ℹ️](./ai_engineering/Prompt_Engineering_Basics.md) | [👆 View](./ai_engineering/Prompt_Engineering_Basics.md) |
|
| 187 |
+
| | RAG System Architecture | [ℹ️](./ai_engineering/RAG_System_Architecture.md) | [👆 View](./ai_engineering/RAG_System_Architecture.md) |
|
| 188 |
+
| | Speech-to-Text Implementation (Whisper) | [ℹ️](./ai_engineering/Speech_to_Text_Whisper.md) | [👆 View](./ai_engineering/Speech_to_Text_Whisper.md) |
|
| 189 |
+
| | Vector Database Setup | [ℹ️](./ai_engineering/Vector_Database_Setup.md) | [👆 View](./ai_engineering/Vector_Database_Setup.md) |
|
| 190 |
+
| | ML Model Quantization | [ℹ️](./ai_engineering/ML_Model_Quantization.md) | [👆 View](./ai_engineering/ML_Model_Quantization.md) |
|
| 191 |
+
| | Time Series Forecasting | [ℹ️](./ai_engineering/Time_Series_Forecasting.md) | [👆 View](./ai_engineering/Time_Series_Forecasting.md) |
|
| 192 |
+
| | Distributed Training (Horovod) | [ℹ️](./ai_engineering/Distributed_Training_Horovod.md) | [👆 View](./ai_engineering/Distributed_Training_Horovod.md) |
|
| 193 |
+
| | Computer Vision Object Detection | [ℹ️](./ai_engineering/Computer_Vision_Object_Detection.md) | [👆 View](./ai_engineering/Computer_Vision_Object_Detection.md) |
|
| 194 |
+
| | Data Drift Detection | [ℹ️](./ai_engineering/Data_Drift_Detection.md) | [👆 View](./ai_engineering/Data_Drift_Detection.md) |
|
| 195 |
+
| | Generative AI Image Synthesis | [ℹ️](./ai_engineering/Generative_AI_Image_Synthesis.md) | [👆 View](./ai_engineering/Generative_AI_Image_Synthesis.md) |
|
| 196 |
+
| | Natural Language to SQL (NL2SQL) | [ℹ️](./ai_engineering/Natural_Language_to_SQL.md) | [👆 View](./ai_engineering/Natural_Language_to_SQL.md) |
|
| 197 |
+
| | AI Agents with LangGraph | [ℹ️](./ai_engineering/AI_Agents_LangGraph.md) | [👆 View](./ai_engineering/AI_Agents_LangGraph.md) |
|
| 198 |
+
| | Advanced Vector Databases (Pinecone & Weaviate) | [ℹ️](./ai_engineering/Vector_Databases_Pinecone_Weaviate.md) | [👆 View](./ai_engineering/Vector_Databases_Pinecone_Weaviate.md) |
|
| 199 |
+
| | Time Series Forecasting with LSTM | [ℹ️](./ai_engineering/Time_Series_Forecasting_LSTM.md) | [👆 View](./ai_engineering/Time_Series_Forecasting_LSTM.md) |
|
| 200 |
+
| | Recommender Systems with Collaborative Filtering | [ℹ️](./ai_engineering/Recommender_Systems_Collaborative_Filtering.md) | [👆 View](./ai_engineering/Recommender_Systems_Collaborative_Filtering.md) |
|
| 201 |
+
| **🏗️ Architecture** | Adapter Pattern in TypeScript | [ℹ️](./architecture/Adapter_Pattern_TypeScript.md) | [👆 View](./architecture/Adapter_Pattern_TypeScript.md) |
|
| 202 |
+
| | Clean Architecture in Node.js | [ℹ️](./architecture/Clean_Architecture_Node.md) | [👆 View](./architecture/Clean_Architecture_Node.md) |
|
| 203 |
+
| | CQRS Pattern Implementation | [ℹ️](./architecture/CQRS_Pattern_Implementation.md) | [👆 View](./architecture/CQRS_Pattern_Implementation.md) |
|
| 204 |
+
| | Domain-Driven Design (DDD) Basics | [ℹ️](./architecture/Domain_Driven_Design_Basics.md) | [👆 View](./architecture/Domain_Driven_Design_Basics.md) |
|
| 205 |
+
| | CQRS Implementation | [ℹ️](./architecture/CQRS_Implementation.md) | [👆 View](./architecture/CQRS_Implementation.md) |
|
| 206 |
+
| | Event Sourcing Pattern | [ℹ️](./architecture/Event_Sourcing_Pattern.md) | [👆 View](./architecture/Event_Sourcing_Pattern.md) |
|
| 207 |
+
| **📦 Code Mgmt** | Branch Protection Rules | [ℹ️](./code_management/Branch_Protection_Rules.md) | [👆 View](./code_management/Branch_Protection_Rules.md) |
|
| 208 |
+
| | Code Review Guidelines | [ℹ️](./code_management/Code_Review_Guidelines.md) | [👆 View](./code_management/Code_Review_Guidelines.md) |
|
| 209 |
+
| | Dead Code Elimination | [ℹ️](./code_management/Dead_Code_Elimination.md) | [👆 View](./code_management/Dead_Code_Elimination.md) |
|
| 210 |
+
| | ESLint & Prettier Setup | [ℹ️](./code_management/ESLint_Prettier_Setup.md) | [👆 View](./code_management/ESLint_Prettier_Setup.md) |
|
| 211 |
+
| | Git Commit Convention | [ℹ️](./code_management/Git_Commit_Convention_Conventional_Commits.md) | [👆 View](./code_management/Git_Commit_Convention_Conventional_Commits.md) |
|
| 212 |
+
| | Git Hooks (Husky) | [ℹ️](./code_management/Git_Hooks_Husky.md) | [👆 View](./code_management/Git_Hooks_Husky.md) |
|
| 213 |
+
| | Managing Technical Debt | [ℹ️](./code_management/Managing_Technical_Debt.md) | [👆 View](./code_management/Managing_Technical_Debt.md) |
|
| 214 |
+
| | Monorepo Setup (Turborepo) | [ℹ️](./code_management/Monorepo_Setup_Turborepo.md) | [👆 View](./code_management/Monorepo_Setup_Turborepo.md) |
|
| 215 |
+
| | NPM Scripts Automation | [ℹ️](./code_management/Npm_Scripts_Automation.md) | [👆 View](./code_management/Npm_Scripts_Automation.md) |
|
| 216 |
+
| | Semantic Versioning | [ℹ️](./code_management/Semantic_Versioning_Strategy.md) | [👆 View](./code_management/Semantic_Versioning_Strategy.md) |
|
| 217 |
+
| | Git Workflow Strategies | [ℹ️](./code_management/Git_Workflow_Strategies.md) | [👆 View](./code_management/Git_Workflow_Strategies.md) |
|
| 218 |
+
| | Advanced Dependency Management (Dependabot/Renovate) | [ℹ️](./code_management/Advanced_Dependency_Management_Dependabot_Renovate.md) | [👆 View](./code_management/Advanced_Dependency_Management_Dependabot_Renovate.md) |
|
| 219 |
+
| | Code Documentation Standards (JSDoc/TSDoc) | [ℹ️](./code_management/Code_Documentation_Standards_JSDoc_TSDoc.md) | [👆 View](./code_management/Code_Documentation_Standards_JSDoc_TSDoc.md) |
|
| 220 |
+
| | Pull Request Templates & Standard Operating Procedure | [ℹ️](./code_management/Pull_Request_Templates_SOP.md) | [👆 View](./code_management/Pull_Request_Templates_SOP.md) |
|
| 221 |
+
| | Repository Structure & Organization | [ℹ️](./code_management/Repository_Structure_Organization.md) | [👆 View](./code_management/Repository_Structure_Organization.md) |
|
| 222 |
+
| | Dependency Pinning & Reproducible Builds | [ℹ️](./code_management/Dependency_Pinning_Reproducible_Builds.md) | [👆 View](./code_management/Dependency_Pinning_Reproducible_Builds.md) |
|
| 223 |
+
| | Git Rebase, Merge & Squash Guidelines | [ℹ️](./code_management/Git_Rebase_Merge_Squash_Guidelines.md) | [👆 View](./code_management/Git_Rebase_Merge_Squash_Guidelines.md) |
|
| 224 |
+
| | Pre-Commit & Pre-Push Hooks Automation | [ℹ️](./code_management/Pre-Commit_Pre-Push_Hooks_Automation.md) | [👆 View](./code_management/Pre-Commit_Pre-Push_Hooks_Automation.md) |
|
| 225 |
+
| | CHANGELOG Conventional Changelog Auto-Generation | [ℹ️](./code_management/CHANGELOG_Conventional_Changelog_Auto_Generation.md) | [👆 View](./code_management/CHANGELOG_Conventional_Changelog_Auto_Generation.md) |
|
| 226 |
+
| | Repository Archiving & Legacy Code Retirement | [ℹ️](./code_management/Repository_Archiving_Legacy_Code_Retirement.md) | [👆 View](./code_management/Repository_Archiving_Legacy_Code_Retirement.md) |
|
| 227 |
+
| **📝 Docs** | ADR Records | [ℹ️](./documentation/Architectural_Decision_Records_ADR.md) | [👆 View](./documentation/Architectural_Decision_Records_ADR.md) |
|
| 228 |
+
| | API Documentation | [ℹ️](./documentation/API_Documentation_Best_Practices.md) | [👆 View](./documentation/API_Documentation_Best_Practices.md) |
|
| 229 |
+
| | API Design Guidelines (REST) | [ℹ️](./documentation/API_Design_Guidelines_REST.md) | [👆 View](./documentation/API_Design_Guidelines_REST.md) |
|
| 230 |
+
| | Changelog Maintenance | [ℹ️](./documentation/Changelog_Maintenance.md) | [👆 View](./documentation/Changelog_Maintenance.md) |
|
| 231 |
+
| | Code Comments | [ℹ️](./documentation/Code_Comments_Best_Practices.md) | [👆 View](./documentation/Code_Comments_Best_Practices.md) |
|
| 232 |
+
| | Component Documentation (Storybook) | [ℹ️](./documentation/Component_Documentation_Storybook.md) | [👆 View](./documentation/Component_Documentation_Storybook.md) |
|
| 233 |
+
| | Contributing Guidelines | [ℹ️](./documentation/Contributing_Guidelines_CONTRIBUTING.md) | [👆 View](./documentation/Contributing_Guidelines_CONTRIBUTING.md) |
|
| 234 |
+
| | Diagramming with Mermaid.js | [ℹ️](./documentation/Diagramming_Mermaid_JS.md) | [👆 View](./documentation/Diagramming_Mermaid_JS.md) |
|
| 235 |
+
| | Effective User Documentation | [ℹ️](./documentation/Effective_User_Documentation.md) | [👆 View](./documentation/Effective_User_Documentation.md) |
|
| 236 |
+
| | Project Onboarding | [ℹ️](./documentation/Project_Onboarding_Guide.md) | [👆 View](./documentation/Project_Onboarding_Guide.md) |
|
| 237 |
+
| | Technical Spec Writing (RFC) | [ℹ️](./documentation/Technical_Spec_Writing.md) | [👆 View](./documentation/Technical_Spec_Writing.md) |
|
| 238 |
+
| | User Manual Creation | [ℹ️](./documentation/User_Manual_Creation.md) | [👆 View](./documentation/User_Manual_Creation.md) |
|
| 239 |
+
| | User Story Mapping | [ℹ️](./documentation/User_Story_Mapping.md) | [👆 View](./documentation/User_Story_Mapping.md) |
|
| 240 |
+
| | Writing Effective README | [ℹ️](./documentation/Writing_Effective_README.md) | [👆 View](./documentation/Writing_Effective_README.md) |
|
TRAE-Skills/ai_engineering/AI_Agent_Design_Patterns.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Agent Design Patterns
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To structure autonomous AI systems that can reason, plan, and execute tools to solve complex, multi-step problems using patterns like ReAct and Multi-Agent orchestration.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When the task requires multiple distinct steps (e.g., "Find the price of BTC and email me the summary").
|
| 8 |
+
- When the LLM needs to interact with the outside world (APIs, Databases, Web Search).
|
| 9 |
+
- When the workflow is non-linear and depends on intermediate results.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Tool Definition (Zod-based)
|
| 14 |
+
Define the tools your agent can use with clear descriptions.
|
| 15 |
+
|
| 16 |
+
```typescript
|
| 17 |
+
import { z } from "zod";
|
| 18 |
+
import { tool } from "@langchain/core/tools";
|
| 19 |
+
|
| 20 |
+
const searchTool = tool(
|
| 21 |
+
async ({ query }) => {
|
| 22 |
+
// Implement search logic here
|
| 23 |
+
return `Results for ${query}...`;
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
name: "web_search",
|
| 27 |
+
description: "Search the web for current events or technical info.",
|
| 28 |
+
schema: z.object({
|
| 29 |
+
query: z.string(),
|
| 30 |
+
}),
|
| 31 |
+
}
|
| 32 |
+
);
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### 2. The ReAct Agent Pattern
|
| 36 |
+
Implement the Reasoning + Acting loop.
|
| 37 |
+
|
| 38 |
+
```typescript
|
| 39 |
+
import { ChatOpenAI } from "@langchain/openai";
|
| 40 |
+
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
| 41 |
+
import { MemorySaver } from "@langchain/langgraph";
|
| 42 |
+
|
| 43 |
+
const model = new ChatOpenAI({ modelName: "gpt-4o" });
|
| 44 |
+
const tools = [searchTool];
|
| 45 |
+
const checkpointer = new MemorySaver();
|
| 46 |
+
|
| 47 |
+
const app = createReactAgent({
|
| 48 |
+
llm: model,
|
| 49 |
+
tools,
|
| 50 |
+
checkpointSaver: checkpointer,
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Usage
|
| 54 |
+
const result = await app.invoke(
|
| 55 |
+
{ messages: [{ role: "user", content: "What is the current price of Ethereum?" }] },
|
| 56 |
+
{ configurable: { thread_id: "user_1" } }
|
| 57 |
+
);
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### 3. Multi-Agent Orchestration (Hand-off)
|
| 61 |
+
Structure specialized agents that pass tasks to each other.
|
| 62 |
+
|
| 63 |
+
```typescript
|
| 64 |
+
// Conceptual LangGraph Flow:
|
| 65 |
+
// 1. Router Agent -> Decides if it's a "Coding" or "Writing" task.
|
| 66 |
+
// 2. Coder Agent -> Generates code.
|
| 67 |
+
// 3. Reviewer Agent -> Reviews code. If errors, sends back to Coder.
|
| 68 |
+
// 4. Final Output.
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 4. Guardrails & Safety
|
| 72 |
+
Implement safety checks for tool execution.
|
| 73 |
+
|
| 74 |
+
```typescript
|
| 75 |
+
const safeExecute = (action: string) => {
|
| 76 |
+
const forbidden = ["rm -rf", "delete", "drop table"];
|
| 77 |
+
if (forbidden.some(word => action.includes(word))) {
|
| 78 |
+
throw new Error("Safety violation: forbidden command.");
|
| 79 |
+
}
|
| 80 |
+
};
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### 5. State Management
|
| 84 |
+
Maintain the conversation and tool execution state.
|
| 85 |
+
|
| 86 |
+
```typescript
|
| 87 |
+
// Use LangGraph state to keep track of:
|
| 88 |
+
// - messages
|
| 89 |
+
// - tool_outputs
|
| 90 |
+
// - current_step
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## Constraints
|
| 94 |
+
- **Infinite Loops**: Always set a `maxIterations` or recursion limit.
|
| 95 |
+
- **Context Bloat**: Agents generate a lot of tokens. Prune history or use summarization for long tasks.
|
| 96 |
+
- **Tool Descriptions**: The agent's performance is 90% dependent on how well you describe the tools. Be extremely precise.
|
| 97 |
+
|
| 98 |
+
## Expected Output
|
| 99 |
+
A robust agentic system capable of autonomous problem solving by effectively utilizing provided tools.
|
TRAE-Skills/ai_engineering/AI_Agents_LangGraph.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Building AI Agents with LangGraph
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To build stateful, multi-agent, and agentic systems using LangGraph for complex workflows (reasoning, tool use, multi-step tasks).
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building customer support agents that use multiple tools
|
| 8 |
+
- For research assistants that can browse, analyze, and summarize
|
| 9 |
+
- When creating multi-agent collaboration systems
|
| 10 |
+
- For building RAG with reasoning loops
|
| 11 |
+
- When you need agentic workflows with human-in-the-loop
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Basic LangGraph Agent
|
| 16 |
+
Create a simple agent with tools.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
from typing import Annotated, Literal, TypedDict
|
| 20 |
+
from langchain_openai import ChatOpenAI
|
| 21 |
+
from langchain_core.tools import tool
|
| 22 |
+
from langgraph.graph import StateGraph, START, END
|
| 23 |
+
from langgraph.graph.message import add_messages
|
| 24 |
+
from langgraph.prebuilt import ToolNode
|
| 25 |
+
|
| 26 |
+
# Define tools
|
| 27 |
+
@tool
|
| 28 |
+
def search_web(query: str) -> str:
|
| 29 |
+
"""Search the web for a query."""
|
| 30 |
+
return f"Search results for '{query}': LangGraph is a library for building stateful agents."
|
| 31 |
+
|
| 32 |
+
@tool
|
| 33 |
+
def calculate(expression: str) -> str:
|
| 34 |
+
"""Calculate a mathematical expression."""
|
| 35 |
+
try:
|
| 36 |
+
return str(eval(expression))
|
| 37 |
+
except:
|
| 38 |
+
return "Invalid expression"
|
| 39 |
+
|
| 40 |
+
tools = [search_web, calculate]
|
| 41 |
+
|
| 42 |
+
# Define state
|
| 43 |
+
class AgentState(TypedDict):
|
| 44 |
+
messages: Annotated[list, add_messages]
|
| 45 |
+
|
| 46 |
+
# Initialize model
|
| 47 |
+
llm = ChatOpenAI(model="gpt-4o", temperature=0)
|
| 48 |
+
llm_with_tools = llm.bind_tools(tools)
|
| 49 |
+
|
| 50 |
+
# Define nodes
|
| 51 |
+
def agent_node(state: AgentState):
|
| 52 |
+
response = llm_with_tools.invoke(state["messages"])
|
| 53 |
+
return {"messages": [response]}
|
| 54 |
+
|
| 55 |
+
tool_node = ToolNode(tools)
|
| 56 |
+
|
| 57 |
+
# Define conditional edge
|
| 58 |
+
def should_continue(state: AgentState) -> Literal["tools", END]:
|
| 59 |
+
last_message = state["messages"][-1]
|
| 60 |
+
if last_message.tool_calls:
|
| 61 |
+
return "tools"
|
| 62 |
+
return END
|
| 63 |
+
|
| 64 |
+
# Build graph
|
| 65 |
+
graph = StateGraph(AgentState)
|
| 66 |
+
graph.add_node("agent", agent_node)
|
| 67 |
+
graph.add_node("tools", tool_node)
|
| 68 |
+
graph.add_edge(START, "agent")
|
| 69 |
+
graph.add_conditional_edges("agent", should_continue)
|
| 70 |
+
graph.add_edge("tools", "agent")
|
| 71 |
+
|
| 72 |
+
app = graph.compile()
|
| 73 |
+
|
| 74 |
+
# Run agent
|
| 75 |
+
result = app.invoke({"messages": [("user", "What's 25 * 4 + 10? Also, tell me about LangGraph.")]})
|
| 76 |
+
print(result["messages"][-1].content)
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### 2. Multi-Agent Collaboration
|
| 80 |
+
Build a team of specialized agents.
|
| 81 |
+
|
| 82 |
+
```python
|
| 83 |
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 84 |
+
from langgraph.graph import StateGraph, START, END
|
| 85 |
+
|
| 86 |
+
# Define specialized agents
|
| 87 |
+
researcher_prompt = ChatPromptTemplate.from_messages([
|
| 88 |
+
("system", "You are a researcher. Use the search tool to find information."),
|
| 89 |
+
MessagesPlaceholder(variable_name="messages")
|
| 90 |
+
])
|
| 91 |
+
|
| 92 |
+
writer_prompt = ChatPromptTemplate.from_messages([
|
| 93 |
+
("system", "You are a writer. Take research and write a concise summary."),
|
| 94 |
+
MessagesPlaceholder(variable_name="messages")
|
| 95 |
+
])
|
| 96 |
+
|
| 97 |
+
reviewer_prompt = ChatPromptTemplate.from_messages([
|
| 98 |
+
("system", "You are a reviewer. Check the summary and improve it if needed."),
|
| 99 |
+
MessagesPlaceholder(variable_name="messages")
|
| 100 |
+
])
|
| 101 |
+
|
| 102 |
+
class TeamState(TypedDict):
|
| 103 |
+
messages: Annotated[list, add_messages]
|
| 104 |
+
current_agent: str
|
| 105 |
+
final_summary: str
|
| 106 |
+
|
| 107 |
+
def researcher_node(state: TeamState):
|
| 108 |
+
researcher_llm = researcher_prompt | llm_with_tools
|
| 109 |
+
response = researcher_llm.invoke(state["messages"])
|
| 110 |
+
return {"messages": [response], "current_agent": "researcher"}
|
| 111 |
+
|
| 112 |
+
def writer_node(state: TeamState):
|
| 113 |
+
writer_llm = writer_prompt | llm
|
| 114 |
+
response = writer_llm.invoke(state["messages"])
|
| 115 |
+
return {"messages": [response], "current_agent": "writer", "final_summary": response.content}
|
| 116 |
+
|
| 117 |
+
def reviewer_node(state: TeamState):
|
| 118 |
+
reviewer_llm = reviewer_prompt | llm
|
| 119 |
+
response = reviewer_llm.invoke(state["messages"])
|
| 120 |
+
return {"messages": [response], "current_agent": "reviewer", "final_summary": response.content}
|
| 121 |
+
|
| 122 |
+
def route_team(state: TeamState) -> Literal["researcher", "writer", "reviewer", END]:
|
| 123 |
+
if not state["current_agent"]:
|
| 124 |
+
return "researcher"
|
| 125 |
+
elif state["current_agent"] == "researcher":
|
| 126 |
+
return "writer"
|
| 127 |
+
elif state["current_agent"] == "writer":
|
| 128 |
+
return "reviewer"
|
| 129 |
+
return END
|
| 130 |
+
|
| 131 |
+
team_graph = StateGraph(TeamState)
|
| 132 |
+
team_graph.add_node("researcher", researcher_node)
|
| 133 |
+
team_graph.add_node("writer", writer_node)
|
| 134 |
+
team_graph.add_node("reviewer", reviewer_node)
|
| 135 |
+
team_graph.add_conditional_edges(START, route_team)
|
| 136 |
+
team_graph.add_conditional_edges("researcher", route_team)
|
| 137 |
+
team_graph.add_conditional_edges("writer", route_team)
|
| 138 |
+
|
| 139 |
+
team_app = team_graph.compile()
|
| 140 |
+
|
| 141 |
+
# Run team
|
| 142 |
+
result = team_app.invoke({"messages": [("user", "Research LangGraph and write a summary.")]})
|
| 143 |
+
print("Final Summary:", result["final_summary"])
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### 3. Human-in-the-Loop (HITL)
|
| 147 |
+
Add human approval steps.
|
| 148 |
+
|
| 149 |
+
```python
|
| 150 |
+
from langgraph.checkpoint.memory import MemorySaver
|
| 151 |
+
|
| 152 |
+
class HITLState(TypedDict):
|
| 153 |
+
messages: Annotated[list, add_messages]
|
| 154 |
+
approved: bool
|
| 155 |
+
|
| 156 |
+
def agent_node_with_approval(state: HITLState):
|
| 157 |
+
if not state.get("approved", False):
|
| 158 |
+
# Wait for human approval (interrupt)
|
| 159 |
+
pass
|
| 160 |
+
response = llm_with_tools.invoke(state["messages"])
|
| 161 |
+
return {"messages": [response]}
|
| 162 |
+
|
| 163 |
+
checkpointer = MemorySaver()
|
| 164 |
+
|
| 165 |
+
hitl_graph = StateGraph(HITLState)
|
| 166 |
+
hitl_graph.add_node("agent", agent_node_with_approval)
|
| 167 |
+
hitl_graph.add_edge(START, "agent")
|
| 168 |
+
hitl_graph.add_conditional_edges("agent", should_continue)
|
| 169 |
+
hitl_graph.add_edge("tools", "agent")
|
| 170 |
+
|
| 171 |
+
hitl_app = hitl_graph.compile(checkpointer=checkpointer, interrupt_before=["agent"])
|
| 172 |
+
|
| 173 |
+
config = {"configurable": {"thread_id": "1"}}
|
| 174 |
+
|
| 175 |
+
# Initial run (interrupts before agent)
|
| 176 |
+
initial_result = hitl_app.invoke({"messages": [("user", "Approve this action?")]}, config)
|
| 177 |
+
|
| 178 |
+
# Human approves
|
| 179 |
+
human_approved_state = hitl_app.update_state(config, {"approved": True})
|
| 180 |
+
|
| 181 |
+
# Continue execution
|
| 182 |
+
final_result = hitl_app.invoke(None, config)
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
### 4. RAG Agent with LangGraph
|
| 186 |
+
Build an agent that does RAG with reasoning.
|
| 187 |
+
|
| 188 |
+
```python
|
| 189 |
+
from langchain_community.vectorstores import InMemoryVectorStore
|
| 190 |
+
from langchain_openai import OpenAIEmbeddings
|
| 191 |
+
from langchain_core.documents import Document
|
| 192 |
+
|
| 193 |
+
# Sample documents
|
| 194 |
+
documents = [
|
| 195 |
+
Document(page_content="LangGraph is for building stateful agents.", metadata={"source": "doc1"}),
|
| 196 |
+
Document(page_content="Agents can use tools and have memory.", metadata={"source": "doc2"})
|
| 197 |
+
]
|
| 198 |
+
|
| 199 |
+
vector_store = InMemoryVectorStore.from_documents(documents, OpenAIEmbeddings())
|
| 200 |
+
retriever = vector_store.as_retriever(k=2)
|
| 201 |
+
|
| 202 |
+
@tool
|
| 203 |
+
def retrieve_documents(query: str) -> str:
|
| 204 |
+
"""Retrieve relevant documents from the knowledge base."""
|
| 205 |
+
docs = retriever.invoke(query)
|
| 206 |
+
return "\n\n".join([f"Source: {d.metadata['source']}\n{d.page_content}" for d in docs])
|
| 207 |
+
|
| 208 |
+
rag_tools = [retrieve_documents]
|
| 209 |
+
rag_llm = llm.bind_tools(rag_tools)
|
| 210 |
+
|
| 211 |
+
# RAG agent graph
|
| 212 |
+
rag_graph = StateGraph(AgentState)
|
| 213 |
+
rag_graph.add_node("agent", lambda s: {"messages": [rag_llm.invoke(s["messages"])]})
|
| 214 |
+
rag_graph.add_node("tools", ToolNode(rag_tools))
|
| 215 |
+
rag_graph.add_edge(START, "agent")
|
| 216 |
+
rag_graph.add_conditional_edges("agent", should_continue)
|
| 217 |
+
rag_graph.add_edge("tools", "agent")
|
| 218 |
+
|
| 219 |
+
rag_app = rag_graph.compile()
|
| 220 |
+
|
| 221 |
+
result = rag_app.invoke({"messages": [("user", "What is LangGraph used for?")]})
|
| 222 |
+
print(result["messages"][-1].content)
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
## Best Practices
|
| 226 |
+
- **State Design**: Keep state minimal and typed
|
| 227 |
+
- **Tool Design**: Make tools with clear, specific descriptions
|
| 228 |
+
- **Error Handling**: Add fallback edges for failures
|
| 229 |
+
- **Checkpoints**: Use checkpoints for long-running workflows
|
| 230 |
+
- **Evaluation**: Test agent workflows with LangSmith
|
| 231 |
+
- **Cost**: Limit tool calls to control costs
|
| 232 |
+
- **Human-in-the-Loop**: Add approval steps for high-stakes actions
|
TRAE-Skills/ai_engineering/AI_Experiment_Tracking.md
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Experiment Tracking
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To systematically track machine learning experiments, including hyperparameters, metrics, artifacts, and results for reproducibility and optimization.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When running multiple ML experiments with different configurations
|
| 8 |
+
- When comparing model performance across different approaches
|
| 9 |
+
- When needing to reproduce experimental results
|
| 10 |
+
- When optimizing hyperparameters and model architectures
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Experiment Tracking Framework
|
| 15 |
+
Create a comprehensive experiment tracking system.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import json
|
| 19 |
+
import logging
|
| 20 |
+
from datetime import datetime
|
| 21 |
+
from typing import Any, Dict, List, Optional
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
import hashlib
|
| 24 |
+
import pandas as pd
|
| 25 |
+
import matplotlib.pyplot as plt
|
| 26 |
+
|
| 27 |
+
class ExperimentTracker:
|
| 28 |
+
"""Track machine learning experiments."""
|
| 29 |
+
|
| 30 |
+
def __init__(self, project_name: str, base_dir: str = "./experiments"):
|
| 31 |
+
self.project_name = project_name
|
| 32 |
+
self.base_dir = Path(base_dir)
|
| 33 |
+
self.project_dir = self.base_dir / project_name
|
| 34 |
+
self.project_dir.mkdir(parents=True, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
self.current_experiment = None
|
| 37 |
+
self.logger = logging.getLogger(f"ExperimentTracker.{project_name}")
|
| 38 |
+
|
| 39 |
+
# Initialize experiment registry
|
| 40 |
+
self.registry_file = self.project_dir / "experiments_registry.json"
|
| 41 |
+
self._init_registry()
|
| 42 |
+
|
| 43 |
+
def _init_registry(self):
|
| 44 |
+
"""Initialize experiments registry."""
|
| 45 |
+
if not self.registry_file.exists():
|
| 46 |
+
self._save_registry({"experiments": [], "total": 0})
|
| 47 |
+
|
| 48 |
+
def _load_registry(self) -> Dict[str, Any]:
|
| 49 |
+
"""Load experiments registry."""
|
| 50 |
+
with open(self.registry_file, 'r') as f:
|
| 51 |
+
return json.load(f)
|
| 52 |
+
|
| 53 |
+
def _save_registry(self, registry: Dict[str, Any]):
|
| 54 |
+
"""Save experiments registry."""
|
| 55 |
+
with open(self.registry_file, 'w') as f:
|
| 56 |
+
json.dump(registry, f, indent=2)
|
| 57 |
+
|
| 58 |
+
def start_experiment(self, name: str, description: str = "", tags: List[str] = None) -> str:
|
| 59 |
+
"""Start a new experiment."""
|
| 60 |
+
experiment_id = self._generate_experiment_id(name)
|
| 61 |
+
|
| 62 |
+
experiment = {
|
| 63 |
+
"id": experiment_id,
|
| 64 |
+
"name": name,
|
| 65 |
+
"description": description,
|
| 66 |
+
"tags": tags or [],
|
| 67 |
+
"start_time": datetime.now().isoformat(),
|
| 68 |
+
"end_time": None,
|
| 69 |
+
"status": "running",
|
| 70 |
+
"hyperparameters": {},
|
| 71 |
+
"metrics": {},
|
| 72 |
+
"artifacts": [],
|
| 73 |
+
"metadata": {}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
# Create experiment directory
|
| 77 |
+
experiment_dir = self.project_dir / experiment_id
|
| 78 |
+
experiment_dir.mkdir(exist_ok=True)
|
| 79 |
+
|
| 80 |
+
# Update registry
|
| 81 |
+
registry = self._load_registry()
|
| 82 |
+
registry["experiments"].append(experiment)
|
| 83 |
+
registry["total"] += 1
|
| 84 |
+
self._save_registry(registry)
|
| 85 |
+
|
| 86 |
+
self.current_experiment = experiment
|
| 87 |
+
self.logger.info(f"Started experiment: {name} (ID: {experiment_id})")
|
| 88 |
+
|
| 89 |
+
return experiment_id
|
| 90 |
+
|
| 91 |
+
def log_hyperparameters(self, params: Dict[str, Any]):
|
| 92 |
+
"""Log hyperparameters for current experiment."""
|
| 93 |
+
if not self.current_experiment:
|
| 94 |
+
raise ValueError("No active experiment. Call start_experiment() first.")
|
| 95 |
+
|
| 96 |
+
self.current_experiment["hyperparameters"].update(params)
|
| 97 |
+
self._update_current_experiment()
|
| 98 |
+
self.logger.info(f"Logged hyperparameters: {params}")
|
| 99 |
+
|
| 100 |
+
def log_metrics(self, metrics: Dict[str, float], step: Optional[int] = None):
|
| 101 |
+
"""Log metrics for current experiment."""
|
| 102 |
+
if not self.current_experiment:
|
| 103 |
+
raise ValueError("No active experiment. Call start_experiment() first.")
|
| 104 |
+
|
| 105 |
+
if step is not None:
|
| 106 |
+
if "metric_history" not in self.current_experiment:
|
| 107 |
+
self.current_experiment["metric_history"] = {}
|
| 108 |
+
|
| 109 |
+
for metric_name, value in metrics.items():
|
| 110 |
+
if metric_name not in self.current_experiment["metric_history"]:
|
| 111 |
+
self.current_experiment["metric_history"][metric_name] = []
|
| 112 |
+
|
| 113 |
+
self.current_experiment["metric_history"][metric_name].append({
|
| 114 |
+
"step": step,
|
| 115 |
+
"value": value,
|
| 116 |
+
"timestamp": datetime.now().isoformat()
|
| 117 |
+
})
|
| 118 |
+
|
| 119 |
+
self.current_experiment["metrics"].update(metrics)
|
| 120 |
+
self._update_current_experiment()
|
| 121 |
+
self.logger.info(f"Logged metrics: {metrics}")
|
| 122 |
+
|
| 123 |
+
def log_artifact(self, artifact_path: str, artifact_type: str = "file"):
|
| 124 |
+
"""Log an artifact for current experiment."""
|
| 125 |
+
if not self.current_experiment:
|
| 126 |
+
raise ValueError("No active experiment. Call start_experiment() first.")
|
| 127 |
+
|
| 128 |
+
import shutil
|
| 129 |
+
experiment_dir = self.project_dir / self.current_experiment["id"]
|
| 130 |
+
artifact_name = Path(artifact_path).name
|
| 131 |
+
dest_path = experiment_dir / "artifacts" / artifact_name
|
| 132 |
+
|
| 133 |
+
# Create artifacts directory
|
| 134 |
+
dest_path.parent.mkdir(exist_ok=True)
|
| 135 |
+
|
| 136 |
+
# Copy artifact
|
| 137 |
+
shutil.copy2(artifact_path, dest_path)
|
| 138 |
+
|
| 139 |
+
artifact_info = {
|
| 140 |
+
"name": artifact_name,
|
| 141 |
+
"type": artifact_type,
|
| 142 |
+
"original_path": artifact_path,
|
| 143 |
+
"stored_path": str(dest_path),
|
| 144 |
+
"timestamp": datetime.now().isoformat()
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
self.current_experiment["artifacts"].append(artifact_info)
|
| 148 |
+
self._update_current_experiment()
|
| 149 |
+
self.logger.info(f"Logged artifact: {artifact_name}")
|
| 150 |
+
|
| 151 |
+
def end_experiment(self, status: str = "completed"):
|
| 152 |
+
"""End the current experiment."""
|
| 153 |
+
if not self.current_experiment:
|
| 154 |
+
raise ValueError("No active experiment to end.")
|
| 155 |
+
|
| 156 |
+
self.current_experiment["end_time"] = datetime.now().isoformat()
|
| 157 |
+
self.current_experiment["status"] = status
|
| 158 |
+
self._update_current_experiment()
|
| 159 |
+
|
| 160 |
+
self.logger.info(f"Ended experiment: {self.current_experiment['name']} (status: {status})")
|
| 161 |
+
self.current_experiment = None
|
| 162 |
+
|
| 163 |
+
def _update_current_experiment(self):
|
| 164 |
+
"""Update current experiment in registry."""
|
| 165 |
+
if not self.current_experiment:
|
| 166 |
+
return
|
| 167 |
+
|
| 168 |
+
registry = self._load_registry()
|
| 169 |
+
for i, exp in enumerate(registry["experiments"]):
|
| 170 |
+
if exp["id"] == self.current_experiment["id"]:
|
| 171 |
+
registry["experiments"][i] = self.current_experiment
|
| 172 |
+
break
|
| 173 |
+
|
| 174 |
+
self._save_registry(registry)
|
| 175 |
+
|
| 176 |
+
def _generate_experiment_id(self, name: str) -> str:
|
| 177 |
+
"""Generate unique experiment ID."""
|
| 178 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 179 |
+
name_hash = hashlib.md5(name.encode()).hexdigest()[:8]
|
| 180 |
+
return f"exp_{timestamp}_{name_hash}"
|
| 181 |
+
|
| 182 |
+
def get_experiment(self, experiment_id: str) -> Optional[Dict[str, Any]]:
|
| 183 |
+
"""Get experiment by ID."""
|
| 184 |
+
registry = self._load_registry()
|
| 185 |
+
for exp in registry["experiments"]:
|
| 186 |
+
if exp["id"] == experiment_id:
|
| 187 |
+
return exp
|
| 188 |
+
return None
|
| 189 |
+
|
| 190 |
+
def list_experiments(self, status: Optional[str] = None, tags: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
| 191 |
+
"""List experiments with optional filtering."""
|
| 192 |
+
registry = self._load_registry()
|
| 193 |
+
experiments = registry["experiments"]
|
| 194 |
+
|
| 195 |
+
if status:
|
| 196 |
+
experiments = [exp for exp in experiments if exp["status"] == status]
|
| 197 |
+
|
| 198 |
+
if tags:
|
| 199 |
+
experiments = [exp for exp in experiments if any(tag in exp["tags"] for tag in tags)]
|
| 200 |
+
|
| 201 |
+
return experiments
|
| 202 |
+
|
| 203 |
+
def compare_experiments(self, experiment_ids: List[str]) -> pd.DataFrame:
|
| 204 |
+
"""Compare experiments side by side."""
|
| 205 |
+
experiments = []
|
| 206 |
+
for exp_id in experiment_ids:
|
| 207 |
+
exp = self.get_experiment(exp_id)
|
| 208 |
+
if exp:
|
| 209 |
+
experiments.append(exp)
|
| 210 |
+
|
| 211 |
+
if not experiments:
|
| 212 |
+
return pd.DataFrame()
|
| 213 |
+
|
| 214 |
+
comparison_data = []
|
| 215 |
+
for exp in experiments:
|
| 216 |
+
row = {
|
| 217 |
+
"id": exp["id"],
|
| 218 |
+
"name": exp["name"],
|
| 219 |
+
"status": exp["status"],
|
| 220 |
+
**exp["hyperparameters"],
|
| 221 |
+
**exp["metrics"]
|
| 222 |
+
}
|
| 223 |
+
comparison_data.append(row)
|
| 224 |
+
|
| 225 |
+
return pd.DataFrame(comparison_data)
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
### 2. Automated Hyperparameter Logging
|
| 229 |
+
Automatically track hyperparameters.
|
| 230 |
+
|
| 231 |
+
```python
|
| 232 |
+
from functools import wraps
|
| 233 |
+
|
| 234 |
+
def track_hyperparameters(tracker: ExperimentTracker):
|
| 235 |
+
"""Decorator to automatically track function parameters as hyperparameters."""
|
| 236 |
+
def decorator(func):
|
| 237 |
+
@wraps(func)
|
| 238 |
+
def wrapper(*args, **kwargs):
|
| 239 |
+
# Extract hyperparameters from function arguments
|
| 240 |
+
import inspect
|
| 241 |
+
sig = inspect.signature(func)
|
| 242 |
+
bound_args = sig.bind(*args, **kwargs)
|
| 243 |
+
bound_args.apply_defaults()
|
| 244 |
+
|
| 245 |
+
# Log all parameters as hyperparameters
|
| 246 |
+
params = dict(bound_args.arguments)
|
| 247 |
+
# Remove self parameter if present
|
| 248 |
+
params.pop('self', None)
|
| 249 |
+
params.pop('tracker', None)
|
| 250 |
+
|
| 251 |
+
tracker.log_hyperparameters(params)
|
| 252 |
+
|
| 253 |
+
return func(*args, **kwargs)
|
| 254 |
+
return wrapper
|
| 255 |
+
return decorator
|
| 256 |
+
|
| 257 |
+
# Usage
|
| 258 |
+
# @track_hyperparameters(tracker)
|
| 259 |
+
# def train_model(learning_rate, batch_size, epochs, model_type):
|
| 260 |
+
# # Training code here
|
| 261 |
+
# pass
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
### 3. Metrics Visualization
|
| 265 |
+
Create visualizations for experiment comparison.
|
| 266 |
+
|
| 267 |
+
```python
|
| 268 |
+
class ExperimentVisualizer:
|
| 269 |
+
"""Visualize experiment results."""
|
| 270 |
+
|
| 271 |
+
def __init__(self, tracker: ExperimentTracker):
|
| 272 |
+
self.tracker = tracker
|
| 273 |
+
|
| 274 |
+
def plot_metric_comparison(self, metric_name: str, top_n: int = 10):
|
| 275 |
+
"""Plot metric comparison across experiments."""
|
| 276 |
+
experiments = self.tracker.list_experiments(status="completed")
|
| 277 |
+
|
| 278 |
+
# Filter experiments that have the metric
|
| 279 |
+
valid_experiments = [
|
| 280 |
+
exp for exp in experiments
|
| 281 |
+
if metric_name in exp["metrics"]
|
| 282 |
+
]
|
| 283 |
+
|
| 284 |
+
if not valid_experiments:
|
| 285 |
+
print(f"No experiments found with metric: {metric_name}")
|
| 286 |
+
return
|
| 287 |
+
|
| 288 |
+
# Sort by metric value
|
| 289 |
+
valid_experiments.sort(key=lambda x: x["metrics"][metric_name])
|
| 290 |
+
|
| 291 |
+
# Take top N
|
| 292 |
+
top_experiments = valid_experiments[:top_n]
|
| 293 |
+
|
| 294 |
+
# Create plot
|
| 295 |
+
plt.figure(figsize=(12, 6))
|
| 296 |
+
names = [exp["name"] for exp in top_experiments]
|
| 297 |
+
values = [exp["metrics"][metric_name] for exp in top_experiments]
|
| 298 |
+
|
| 299 |
+
plt.bar(range(len(names)), values)
|
| 300 |
+
plt.xticks(range(len(names)), names, rotation=45, ha='right')
|
| 301 |
+
plt.ylabel(metric_name)
|
| 302 |
+
plt.title(f'{metric_name} Comparison (Top {top_n})')
|
| 303 |
+
plt.tight_layout()
|
| 304 |
+
|
| 305 |
+
# Save plot
|
| 306 |
+
output_path = self.tracker.project_dir / f"{metric_name}_comparison.png"
|
| 307 |
+
plt.savefig(output_path)
|
| 308 |
+
plt.close()
|
| 309 |
+
|
| 310 |
+
print(f"Plot saved to: {output_path}")
|
| 311 |
+
|
| 312 |
+
def plot_training_curves(self, experiment_id: str, metric_name: str):
|
| 313 |
+
"""Plot training curves for a specific experiment."""
|
| 314 |
+
exp = self.tracker.get_experiment(experiment_id)
|
| 315 |
+
|
| 316 |
+
if not exp or "metric_history" not in exp:
|
| 317 |
+
print(f"No metric history found for experiment: {experiment_id}")
|
| 318 |
+
return
|
| 319 |
+
|
| 320 |
+
if metric_name not in exp["metric_history"]:
|
| 321 |
+
print(f"Metric {metric_name} not found in experiment history")
|
| 322 |
+
return
|
| 323 |
+
|
| 324 |
+
# Extract data
|
| 325 |
+
history = exp["metric_history"][metric_name]
|
| 326 |
+
steps = [entry["step"] for entry in history]
|
| 327 |
+
values = [entry["value"] for entry in history]
|
| 328 |
+
|
| 329 |
+
# Create plot
|
| 330 |
+
plt.figure(figsize=(10, 6))
|
| 331 |
+
plt.plot(steps, values, marker='o')
|
| 332 |
+
plt.xlabel("Step")
|
| 333 |
+
plt.ylabel(metric_name)
|
| 334 |
+
plt.title(f'{metric_name} Training Curve - {exp["name"]}')
|
| 335 |
+
plt.grid(True)
|
| 336 |
+
|
| 337 |
+
# Save plot
|
| 338 |
+
output_path = self.tracker.project_dir / f"{experiment_id}_{metric_name}_curve.png"
|
| 339 |
+
plt.savefig(output_path)
|
| 340 |
+
plt.close()
|
| 341 |
+
|
| 342 |
+
print(f"Training curve saved to: {output_path}")
|
| 343 |
+
|
| 344 |
+
def create_experiment_report(self, experiment_id: str) -> str:
|
| 345 |
+
"""Create a comprehensive experiment report."""
|
| 346 |
+
exp = self.tracker.get_experiment(experiment_id)
|
| 347 |
+
|
| 348 |
+
if not exp:
|
| 349 |
+
return f"Experiment not found: {experiment_id}"
|
| 350 |
+
|
| 351 |
+
report = f"""
|
| 352 |
+
Experiment Report
|
| 353 |
+
{'=' * 50}
|
| 354 |
+
|
| 355 |
+
Name: {exp['name']}
|
| 356 |
+
ID: {exp['id']}
|
| 357 |
+
Status: {exp['status']}
|
| 358 |
+
Description: {exp['description']}
|
| 359 |
+
|
| 360 |
+
Timeline:
|
| 361 |
+
- Started: {exp['start_time']}
|
| 362 |
+
- Ended: {exp['end_time'] or 'Running'}
|
| 363 |
+
|
| 364 |
+
Tags: {', '.join(exp['tags']) if exp['tags'] else 'None'}
|
| 365 |
+
|
| 366 |
+
Hyperparameters:
|
| 367 |
+
"""
|
| 368 |
+
for param, value in exp['hyperparameters'].items():
|
| 369 |
+
report += f"- {param}: {value}\n"
|
| 370 |
+
|
| 371 |
+
report += f"\nFinal Metrics:\n"
|
| 372 |
+
for metric, value in exp['metrics'].items():
|
| 373 |
+
report += f"- {metric}: {value}\n"
|
| 374 |
+
|
| 375 |
+
if exp['artifacts']:
|
| 376 |
+
report += f"\nArtifacts ({len(exp['artifacts'])}):\n"
|
| 377 |
+
for artifact in exp['artifacts']:
|
| 378 |
+
report += f"- {artifact['name']} ({artifact['type']})\n"
|
| 379 |
+
|
| 380 |
+
return report
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
### 4. Model Artifact Management
|
| 384 |
+
Manage model artifacts and versions.
|
| 385 |
+
|
| 386 |
+
```python
|
| 387 |
+
import joblib
|
| 388 |
+
import pickle
|
| 389 |
+
|
| 390 |
+
class ModelArtifactManager:
|
| 391 |
+
"""Manage model artifacts with versioning."""
|
| 392 |
+
|
| 393 |
+
def __init__(self, tracker: ExperimentTracker):
|
| 394 |
+
self.tracker = tracker
|
| 395 |
+
self.artifacts_dir = tracker.project_dir / "artifacts"
|
| 396 |
+
self.artifacts_dir.mkdir(exist_ok=True)
|
| 397 |
+
|
| 398 |
+
def save_model(self, model, model_name: str, framework: str = "sklearn"):
|
| 399 |
+
"""Save model and log as artifact."""
|
| 400 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 401 |
+
artifact_filename = f"{model_name}_{timestamp}.joblib"
|
| 402 |
+
artifact_path = self.artifacts_dir / artifact_filename
|
| 403 |
+
|
| 404 |
+
# Save model
|
| 405 |
+
if framework == "sklearn":
|
| 406 |
+
joblib.dump(model, artifact_path)
|
| 407 |
+
elif framework == "pickle":
|
| 408 |
+
with open(artifact_path, 'wb') as f:
|
| 409 |
+
pickle.dump(model, f)
|
| 410 |
+
else:
|
| 411 |
+
raise ValueError(f"Unsupported framework: {framework}")
|
| 412 |
+
|
| 413 |
+
# Log artifact
|
| 414 |
+
self.tracker.log_artifact(str(artifact_path), artifact_type="model")
|
| 415 |
+
|
| 416 |
+
return artifact_path
|
| 417 |
+
|
| 418 |
+
def load_model(self, artifact_path: str):
|
| 419 |
+
"""Load model from artifact."""
|
| 420 |
+
if artifact_path.endswith('.joblib'):
|
| 421 |
+
return joblib.load(artifact_path)
|
| 422 |
+
elif artifact_path.endswith('.pkl'):
|
| 423 |
+
with open(artifact_path, 'rb') as f:
|
| 424 |
+
return pickle.load(f)
|
| 425 |
+
else:
|
| 426 |
+
raise ValueError(f"Unsupported artifact format: {artifact_path}")
|
| 427 |
+
|
| 428 |
+
def compare_model_versions(self, model_name: str) -> pd.DataFrame:
|
| 429 |
+
"""Compare different versions of a model."""
|
| 430 |
+
experiments = self.tracker.list_experiments()
|
| 431 |
+
|
| 432 |
+
model_versions = []
|
| 433 |
+
for exp in experiments:
|
| 434 |
+
for artifact in exp['artifacts']:
|
| 435 |
+
if artifact['name'].startswith(model_name) and artifact['type'] == 'model':
|
| 436 |
+
model_versions.append({
|
| 437 |
+
'experiment_id': exp['id'],
|
| 438 |
+
'experiment_name': exp['name'],
|
| 439 |
+
'artifact_name': artifact['name'],
|
| 440 |
+
'created': artifact['timestamp'],
|
| 441 |
+
**exp['metrics']
|
| 442 |
+
})
|
| 443 |
+
|
| 444 |
+
return pd.DataFrame(model_versions)
|
| 445 |
+
```
|
| 446 |
+
|
| 447 |
+
### 5. Experiment Analysis and Insights
|
| 448 |
+
Analyze experiments to derive insights.
|
| 449 |
+
|
| 450 |
+
```python
|
| 451 |
+
class ExperimentAnalyzer:
|
| 452 |
+
"""Analyze experiments to provide insights."""
|
| 453 |
+
|
| 454 |
+
def __init__(self, tracker: ExperimentTracker):
|
| 455 |
+
self.tracker = tracker
|
| 456 |
+
|
| 457 |
+
def find_best_experiment(self, metric_name: str, higher_is_better: bool = True) -> Optional[Dict[str, Any]]:
|
| 458 |
+
"""Find the best performing experiment for a given metric."""
|
| 459 |
+
experiments = self.tracker.list_experiments(status="completed")
|
| 460 |
+
|
| 461 |
+
valid_experiments = [
|
| 462 |
+
exp for exp in experiments
|
| 463 |
+
if metric_name in exp["metrics"]
|
| 464 |
+
]
|
| 465 |
+
|
| 466 |
+
if not valid_experiments:
|
| 467 |
+
return None
|
| 468 |
+
|
| 469 |
+
if higher_is_better:
|
| 470 |
+
best_exp = max(valid_experiments, key=lambda x: x["metrics"][metric_name])
|
| 471 |
+
else:
|
| 472 |
+
best_exp = min(valid_experiments, key=lambda x: x["metrics"][metric_name])
|
| 473 |
+
|
| 474 |
+
return best_exp
|
| 475 |
+
|
| 476 |
+
def analyze_hyperparameter_importance(self, metric_name: str) -> pd.DataFrame:
|
| 477 |
+
"""Analyze the importance of hyperparameters on a metric."""
|
| 478 |
+
experiments = self.tracker.list_experiments(status="completed")
|
| 479 |
+
|
| 480 |
+
valid_experiments = [
|
| 481 |
+
exp for exp in experiments
|
| 482 |
+
if metric_name in exp["metrics"]
|
| 483 |
+
]
|
| 484 |
+
|
| 485 |
+
if not valid_experiments:
|
| 486 |
+
return pd.DataFrame()
|
| 487 |
+
|
| 488 |
+
# Create analysis data
|
| 489 |
+
analysis_data = []
|
| 490 |
+
for exp in valid_experiments:
|
| 491 |
+
row = {
|
| 492 |
+
"experiment_id": exp["id"],
|
| 493 |
+
"experiment_name": exp["name"],
|
| 494 |
+
"metric_value": exp["metrics"][metric_name]
|
| 495 |
+
}
|
| 496 |
+
# Add all hyperparameters
|
| 497 |
+
row.update(exp["hyperparameters"])
|
| 498 |
+
analysis_data.append(row)
|
| 499 |
+
|
| 500 |
+
df = pd.DataFrame(analysis_data)
|
| 501 |
+
|
| 502 |
+
# Calculate correlations for numeric hyperparameters
|
| 503 |
+
numeric_cols = df.select_dtypes(include=['number']).columns
|
| 504 |
+
if len(numeric_cols) > 1:
|
| 505 |
+
correlations = df[numeric_cols].corr()['metric_value'].sort_values(ascending=False)
|
| 506 |
+
return correlations
|
| 507 |
+
|
| 508 |
+
return df
|
| 509 |
+
|
| 510 |
+
def generate_experiment_summary(self) -> str:
|
| 511 |
+
"""Generate a summary of all experiments."""
|
| 512 |
+
experiments = self.tracker.list_experiments()
|
| 513 |
+
|
| 514 |
+
summary = f"""
|
| 515 |
+
Experiment Summary for {self.tracker.project_name}
|
| 516 |
+
{'=' * 60}
|
| 517 |
+
|
| 518 |
+
Total Experiments: {len(experiments)}
|
| 519 |
+
|
| 520 |
+
Status Breakdown:
|
| 521 |
+
"""
|
| 522 |
+
status_counts = {}
|
| 523 |
+
for exp in experiments:
|
| 524 |
+
status = exp["status"]
|
| 525 |
+
status_counts[status] = status_counts.get(status, 0) + 1
|
| 526 |
+
|
| 527 |
+
for status, count in sorted(status_counts.items()):
|
| 528 |
+
summary += f"- {status}: {count}\n"
|
| 529 |
+
|
| 530 |
+
if status_counts.get("completed", 0) > 0:
|
| 531 |
+
summary += f"\nCompleted Experiments: {status_counts['completed']}\n"
|
| 532 |
+
summary += "Best performing experiments by common metrics:\n"
|
| 533 |
+
|
| 534 |
+
common_metrics = {}
|
| 535 |
+
for exp in experiments:
|
| 536 |
+
if exp["status"] == "completed":
|
| 537 |
+
for metric in exp["metrics"].keys():
|
| 538 |
+
if metric not in common_metrics:
|
| 539 |
+
common_metrics[metric] = []
|
| 540 |
+
common_metrics[metric].append((exp["name"], exp["metrics"][metric]))
|
| 541 |
+
|
| 542 |
+
for metric, values in common_metrics.items():
|
| 543 |
+
best = max(values, key=lambda x: x[1])
|
| 544 |
+
summary += f"- {metric}: {best[0]} ({best[1]:.4f})\n"
|
| 545 |
+
|
| 546 |
+
return summary
|
| 547 |
+
```
|
| 548 |
+
|
| 549 |
+
### 6. Complete Example Usage
|
| 550 |
+
Demonstrate complete experiment tracking workflow.
|
| 551 |
+
|
| 552 |
+
```python
|
| 553 |
+
def example_experiment_tracking():
|
| 554 |
+
"""Demonstrate complete experiment tracking."""
|
| 555 |
+
|
| 556 |
+
# Initialize tracker
|
| 557 |
+
tracker = ExperimentTracker(project_name="sentiment_analysis")
|
| 558 |
+
|
| 559 |
+
# Start experiment
|
| 560 |
+
exp_id = tracker.start_experiment(
|
| 561 |
+
name="random_forest_baseline",
|
| 562 |
+
description="Random Forest baseline model for sentiment analysis",
|
| 563 |
+
tags=["baseline", "random_forest"]
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
# Log hyperparameters
|
| 567 |
+
tracker.log_hyperparameters({
|
| 568 |
+
"model_type": "random_forest",
|
| 569 |
+
"n_estimators": 100,
|
| 570 |
+
"max_depth": 10,
|
| 571 |
+
"min_samples_split": 2,
|
| 572 |
+
"learning_rate": None
|
| 573 |
+
})
|
| 574 |
+
|
| 575 |
+
# Simulate training and log metrics
|
| 576 |
+
import numpy as np
|
| 577 |
+
for epoch in range(10):
|
| 578 |
+
# Simulate training
|
| 579 |
+
train_loss = 1.0 - (epoch * 0.08)
|
| 580 |
+
val_loss = 1.0 - (epoch * 0.07) + np.random.normal(0, 0.05)
|
| 581 |
+
|
| 582 |
+
tracker.log_metrics({
|
| 583 |
+
"train_loss": train_loss,
|
| 584 |
+
"val_loss": val_loss,
|
| 585 |
+
"epoch": epoch
|
| 586 |
+
}, step=epoch)
|
| 587 |
+
|
| 588 |
+
# Log final metrics
|
| 589 |
+
tracker.log_metrics({
|
| 590 |
+
"train_accuracy": 0.92,
|
| 591 |
+
"val_accuracy": 0.87,
|
| 592 |
+
"test_accuracy": 0.85,
|
| 593 |
+
"f1_score": 0.84,
|
| 594 |
+
"precision": 0.86,
|
| 595 |
+
"recall": 0.82
|
| 596 |
+
})
|
| 597 |
+
|
| 598 |
+
# Save model artifact
|
| 599 |
+
artifact_manager = ModelArtifactManager(tracker)
|
| 600 |
+
import joblib
|
| 601 |
+
dummy_model = {"model": "dummy_model", "accuracy": 0.85}
|
| 602 |
+
joblib.dump(dummy_model, "dummy_model.joblib")
|
| 603 |
+
artifact_manager.save_model(dummy_model, "sentiment_model")
|
| 604 |
+
|
| 605 |
+
# End experiment
|
| 606 |
+
tracker.end_experiment(status="completed")
|
| 607 |
+
|
| 608 |
+
# Create visualizations
|
| 609 |
+
visualizer = ExperimentVisualizer(tracker)
|
| 610 |
+
visualizer.plot_metric_comparison("test_accuracy")
|
| 611 |
+
visualizer.plot_training_curves(exp_id, "train_loss")
|
| 612 |
+
|
| 613 |
+
# Generate report
|
| 614 |
+
report = visualizer.create_experiment_report(exp_id)
|
| 615 |
+
print(report)
|
| 616 |
+
|
| 617 |
+
# Analyze experiments
|
| 618 |
+
analyzer = ExperimentAnalyzer(tracker)
|
| 619 |
+
best_exp = analyzer.find_best_experiment("test_accuracy")
|
| 620 |
+
print(f"\nBest experiment: {best_exp['name']} with accuracy {best_exp['metrics']['test_accuracy']}")
|
| 621 |
+
|
| 622 |
+
summary = analyzer.generate_experiment_summary()
|
| 623 |
+
print(summary)
|
| 624 |
+
|
| 625 |
+
# Usage
|
| 626 |
+
if __name__ == "__main__":
|
| 627 |
+
logging.basicConfig(level=logging.INFO)
|
| 628 |
+
example_experiment_tracking()
|
| 629 |
+
```
|
| 630 |
+
|
| 631 |
+
## Constraints
|
| 632 |
+
- **Storage Space**: Experiment artifacts can consume significant storage
|
| 633 |
+
- **Performance**: Extensive logging may impact training performance
|
| 634 |
+
- **Reproducibility**: Ensure complete environment capture for true reproducibility
|
| 635 |
+
- **Scalability**: Consider scalability for large numbers of experiments
|
| 636 |
+
- **Privacy**: Be careful with sensitive data in experiment logs
|
| 637 |
+
- **Organization**: Maintain consistent naming and tagging conventions
|
| 638 |
+
|
| 639 |
+
## Expected Output
|
| 640 |
+
Comprehensive experiment tracking system that captures all aspects of ML experiments, enables detailed comparison and analysis, and provides insights for model optimization.
|
TRAE-Skills/ai_engineering/AI_Model_Evaluation.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Model Evaluation
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To systematically assess the performance, accuracy, and safety of LLM outputs using quantitative metrics and "LLM-as-a-Judge" patterns, ensuring production readiness.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- Before deploying any LLM application to production.
|
| 8 |
+
- When comparing different models (e.g., GPT-4o vs. Claude 3.5 Sonnet) or prompt versions.
|
| 9 |
+
- To detect regressions after updating prompts or RAG knowledge bases.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Define the Evaluation Dataset (Golden Set)
|
| 14 |
+
Create a `tests.json` file containing inputs and expected outputs.
|
| 15 |
+
|
| 16 |
+
```json
|
| 17 |
+
[
|
| 18 |
+
{
|
| 19 |
+
"input": "What is the return policy?",
|
| 20 |
+
"expected": "You can return items within 30 days.",
|
| 21 |
+
"context": "Our policy allows returns for 30 days from purchase date."
|
| 22 |
+
}
|
| 23 |
+
]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 2. Implementation with Promptfoo (CLI)
|
| 27 |
+
Promptfoo is a popular tool for running batch evaluations.
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# Install
|
| 31 |
+
npm install -g promptfoo
|
| 32 |
+
|
| 33 |
+
# Initialize
|
| 34 |
+
promptfoo init
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
Configure `promptfooconfig.yaml`:
|
| 38 |
+
```yaml
|
| 39 |
+
prompts:
|
| 40 |
+
- "Answer this question using the context: {{context}}. Question: {{input}}"
|
| 41 |
+
|
| 42 |
+
providers:
|
| 43 |
+
- openai:gpt-4o
|
| 44 |
+
|
| 45 |
+
tests:
|
| 46 |
+
- vars:
|
| 47 |
+
input: "What is the return policy?"
|
| 48 |
+
context: "30-day return policy applies."
|
| 49 |
+
assert:
|
| 50 |
+
- type: icontains
|
| 51 |
+
value: "30 days"
|
| 52 |
+
- type: llm-rubric
|
| 53 |
+
value: "Does not mention unrelated topics"
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
### 3. RAG Evaluation (Ragas/DeepEval)
|
| 57 |
+
For RAG systems, evaluate the three-way relationship: Question, Context, and Answer.
|
| 58 |
+
|
| 59 |
+
```typescript
|
| 60 |
+
import { rce } from "deepeval"; // Conceptual example
|
| 61 |
+
|
| 62 |
+
async function evaluateRag(query: string, retrievalContext: string, output: string) {
|
| 63 |
+
// 1. Faithfulness: Is the answer grounded in the context?
|
| 64 |
+
// 2. Answer Relevance: Does it answer the query?
|
| 65 |
+
// 3. Context Precision: Was the retrieved context relevant?
|
| 66 |
+
}
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 4. Custom LLM-as-a-Judge Script
|
| 70 |
+
Use a stronger model to grade your target model.
|
| 71 |
+
|
| 72 |
+
```typescript
|
| 73 |
+
async function gradeOutput(question: string, answer: string, reference: string) {
|
| 74 |
+
const graderPrompt = `
|
| 75 |
+
You are an impartial judge. Grade the student's answer based on the reference.
|
| 76 |
+
Question: ${question}
|
| 77 |
+
Reference: ${reference}
|
| 78 |
+
Student Answer: ${answer}
|
| 79 |
+
|
| 80 |
+
Provide a score from 1-10 and a brief explanation.
|
| 81 |
+
Output JSON: { "score": number, "explanation": string }
|
| 82 |
+
`;
|
| 83 |
+
|
| 84 |
+
// Call GPT-4 with JSON mode enabled
|
| 85 |
+
}
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### 5. Continuous Integration (CI)
|
| 89 |
+
Integrate evaluation into your GitHub Actions to prevent regressions.
|
| 90 |
+
|
| 91 |
+
```yaml
|
| 92 |
+
# .github/workflows/ai-eval.yml
|
| 93 |
+
jobs:
|
| 94 |
+
evaluate:
|
| 95 |
+
runs-on: ubuntu-latest
|
| 96 |
+
steps:
|
| 97 |
+
- uses: actions/checkout@v4
|
| 98 |
+
- run: npx promptfoo eval
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
## Constraints
|
| 102 |
+
- **Bias**: LLM judges tend to prefer longer answers or answers from the same provider. Use diverse judges (OpenAI + Anthropic).
|
| 103 |
+
- **Cost**: Running evaluations on 1000s of rows can be expensive. Use `gpt-4o-mini` for simpler checks.
|
| 104 |
+
- **Reference Accuracy**: A "Golden Set" is only as good as the human-verified reference answers.
|
| 105 |
+
|
| 106 |
+
## Expected Output
|
| 107 |
+
A detailed report (HTML/JSON) showing pass/fail status, accuracy percentages, and regression analysis.
|
TRAE-Skills/ai_engineering/AI_Model_Serving.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Model Serving
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To deploy and serve machine learning models in production environments with proper scaling, monitoring, and API integration.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying ML models to production
|
| 8 |
+
- When building ML-powered APIs and services
|
| 9 |
+
- When implementing real-time inference systems
|
| 10 |
+
- When scaling ML services for production traffic
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Model Server Setup
|
| 15 |
+
Create a production-ready model server.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
| 19 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 20 |
+
from pydantic import BaseModel, Field
|
| 21 |
+
from typing import List, Optional, Dict, Any
|
| 22 |
+
import joblib
|
| 23 |
+
import logging
|
| 24 |
+
import time
|
| 25 |
+
from datetime import datetime
|
| 26 |
+
import asyncio
|
| 27 |
+
from prometheus_client import Counter, Histogram, generate_latest
|
| 28 |
+
import numpy as np
|
| 29 |
+
|
| 30 |
+
# Define request/response models
|
| 31 |
+
class PredictionRequest(BaseModel):
|
| 32 |
+
model_name: str = Field(..., description="Name of the model to use")
|
| 33 |
+
model_version: str = Field(default="latest", description="Version of the model")
|
| 34 |
+
input_data: Dict[str, Any] = Field(..., description="Input data for prediction")
|
| 35 |
+
preprocessing: Optional[Dict[str, Any]] = Field(default=None, description="Preprocessing options")
|
| 36 |
+
postprocessing: Optional[Dict[str, Any]] = Field(default=None, description="Postprocessing options")
|
| 37 |
+
|
| 38 |
+
class PredictionResponse(BaseModel):
|
| 39 |
+
prediction: Any
|
| 40 |
+
model_name: str
|
| 41 |
+
model_version: str
|
| 42 |
+
prediction_time: float
|
| 43 |
+
timestamp: str
|
| 44 |
+
metadata: Optional[Dict[str, Any]] = None
|
| 45 |
+
|
| 46 |
+
class HealthResponse(BaseModel):
|
| 47 |
+
status: str
|
| 48 |
+
models_loaded: List[str]
|
| 49 |
+
uptime_seconds: float
|
| 50 |
+
version: str
|
| 51 |
+
|
| 52 |
+
# Create FastAPI app
|
| 53 |
+
app = FastAPI(
|
| 54 |
+
title="ML Model Serving API",
|
| 55 |
+
description="Production ML model inference server",
|
| 56 |
+
version="1.0.0"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
# Add CORS middleware
|
| 60 |
+
app.add_middleware(
|
| 61 |
+
CORSMiddleware,
|
| 62 |
+
allow_origins=["*"],
|
| 63 |
+
allow_credentials=True,
|
| 64 |
+
allow_methods=["*"],
|
| 65 |
+
allow_headers=["*"],
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Prometheus metrics
|
| 69 |
+
prediction_counter = Counter('predictions_total', 'Total predictions', ['model_name', 'status'])
|
| 70 |
+
prediction_duration = Histogram('prediction_duration_seconds', 'Prediction duration', ['model_name'])
|
| 71 |
+
|
| 72 |
+
class ModelServer:
|
| 73 |
+
"""Production model server."""
|
| 74 |
+
|
| 75 |
+
def __init__(self, model_registry_path: str = "./models"):
|
| 76 |
+
self.model_registry_path = model_registry_path
|
| 77 |
+
self.loaded_models = {}
|
| 78 |
+
self.model_metadata = {}
|
| 79 |
+
self.start_time = time.time()
|
| 80 |
+
self.logger = logging.getLogger("ModelServer")
|
| 81 |
+
|
| 82 |
+
# Load model registry
|
| 83 |
+
self._load_model_registry()
|
| 84 |
+
|
| 85 |
+
def _load_model_registry(self):
|
| 86 |
+
"""Load model registry."""
|
| 87 |
+
import json
|
| 88 |
+
registry_path = f"{self.model_registry_path}/model_registry.json"
|
| 89 |
+
try:
|
| 90 |
+
with open(registry_path, 'r') as f:
|
| 91 |
+
self.model_registry = json.load(f)
|
| 92 |
+
except FileNotFoundError:
|
| 93 |
+
self.model_registry = {"models": [], "current": None}
|
| 94 |
+
self.logger.warning("Model registry not found, starting with empty registry")
|
| 95 |
+
|
| 96 |
+
def load_model(self, model_name: str, version: str = "latest"):
|
| 97 |
+
"""Load model into memory."""
|
| 98 |
+
model_key = f"{model_name}_{version}"
|
| 99 |
+
|
| 100 |
+
if model_key in self.loaded_models:
|
| 101 |
+
return self.loaded_models[model_key]
|
| 102 |
+
|
| 103 |
+
# Find model in registry
|
| 104 |
+
model_info = None
|
| 105 |
+
for model in self.model_registry["models"]:
|
| 106 |
+
if model["model_type"] == model_name:
|
| 107 |
+
if version == "latest" or model["version"] == version:
|
| 108 |
+
model_info = model
|
| 109 |
+
break
|
| 110 |
+
|
| 111 |
+
if not model_info:
|
| 112 |
+
raise ValueError(f"Model {model_name} version {version} not found")
|
| 113 |
+
|
| 114 |
+
# Load model from disk
|
| 115 |
+
model_path = model_info["model_path"]
|
| 116 |
+
try:
|
| 117 |
+
model = joblib.load(model_path)
|
| 118 |
+
self.loaded_models[model_key] = model
|
| 119 |
+
self.model_metadata[model_key] = model_info
|
| 120 |
+
self.logger.info(f"Loaded model: {model_key}")
|
| 121 |
+
return model
|
| 122 |
+
except Exception as e:
|
| 123 |
+
self.logger.error(f"Failed to load model {model_key}: {str(e)}")
|
| 124 |
+
raise
|
| 125 |
+
|
| 126 |
+
def unload_model(self, model_name: str, version: str = "latest"):
|
| 127 |
+
"""Unload model from memory."""
|
| 128 |
+
model_key = f"{model_name}_{version}"
|
| 129 |
+
if model_key in self.loaded_models:
|
| 130 |
+
del self.loaded_models[model_key]
|
| 131 |
+
del self.model_metadata[model_key]
|
| 132 |
+
self.logger.info(f"Unloaded model: {model_key}")
|
| 133 |
+
|
| 134 |
+
async def predict(self, request: PredictionRequest) -> PredictionResponse:
|
| 135 |
+
"""Make prediction."""
|
| 136 |
+
start_time = time.time()
|
| 137 |
+
model_key = f"{request.model_name}_{request.model_version}"
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
# Load model if not in memory
|
| 141 |
+
model = self.load_model(request.model_name, request.model_version)
|
| 142 |
+
|
| 143 |
+
# Preprocess input
|
| 144 |
+
processed_input = self._preprocess_input(request.input_data, request.preprocessing)
|
| 145 |
+
|
| 146 |
+
# Make prediction
|
| 147 |
+
prediction = model.predict([processed_input])[0] if hasattr(model, 'predict') else model(processed_input)
|
| 148 |
+
|
| 149 |
+
# Postprocess prediction
|
| 150 |
+
final_prediction = self._postprocess_prediction(prediction, request.postprocessing)
|
| 151 |
+
|
| 152 |
+
prediction_time = time.time() - start_time
|
| 153 |
+
|
| 154 |
+
# Record metrics
|
| 155 |
+
prediction_counter.labels(model_name=request.model_name, status='success').inc()
|
| 156 |
+
prediction_duration.labels(model_name=request.model_name).observe(prediction_time)
|
| 157 |
+
|
| 158 |
+
return PredictionResponse(
|
| 159 |
+
prediction=final_prediction,
|
| 160 |
+
model_name=request.model_name,
|
| 161 |
+
model_version=request.model_version,
|
| 162 |
+
prediction_time=prediction_time,
|
| 163 |
+
timestamp=datetime.now().isoformat(),
|
| 164 |
+
metadata=self.model_metadata.get(model_key)
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
except Exception as e:
|
| 168 |
+
prediction_counter.labels(model_name=request.model_name, status='error').inc()
|
| 169 |
+
self.logger.error(f"Prediction failed: {str(e)}")
|
| 170 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 171 |
+
|
| 172 |
+
def _preprocess_input(self, input_data: Dict[str, Any], preprocessing: Optional[Dict[str, Any]]) -> Any:
|
| 173 |
+
"""Preprocess input data."""
|
| 174 |
+
# Implement preprocessing logic based on model requirements
|
| 175 |
+
# This is a placeholder - customize based on your model
|
| 176 |
+
return input_data
|
| 177 |
+
|
| 178 |
+
def _postprocess_prediction(self, prediction: Any, postprocessing: Optional[Dict[str, Any]]) -> Any:
|
| 179 |
+
"""Postprocess prediction."""
|
| 180 |
+
# Implement postprocessing logic
|
| 181 |
+
return prediction
|
| 182 |
+
|
| 183 |
+
def get_health(self) -> HealthResponse:
|
| 184 |
+
"""Get server health status."""
|
| 185 |
+
return HealthResponse(
|
| 186 |
+
status="healthy",
|
| 187 |
+
models_loaded=list(self.loaded_models.keys()),
|
| 188 |
+
uptime_seconds=time.time() - self.start_time,
|
| 189 |
+
version="1.0.0"
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
# Create model server instance
|
| 193 |
+
model_server = ModelServer()
|
| 194 |
+
|
| 195 |
+
# API endpoints
|
| 196 |
+
@app.post("/predict", response_model=PredictionResponse)
|
| 197 |
+
async def predict(request: PredictionRequest):
|
| 198 |
+
"""Make prediction."""
|
| 199 |
+
return await model_server.predict(request)
|
| 200 |
+
|
| 201 |
+
@app.get("/health", response_model=HealthResponse)
|
| 202 |
+
async def health():
|
| 203 |
+
"""Health check endpoint."""
|
| 204 |
+
return model_server.get_health()
|
| 205 |
+
|
| 206 |
+
@app.get("/models")
|
| 207 |
+
async def list_models():
|
| 208 |
+
"""List available models."""
|
| 209 |
+
return {"models": model_server.model_registry["models"]}
|
| 210 |
+
|
| 211 |
+
@app.post("/models/{model_name}/load")
|
| 212 |
+
async def load_model_endpoint(model_name: str, version: str = "latest"):
|
| 213 |
+
"""Load model endpoint."""
|
| 214 |
+
try:
|
| 215 |
+
model_server.load_model(model_name, version)
|
| 216 |
+
return {"status": "success", "message": f"Model {model_name} version {version} loaded"}
|
| 217 |
+
except Exception as e:
|
| 218 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 219 |
+
|
| 220 |
+
@app.delete("/models/{model_name}/unload")
|
| 221 |
+
async def unload_model_endpoint(model_name: str, version: str = "latest"):
|
| 222 |
+
"""Unload model endpoint."""
|
| 223 |
+
model_server.unload_model(model_name, version)
|
| 224 |
+
return {"status": "success", "message": f"Model {model_name} version {version} unloaded"}
|
| 225 |
+
|
| 226 |
+
@app.get("/metrics")
|
| 227 |
+
async def metrics():
|
| 228 |
+
"""Prometheus metrics endpoint."""
|
| 229 |
+
return generate_latest()
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
### 2. Batch Prediction Service
|
| 233 |
+
Handle batch prediction requests efficiently.
|
| 234 |
+
|
| 235 |
+
```python
|
| 236 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 237 |
+
import asyncio
|
| 238 |
+
from typing import List
|
| 239 |
+
|
| 240 |
+
class BatchPredictionService:
|
| 241 |
+
"""Service for batch predictions."""
|
| 242 |
+
|
| 243 |
+
def __init__(self, model_server: ModelServer, max_workers: int = 4):
|
| 244 |
+
self.model_server = model_server
|
| 245 |
+
self.executor = ThreadPoolExecutor(max_workers=max_workers)
|
| 246 |
+
self.logger = logging.getLogger("BatchPredictionService")
|
| 247 |
+
|
| 248 |
+
async def predict_batch(self, requests: List[PredictionRequest]) -> List[PredictionResponse]:
|
| 249 |
+
"""Process multiple prediction requests in parallel."""
|
| 250 |
+
loop = asyncio.get_event_loop()
|
| 251 |
+
|
| 252 |
+
# Create tasks for parallel processing
|
| 253 |
+
tasks = [
|
| 254 |
+
loop.run_in_executor(
|
| 255 |
+
self.executor,
|
| 256 |
+
self._predict_sync,
|
| 257 |
+
request
|
| 258 |
+
)
|
| 259 |
+
for request in requests
|
| 260 |
+
]
|
| 261 |
+
|
| 262 |
+
# Wait for all tasks to complete
|
| 263 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 264 |
+
|
| 265 |
+
# Handle exceptions
|
| 266 |
+
responses = []
|
| 267 |
+
for i, result in enumerate(results):
|
| 268 |
+
if isinstance(result, Exception):
|
| 269 |
+
self.logger.error(f"Batch prediction failed for request {i}: {str(result)}")
|
| 270 |
+
# Create error response
|
| 271 |
+
responses.append(PredictionResponse(
|
| 272 |
+
prediction=None,
|
| 273 |
+
model_name=requests[i].model_name,
|
| 274 |
+
model_version=requests[i].model_version,
|
| 275 |
+
prediction_time=0,
|
| 276 |
+
timestamp=datetime.now().isoformat(),
|
| 277 |
+
metadata={"error": str(result)}
|
| 278 |
+
))
|
| 279 |
+
else:
|
| 280 |
+
responses.append(result)
|
| 281 |
+
|
| 282 |
+
return responses
|
| 283 |
+
|
| 284 |
+
def _predict_sync(self, request: PredictionRequest) -> PredictionResponse:
|
| 285 |
+
"""Synchronous prediction for thread pool."""
|
| 286 |
+
return asyncio.run(self.model_server.predict(request))
|
| 287 |
+
|
| 288 |
+
async def predict_streaming(self, request_generator):
|
| 289 |
+
"""Stream predictions as they complete."""
|
| 290 |
+
loop = asyncio.get_event_loop()
|
| 291 |
+
|
| 292 |
+
async for request in request_generator:
|
| 293 |
+
prediction = await self.model_server.predict(request)
|
| 294 |
+
yield prediction
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
### 3. Model Caching and Optimization
|
| 298 |
+
Implement model caching and prediction optimization.
|
| 299 |
+
|
| 300 |
+
```python
|
| 301 |
+
from functools import lru_cache
|
| 302 |
+
import hashlib
|
| 303 |
+
import json
|
| 304 |
+
|
| 305 |
+
class CachedModelServer(ModelServer):
|
| 306 |
+
"""Model server with caching capabilities."""
|
| 307 |
+
|
| 308 |
+
def __init__(self, *args, cache_size: int = 1000, cache_ttl: int = 3600, **kwargs):
|
| 309 |
+
super().__init__(*args, **kwargs)
|
| 310 |
+
self.cache_size = cache_size
|
| 311 |
+
self.cache_ttl = cache_ttl
|
| 312 |
+
self.cache = {}
|
| 313 |
+
self.cache_timestamps = {}
|
| 314 |
+
self.logger = logging.getLogger("CachedModelServer")
|
| 315 |
+
|
| 316 |
+
def _generate_cache_key(self, model_name: str, model_version: str, input_data: Dict[str, Any]) -> str:
|
| 317 |
+
"""Generate cache key."""
|
| 318 |
+
cache_input = f"{model_name}_{model_version}_{json.dumps(input_data, sort_keys=True)}"
|
| 319 |
+
return hashlib.sha256(cache_input.encode()).hexdigest()
|
| 320 |
+
|
| 321 |
+
def _get_from_cache(self, cache_key: str) -> Optional[Any]:
|
| 322 |
+
"""Get prediction from cache."""
|
| 323 |
+
if cache_key in self.cache:
|
| 324 |
+
cache_time = self.cache_timestamps[cache_key]
|
| 325 |
+
if time.time() - cache_time < self.cache_ttl:
|
| 326 |
+
self.logger.debug(f"Cache hit for key: {cache_key}")
|
| 327 |
+
return self.cache[cache_key]
|
| 328 |
+
else:
|
| 329 |
+
# Cache expired
|
| 330 |
+
del self.cache[cache_key]
|
| 331 |
+
del self.cache_timestamps[cache_key]
|
| 332 |
+
|
| 333 |
+
return None
|
| 334 |
+
|
| 335 |
+
def _set_cache(self, cache_key: str, prediction: Any):
|
| 336 |
+
"""Set prediction in cache."""
|
| 337 |
+
# Implement simple LRU cache
|
| 338 |
+
if len(self.cache) >= self.cache_size:
|
| 339 |
+
# Remove oldest entry
|
| 340 |
+
oldest_key = min(self.cache_timestamps.keys(), key=lambda k: self.cache_timestamps[k])
|
| 341 |
+
del self.cache[oldest_key]
|
| 342 |
+
del self.cache_timestamps[oldest_key]
|
| 343 |
+
|
| 344 |
+
self.cache[cache_key] = prediction
|
| 345 |
+
self.cache_timestamps[cache_key] = time.time()
|
| 346 |
+
|
| 347 |
+
async def predict(self, request: PredictionRequest) -> PredictionResponse:
|
| 348 |
+
"""Predict with caching."""
|
| 349 |
+
cache_key = self._generate_cache_key(request.model_name, request.model_version, request.input_data)
|
| 350 |
+
|
| 351 |
+
# Check cache
|
| 352 |
+
cached_prediction = self._get_from_cache(cache_key)
|
| 353 |
+
if cached_prediction is not None:
|
| 354 |
+
return cached_prediction
|
| 355 |
+
|
| 356 |
+
# Make prediction
|
| 357 |
+
prediction = await super().predict(request)
|
| 358 |
+
|
| 359 |
+
# Cache result
|
| 360 |
+
self._set_cache(cache_key, prediction)
|
| 361 |
+
|
| 362 |
+
return prediction
|
| 363 |
+
|
| 364 |
+
def clear_cache(self):
|
| 365 |
+
"""Clear prediction cache."""
|
| 366 |
+
self.cache.clear()
|
| 367 |
+
self.cache_timestamps.clear()
|
| 368 |
+
self.logger.info("Cache cleared")
|
| 369 |
+
```
|
| 370 |
+
|
| 371 |
+
### 4. Model Version Management
|
| 372 |
+
Manage multiple model versions and A/B testing.
|
| 373 |
+
|
| 374 |
+
```python
|
| 375 |
+
class ModelVersionManager:
|
| 376 |
+
"""Manage model versions and A/B testing."""
|
| 377 |
+
|
| 378 |
+
def __init__(self, model_server: ModelServer):
|
| 379 |
+
self.model_server = model_server
|
| 380 |
+
self.traffic_rules = {}
|
| 381 |
+
self.logger = logging.getLogger("ModelVersionManager")
|
| 382 |
+
|
| 383 |
+
def set_traffic_split(self, model_name: str, version_rules: Dict[str, float]):
|
| 384 |
+
"""Set traffic split for model versions."""
|
| 385 |
+
total_percentage = sum(version_rules.values())
|
| 386 |
+
if abs(total_percentage - 1.0) > 0.01:
|
| 387 |
+
raise ValueError(f"Traffic split must sum to 1.0, got {total_percentage}")
|
| 388 |
+
|
| 389 |
+
self.traffic_rules[model_name] = version_rules
|
| 390 |
+
self.logger.info(f"Set traffic split for {model_name}: {version_rules}")
|
| 391 |
+
|
| 392 |
+
def get_model_for_request(self, model_name: str, request_id: str) -> str:
|
| 393 |
+
"""Determine which model version to use for request."""
|
| 394 |
+
if model_name not in self.traffic_rules:
|
| 395 |
+
return "latest" # Default to latest
|
| 396 |
+
|
| 397 |
+
version_rules = self.traffic_rules[model_name]
|
| 398 |
+
|
| 399 |
+
# Use request_id hash for consistent routing
|
| 400 |
+
hash_value = int(hashlib.md5(request_id.encode()).hexdigest(), 16)
|
| 401 |
+
random_value = (hash_value % 100) / 100.0
|
| 402 |
+
|
| 403 |
+
cumulative = 0.0
|
| 404 |
+
for version, percentage in version_rules.items():
|
| 405 |
+
cumulative += percentage
|
| 406 |
+
if random_value <= cumulative:
|
| 407 |
+
return version
|
| 408 |
+
|
| 409 |
+
return list(version_rules.keys())[-1] # Fallback to last version
|
| 410 |
+
|
| 411 |
+
async def predict_with_routing(self, request: PredictionRequest, request_id: str) -> PredictionResponse:
|
| 412 |
+
"""Predict with intelligent version routing."""
|
| 413 |
+
# Determine version
|
| 414 |
+
version = self.get_model_for_request(request.model_name, request_id)
|
| 415 |
+
request.model_version = version
|
| 416 |
+
|
| 417 |
+
# Make prediction
|
| 418 |
+
prediction = await self.model_server.predict(request)
|
| 419 |
+
prediction.metadata = prediction.metadata or {}
|
| 420 |
+
prediction.metadata["routed_version"] = version
|
| 421 |
+
prediction.metadata["request_id"] = request_id
|
| 422 |
+
|
| 423 |
+
return prediction
|
| 424 |
+
|
| 425 |
+
class ABTestFramework:
|
| 426 |
+
"""A/B testing framework for models."""
|
| 427 |
+
|
| 428 |
+
def __init__(self, version_manager: ModelVersionManager):
|
| 429 |
+
self.version_manager = version_manager
|
| 430 |
+
self.metrics = defaultdict(lambda: {"predictions": 0, "errors": 0, "latencies": []})
|
| 431 |
+
|
| 432 |
+
async def predict_with_tracking(self, request: PredictionRequest, request_id: str) -> PredictionResponse:
|
| 433 |
+
"""Predict with A/B test tracking."""
|
| 434 |
+
prediction = await self.version_manager.predict_with_routing(request, request_id)
|
| 435 |
+
|
| 436 |
+
# Track metrics
|
| 437 |
+
version = prediction.metadata.get("routed_version", "unknown")
|
| 438 |
+
self.metrics[version]["predictions"] += 1
|
| 439 |
+
self.metrics[version]["latencies"].append(prediction.prediction_time)
|
| 440 |
+
|
| 441 |
+
return prediction
|
| 442 |
+
|
| 443 |
+
def get_ab_test_results(self) -> Dict[str, Any]:
|
| 444 |
+
"""Get A/B test results."""
|
| 445 |
+
results = {}
|
| 446 |
+
for version, data in self.metrics.items():
|
| 447 |
+
latencies = data["latencies"]
|
| 448 |
+
results[version] = {
|
| 449 |
+
"predictions": data["predictions"],
|
| 450 |
+
"errors": data["errors"],
|
| 451 |
+
"avg_latency": sum(latencies) / len(latencies) if latencies else 0,
|
| 452 |
+
"p95_latency": sorted(latencies)[int(len(latencies) * 0.95)] if len(latencies) > 20 else 0,
|
| 453 |
+
"error_rate": data["errors"] / data["predictions"] if data["predictions"] > 0 else 0
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
return results
|
| 457 |
+
```
|
| 458 |
+
|
| 459 |
+
### 5. Monitoring and Observability
|
| 460 |
+
Implement comprehensive monitoring for model serving.
|
| 461 |
+
|
| 462 |
+
```python
|
| 463 |
+
from prometheus_client import Counter, Histogram, Gauge, Info
|
| 464 |
+
import logging
|
| 465 |
+
from logging.handlers import RotatingFileHandler
|
| 466 |
+
|
| 467 |
+
class ModelServingMonitor:
|
| 468 |
+
"""Monitor model serving metrics."""
|
| 469 |
+
|
| 470 |
+
def __init__(self):
|
| 471 |
+
# Prometheus metrics
|
| 472 |
+
self.request_counter = Counter('model_requests_total', 'Total model requests', ['model_name', 'version', 'status'])
|
| 473 |
+
self.request_duration = Histogram('model_request_duration_seconds', 'Model request duration', ['model_name', 'version'])
|
| 474 |
+
self.model_load_counter = Counter('model_loads_total', 'Total model loads', ['model_name', 'version', 'status'])
|
| 475 |
+
self.active_models = Gauge('active_models', 'Number of loaded models')
|
| 476 |
+
self.memory_usage = Gauge('memory_usage_bytes', 'Memory usage')
|
| 477 |
+
self.cpu_usage = Gauge('cpu_usage_percent', 'CPU usage percent')
|
| 478 |
+
|
| 479 |
+
# Setup logging
|
| 480 |
+
self._setup_logging()
|
| 481 |
+
|
| 482 |
+
def _setup_logging(self):
|
| 483 |
+
"""Setup detailed logging."""
|
| 484 |
+
logger = logging.getLogger("ModelServing")
|
| 485 |
+
logger.setLevel(logging.INFO)
|
| 486 |
+
|
| 487 |
+
# Rotating file handler
|
| 488 |
+
handler = RotatingFileHandler('model_serving.log', maxBytes=10*1024*1024, backupCount=5)
|
| 489 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 490 |
+
handler.setFormatter(formatter)
|
| 491 |
+
logger.addHandler(handler)
|
| 492 |
+
```
|
| 493 |
+
|
| 494 |
+
## Constraints
|
| 495 |
+
- **Performance**: Model loading and prediction should be optimized for low latency
|
| 496 |
+
- **Scalability**: Design for horizontal scaling and load balancing
|
| 497 |
+
- **Monitoring**: Implement comprehensive monitoring and alerting
|
| 498 |
+
- **Error Handling**: Robust error handling and graceful degradation
|
| 499 |
+
- **Security**: Implement authentication, authorization, and input validation
|
| 500 |
+
- **Resource Management**: Monitor and manage memory, CPU, and GPU usage
|
| 501 |
+
|
| 502 |
+
## Expected Output
|
| 503 |
+
Production-ready model serving infrastructure with proper API design, monitoring, caching, and scalability for reliable ML model deployment.
|
TRAE-Skills/ai_engineering/AI_Monitoring_Observability.md
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Monitoring and Observability
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To implement comprehensive monitoring, logging, and observability for AI systems in production to ensure reliability, performance, and safety.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying AI models to production
|
| 8 |
+
- When monitoring model performance and drift
|
| 9 |
+
- When troubleshooting AI system issues
|
| 10 |
+
- When ensuring SLA compliance and user satisfaction
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Model Performance Monitoring
|
| 15 |
+
Track key performance metrics in real-time.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import time
|
| 19 |
+
import logging
|
| 20 |
+
from datetime import datetime
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
import json
|
| 23 |
+
|
| 24 |
+
class ModelPerformanceMonitor:
|
| 25 |
+
def __init__(self, model_name):
|
| 26 |
+
self.model_name = model_name
|
| 27 |
+
self.metrics = defaultdict(list)
|
| 28 |
+
self.logger = self._setup_logger()
|
| 29 |
+
|
| 30 |
+
def _setup_logger(self):
|
| 31 |
+
"""Setup logging configuration."""
|
| 32 |
+
logger = logging.getLogger(f"{self.model_name}_monitor")
|
| 33 |
+
logger.setLevel(logging.INFO)
|
| 34 |
+
|
| 35 |
+
handler = logging.StreamHandler()
|
| 36 |
+
formatter = logging.Formatter(
|
| 37 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 38 |
+
)
|
| 39 |
+
handler.setFormatter(formatter)
|
| 40 |
+
logger.addHandler(handler)
|
| 41 |
+
|
| 42 |
+
return logger
|
| 43 |
+
|
| 44 |
+
def log_prediction(self, input_data, prediction, latency_ms, metadata=None):
|
| 45 |
+
"""Log individual prediction with metadata."""
|
| 46 |
+
timestamp = datetime.now().isoformat()
|
| 47 |
+
|
| 48 |
+
log_entry = {
|
| 49 |
+
'timestamp': timestamp,
|
| 50 |
+
'model': self.model_name,
|
| 51 |
+
'input_hash': hash(str(input_data)),
|
| 52 |
+
'prediction': str(prediction),
|
| 53 |
+
'latency_ms': latency_ms,
|
| 54 |
+
'metadata': metadata or {}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
self.metrics['predictions'].append(log_entry)
|
| 58 |
+
self.logger.info(f"Prediction logged: {latency_ms}ms")
|
| 59 |
+
|
| 60 |
+
return log_entry
|
| 61 |
+
|
| 62 |
+
def log_feedback(self, prediction_id, feedback):
|
| 63 |
+
"""Log user feedback on predictions."""
|
| 64 |
+
feedback_entry = {
|
| 65 |
+
'prediction_id': prediction_id,
|
| 66 |
+
'feedback': feedback,
|
| 67 |
+
'timestamp': datetime.now().isoformat()
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
self.metrics['feedback'].append(feedback_entry)
|
| 71 |
+
self.logger.info(f"Feedback received: {feedback}")
|
| 72 |
+
|
| 73 |
+
return feedback_entry
|
| 74 |
+
|
| 75 |
+
def calculate_metrics(self, time_window_minutes=5):
|
| 76 |
+
"""Calculate performance metrics over time window."""
|
| 77 |
+
cutoff_time = datetime.now().timestamp() - (time_window_minutes * 60)
|
| 78 |
+
|
| 79 |
+
recent_predictions = [
|
| 80 |
+
p for p in self.metrics['predictions']
|
| 81 |
+
if datetime.fromisoformat(p['timestamp']).timestamp() > cutoff_time
|
| 82 |
+
]
|
| 83 |
+
|
| 84 |
+
if not recent_predictions:
|
| 85 |
+
return {'error': 'No recent predictions'}
|
| 86 |
+
|
| 87 |
+
latencies = [p['latency_ms'] for p in recent_predictions]
|
| 88 |
+
|
| 89 |
+
metrics = {
|
| 90 |
+
'total_predictions': len(recent_predictions),
|
| 91 |
+
'avg_latency_ms': sum(latencies) / len(latencies),
|
| 92 |
+
'p50_latency': sorted(latencies)[len(latencies) // 2],
|
| 93 |
+
'p95_latency': sorted(latencies)[int(len(latencies) * 0.95)],
|
| 94 |
+
'p99_latency': sorted(latencies)[int(len(latencies) * 0.99)],
|
| 95 |
+
'max_latency': max(latencies),
|
| 96 |
+
'min_latency': min(latencies)
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
# Calculate accuracy if feedback available
|
| 100 |
+
if self.metrics['feedback']:
|
| 101 |
+
recent_feedback = [
|
| 102 |
+
f for f in self.metrics['feedback']
|
| 103 |
+
if datetime.fromisoformat(f['timestamp']).timestamp() > cutoff_time
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
if recent_feedback:
|
| 107 |
+
correct = sum(1 for f in recent_feedback if f['feedback'] == 'correct')
|
| 108 |
+
metrics['accuracy'] = correct / len(recent_feedback)
|
| 109 |
+
|
| 110 |
+
return metrics
|
| 111 |
+
|
| 112 |
+
def check_sla_compliance(self, sla_max_latency_ms=500):
|
| 113 |
+
"""Check if SLA requirements are met."""
|
| 114 |
+
metrics = self.calculate_metrics()
|
| 115 |
+
|
| 116 |
+
if 'error' in metrics:
|
| 117 |
+
return {'status': 'error', 'message': metrics['error']}
|
| 118 |
+
|
| 119 |
+
compliance = {
|
| 120 |
+
'sla_max_latency_ms': sla_max_latency_ms,
|
| 121 |
+
'p95_latency': metrics['p95_latency'],
|
| 122 |
+
'sla_met': metrics['p95_latency'] <= sla_max_latency_ms,
|
| 123 |
+
'avg_latency': metrics['avg_latency_ms']
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
if not compliance['sla_met']:
|
| 127 |
+
self.logger.warning(f"SLA violation: P95 latency {metrics['p95_latency']:.2f}ms exceeds {sla_max_latency_ms}ms")
|
| 128 |
+
|
| 129 |
+
return compliance
|
| 130 |
+
|
| 131 |
+
# Usage
|
| 132 |
+
# monitor = ModelPerformanceMonitor("sentiment_model")
|
| 133 |
+
#
|
| 134 |
+
# start_time = time.time()
|
| 135 |
+
# prediction = model.predict("This is great!")
|
| 136 |
+
# latency = (time.time() - start_time) * 1000
|
| 137 |
+
#
|
| 138 |
+
# monitor.log_prediction("This is great!", prediction, latency)
|
| 139 |
+
#
|
| 140 |
+
# metrics = monitor.calculate_metrics()
|
| 141 |
+
# sla_status = monitor.check_sla_compliance(sla_max_latency_ms=500)
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
### 2. Data Drift Detection
|
| 145 |
+
Monitor and detect data distribution changes.
|
| 146 |
+
|
| 147 |
+
```python
|
| 148 |
+
import numpy as np
|
| 149 |
+
from scipy import stats
|
| 150 |
+
from typing import Dict, List
|
| 151 |
+
|
| 152 |
+
class DataDriftDetector:
|
| 153 |
+
def __init__(self, reference_data, significance_level=0.05):
|
| 154 |
+
"""
|
| 155 |
+
Initialize with reference (training) data.
|
| 156 |
+
|
| 157 |
+
Args:
|
| 158 |
+
reference_data: Dictionary of feature names to arrays
|
| 159 |
+
significance_level: Threshold for detecting drift
|
| 160 |
+
"""
|
| 161 |
+
self.reference_data = reference_data
|
| 162 |
+
self.significance_level = significance_level
|
| 163 |
+
self.drift_history = []
|
| 164 |
+
|
| 165 |
+
def calculate_kl_divergence(self, p, q):
|
| 166 |
+
"""Calculate Kullback-Leibler divergence."""
|
| 167 |
+
# Add small epsilon to avoid division by zero
|
| 168 |
+
epsilon = 1e-10
|
| 169 |
+
p = p + epsilon
|
| 170 |
+
q = q + epsilon
|
| 171 |
+
|
| 172 |
+
return np.sum(p * np.log(p / q))
|
| 173 |
+
|
| 174 |
+
def detect_drift(self, new_data):
|
| 175 |
+
"""Detect drift in new data compared to reference."""
|
| 176 |
+
drift_report = {
|
| 177 |
+
'timestamp': datetime.now().isoformat(),
|
| 178 |
+
'features': {},
|
| 179 |
+
'overall_drift_detected': False
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
for feature in self.reference_data.keys():
|
| 183 |
+
if feature not in new_data:
|
| 184 |
+
continue
|
| 185 |
+
|
| 186 |
+
ref_values = self.reference_data[feature]
|
| 187 |
+
new_values = new_data[feature]
|
| 188 |
+
|
| 189 |
+
# Kolmogorov-Smirnov test
|
| 190 |
+
ks_statistic, ks_pvalue = stats.ks_2samp(ref_values, new_values)
|
| 191 |
+
|
| 192 |
+
# Calculate distribution statistics
|
| 193 |
+
ref_mean, ref_std = np.mean(ref_values), np.std(ref_values)
|
| 194 |
+
new_mean, new_std = np.mean(new_values), np.std(new_values)
|
| 195 |
+
|
| 196 |
+
# Feature drift detected if p-value < significance level
|
| 197 |
+
feature_drift = ks_pvalue < self.significance_level
|
| 198 |
+
|
| 199 |
+
feature_report = {
|
| 200 |
+
'drift_detected': feature_drift,
|
| 201 |
+
'ks_statistic': ks_statistic,
|
| 202 |
+
'ks_pvalue': ks_pvalue,
|
| 203 |
+
'reference_mean': ref_mean,
|
| 204 |
+
'new_mean': new_mean,
|
| 205 |
+
'reference_std': ref_std,
|
| 206 |
+
'new_std': new_std,
|
| 207 |
+
'mean_shift': abs(new_mean - ref_mean)
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
drift_report['features'][feature] = feature_report
|
| 211 |
+
|
| 212 |
+
if feature_drift:
|
| 213 |
+
drift_report['overall_drift_detected'] = True
|
| 214 |
+
|
| 215 |
+
self.drift_history.append(drift_report)
|
| 216 |
+
return drift_report
|
| 217 |
+
|
| 218 |
+
def get_drift_summary(self, window_size=10):
|
| 219 |
+
"""Get summary of recent drift detections."""
|
| 220 |
+
recent_drift = self.drift_history[-window_size:]
|
| 221 |
+
|
| 222 |
+
if not recent_drift:
|
| 223 |
+
return {'status': 'No drift history available'}
|
| 224 |
+
|
| 225 |
+
summary = {
|
| 226 |
+
'total_checks': len(recent_drift),
|
| 227 |
+
'drift_detected_count': sum(1 for r in recent_drift if r['overall_drift_detected']),
|
| 228 |
+
'drift_rate': sum(1 for r in recent_drift if r['overall_drift_detected']) / len(recent_drift),
|
| 229 |
+
'most_drifted_features': self._get_most_drifted_features(recent_drift)
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
return summary
|
| 233 |
+
|
| 234 |
+
def _get_most_drifted_features(self, drift_reports):
|
| 235 |
+
"""Identify features with most frequent drift."""
|
| 236 |
+
feature_drift_counts = defaultdict(int)
|
| 237 |
+
|
| 238 |
+
for report in drift_reports:
|
| 239 |
+
for feature, feature_report in report['features'].items():
|
| 240 |
+
if feature_report['drift_detected']:
|
| 241 |
+
feature_drift_counts[feature] += 1
|
| 242 |
+
|
| 243 |
+
return sorted(feature_drift_counts.items(), key=lambda x: x[1], reverse=True)
|
| 244 |
+
|
| 245 |
+
# Usage
|
| 246 |
+
# reference_data = {
|
| 247 |
+
# 'age': np.random.normal(35, 10, 1000),
|
| 248 |
+
# 'income': np.random.normal(50000, 15000, 1000)
|
| 249 |
+
# }
|
| 250 |
+
#
|
| 251 |
+
# detector = DataDriftDetector(reference_data)
|
| 252 |
+
#
|
| 253 |
+
# # Simulate new data with drift
|
| 254 |
+
# new_data = {
|
| 255 |
+
# 'age': np.random.normal(45, 10, 100), # Drifted age
|
| 256 |
+
# 'income': np.random.normal(52000, 15000, 100) # Similar income
|
| 257 |
+
# }
|
| 258 |
+
#
|
| 259 |
+
# drift_report = detector.detect_drift(new_data)
|
| 260 |
+
# print(drift_report)
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
### 3. Error Analysis and Tracking
|
| 264 |
+
Track and analyze model errors.
|
| 265 |
+
|
| 266 |
+
```python
|
| 267 |
+
class ErrorAnalyzer:
|
| 268 |
+
def __init__(self):
|
| 269 |
+
self.errors = []
|
| 270 |
+
self.error_categories = defaultdict(int)
|
| 271 |
+
|
| 272 |
+
def log_error(self, input_data, prediction, ground_truth, error_type, metadata=None):
|
| 273 |
+
"""Log model error with details."""
|
| 274 |
+
error_entry = {
|
| 275 |
+
'timestamp': datetime.now().isoformat(),
|
| 276 |
+
'input_data': str(input_data),
|
| 277 |
+
'prediction': prediction,
|
| 278 |
+
'ground_truth': ground_truth,
|
| 279 |
+
'error_type': error_type,
|
| 280 |
+
'metadata': metadata or {}
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
self.errors.append(error_entry)
|
| 284 |
+
self.error_categories[error_type] += 1
|
| 285 |
+
|
| 286 |
+
return error_entry
|
| 287 |
+
|
| 288 |
+
def analyze_error_patterns(self):
|
| 289 |
+
"""Analyze patterns in errors."""
|
| 290 |
+
if not self.errors:
|
| 291 |
+
return {'status': 'No errors to analyze'}
|
| 292 |
+
|
| 293 |
+
analysis = {
|
| 294 |
+
'total_errors': len(self.errors),
|
| 295 |
+
'error_types': dict(self.error_categories),
|
| 296 |
+
'error_rate_by_type': {},
|
| 297 |
+
'recent_errors': self.errors[-10:] # Last 10 errors
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
# Calculate error rates
|
| 301 |
+
total_errors = len(self.errors)
|
| 302 |
+
for error_type, count in self.error_categories.items():
|
| 303 |
+
analysis['error_rate_by_type'][error_type] = count / total_errors
|
| 304 |
+
|
| 305 |
+
return analysis
|
| 306 |
+
|
| 307 |
+
def identify_error_clusters(self, feature_extractor=None):
|
| 308 |
+
"""Identify clusters of similar errors."""
|
| 309 |
+
if not self.errors:
|
| 310 |
+
return {'status': 'No errors to cluster'}
|
| 311 |
+
|
| 312 |
+
# Extract error features
|
| 313 |
+
error_features = []
|
| 314 |
+
for error in self.errors:
|
| 315 |
+
if feature_extractor:
|
| 316 |
+
features = feature_extractor(error['input_data'])
|
| 317 |
+
else:
|
| 318 |
+
features = hash(error['input_data'])
|
| 319 |
+
|
| 320 |
+
error_features.append({
|
| 321 |
+
'error': error,
|
| 322 |
+
'features': features
|
| 323 |
+
})
|
| 324 |
+
|
| 325 |
+
# Simple clustering by error type
|
| 326 |
+
clusters = defaultdict(list)
|
| 327 |
+
for item in error_features:
|
| 328 |
+
error_type = item['error']['error_type']
|
| 329 |
+
clusters[error_type].append(item['error'])
|
| 330 |
+
|
| 331 |
+
return {
|
| 332 |
+
'num_clusters': len(clusters),
|
| 333 |
+
'cluster_sizes': {k: len(v) for k, v in clusters.items()},
|
| 334 |
+
'clusters': dict(clusters)
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
def generate_error_report(self):
|
| 338 |
+
"""Generate comprehensive error report."""
|
| 339 |
+
patterns = self.analyze_error_patterns()
|
| 340 |
+
clusters = self.identify_error_clusters()
|
| 341 |
+
|
| 342 |
+
report = {
|
| 343 |
+
'summary': {
|
| 344 |
+
'total_errors': patterns['total_errors'],
|
| 345 |
+
'error_types': patterns['error_types']
|
| 346 |
+
},
|
| 347 |
+
'patterns': patterns,
|
| 348 |
+
'clusters': clusters
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
return report
|
| 352 |
+
|
| 353 |
+
# Usage
|
| 354 |
+
# error_analyzer = ErrorAnalyzer()
|
| 355 |
+
#
|
| 356 |
+
# # Log some errors
|
| 357 |
+
# error_analyzer.log_error(
|
| 358 |
+
# input_data="Great product!",
|
| 359 |
+
# prediction="negative",
|
| 360 |
+
# ground_truth="positive",
|
| 361 |
+
# error_type="sentiment_misclassification"
|
| 362 |
+
# )
|
| 363 |
+
#
|
| 364 |
+
# report = error_analyzer.generate_error_report()
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
### 4. System Health Monitoring
|
| 368 |
+
Monitor overall AI system health.
|
| 369 |
+
|
| 370 |
+
```python
|
| 371 |
+
import psutil
|
| 372 |
+
import GPUtil
|
| 373 |
+
|
| 374 |
+
class AISystemHealthMonitor:
|
| 375 |
+
def __init__(self):
|
| 376 |
+
self.health_metrics = []
|
| 377 |
+
|
| 378 |
+
def collect_system_metrics(self):
|
| 379 |
+
"""Collect system resource usage."""
|
| 380 |
+
metrics = {
|
| 381 |
+
'timestamp': datetime.now().isoformat(),
|
| 382 |
+
'cpu_percent': psutil.cpu_percent(interval=1),
|
| 383 |
+
'memory_percent': psutil.virtual_memory().percent,
|
| 384 |
+
'disk_usage': psutil.disk_usage('/').percent,
|
| 385 |
+
'network_sent': psutil.net_io_counters().bytes_sent,
|
| 386 |
+
'network_recv': psutil.net_io_counters().bytes_recv
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
# GPU metrics if available
|
| 390 |
+
try:
|
| 391 |
+
gpus = GPUtil.getGPUs()
|
| 392 |
+
if gpus:
|
| 393 |
+
metrics['gpu_usage'] = gpus[0].load * 100
|
| 394 |
+
metrics['gpu_memory'] = gpus[0].memoryUtil * 100
|
| 395 |
+
except:
|
| 396 |
+
pass
|
| 397 |
+
|
| 398 |
+
self.health_metrics.append(metrics)
|
| 399 |
+
return metrics
|
| 400 |
+
|
| 401 |
+
def check_health_status(self, thresholds=None):
|
| 402 |
+
"""Check if system is healthy based on thresholds."""
|
| 403 |
+
if thresholds is None:
|
| 404 |
+
thresholds = {
|
| 405 |
+
'cpu_percent': 80,
|
| 406 |
+
'memory_percent': 85,
|
| 407 |
+
'disk_usage': 90,
|
| 408 |
+
'gpu_usage': 90
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
latest_metrics = self.collect_system_metrics()
|
| 412 |
+
health_status = {
|
| 413 |
+
'status': 'healthy',
|
| 414 |
+
'alerts': [],
|
| 415 |
+
'metrics': latest_metrics
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
# Check each metric against threshold
|
| 419 |
+
for metric, threshold in thresholds.items():
|
| 420 |
+
if metric in latest_metrics:
|
| 421 |
+
value = latest_metrics[metric]
|
| 422 |
+
if value > threshold:
|
| 423 |
+
health_status['status'] = 'warning'
|
| 424 |
+
health_status['alerts'].append({
|
| 425 |
+
'metric': metric,
|
| 426 |
+
'value': value,
|
| 427 |
+
'threshold': threshold,
|
| 428 |
+
'message': f'{metric} ({value:.1f}%) exceeds threshold ({threshold}%)'
|
| 429 |
+
})
|
| 430 |
+
|
| 431 |
+
return health_status
|
| 432 |
+
|
| 433 |
+
def generate_health_report(self):
|
| 434 |
+
"""Generate system health report."""
|
| 435 |
+
health_status = self.check_health_status()
|
| 436 |
+
|
| 437 |
+
report = f"""
|
| 438 |
+
System Health Report - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 439 |
+
{'=' * 60}
|
| 440 |
+
|
| 441 |
+
Status: {health_status['status'].upper()}
|
| 442 |
+
|
| 443 |
+
System Metrics:
|
| 444 |
+
- CPU Usage: {health_status['metrics']['cpu_percent']:.1f}%
|
| 445 |
+
- Memory Usage: {health_status['metrics']['memory_percent']:.1f}%
|
| 446 |
+
- Disk Usage: {health_status['metrics']['disk_usage']:.1f}%
|
| 447 |
+
"""
|
| 448 |
+
|
| 449 |
+
if 'gpu_usage' in health_status['metrics']:
|
| 450 |
+
report += f"- GPU Usage: {health_status['metrics']['gpu_usage']:.1f}%\n"
|
| 451 |
+
|
| 452 |
+
if health_status['alerts']:
|
| 453 |
+
report += "\nAlerts:\n"
|
| 454 |
+
for alert in health_status['alerts']:
|
| 455 |
+
report += f" ⚠️ {alert['message']}\n"
|
| 456 |
+
else:
|
| 457 |
+
report += "\n✅ All systems operating within normal parameters.\n"
|
| 458 |
+
|
| 459 |
+
return report
|
| 460 |
+
|
| 461 |
+
# Usage
|
| 462 |
+
# health_monitor = AISystemHealthMonitor()
|
| 463 |
+
# health_report = health_monitor.generate_health_report()
|
| 464 |
+
# print(health_report)
|
| 465 |
+
```
|
| 466 |
+
|
| 467 |
+
### 5. Integration with Monitoring Platforms
|
| 468 |
+
Export metrics to monitoring platforms.
|
| 469 |
+
|
| 470 |
+
```python
|
| 471 |
+
class MonitoringPlatformIntegration:
|
| 472 |
+
def __init__(self, platform='prometheus'):
|
| 473 |
+
self.platform = platform
|
| 474 |
+
self.metrics_buffer = []
|
| 475 |
+
|
| 476 |
+
def export_to_prometheus(self, metrics):
|
| 477 |
+
"""Format metrics for Prometheus."""
|
| 478 |
+
prometheus_metrics = []
|
| 479 |
+
|
| 480 |
+
for metric_name, value in metrics.items():
|
| 481 |
+
# Convert metric name to Prometheus format
|
| 482 |
+
prom_name = metric_name.lower().replace(' ', '_')
|
| 483 |
+
prometheus_metrics.append(f"{prom_name} {value}")
|
| 484 |
+
|
| 485 |
+
return '\n'.join(prometheus_metrics)
|
| 486 |
+
|
| 487 |
+
def export_to_datadog(self, metrics):
|
| 488 |
+
"""Format metrics for Datadog."""
|
| 489 |
+
datadog_metrics = []
|
| 490 |
+
|
| 491 |
+
for metric_name, value in metrics.items():
|
| 492 |
+
metric_data = {
|
| 493 |
+
'metric': f'ai.{metric_name.lower().replace(" ", ".")}',
|
| 494 |
+
'points': [[int(time.time()), value]],
|
| 495 |
+
'type': 'gauge'
|
| 496 |
+
}
|
| 497 |
+
datadog_metrics.append(metric_data)
|
| 498 |
+
|
| 499 |
+
return datadog_metrics
|
| 500 |
+
|
| 501 |
+
def send_metrics(self, metrics, api_endpoint):
|
| 502 |
+
"""Send metrics to monitoring platform."""
|
| 503 |
+
if self.platform == 'prometheus':
|
| 504 |
+
formatted_metrics = self.export_to_prometheus(metrics)
|
| 505 |
+
# In real implementation, push to Prometheus Pushgateway
|
| 506 |
+
print(f"Sending to Prometheus:\n{formatted_metrics}")
|
| 507 |
+
|
| 508 |
+
elif self.platform == 'datadog':
|
| 509 |
+
formatted_metrics = self.export_to_datadog(metrics)
|
| 510 |
+
# In real implementation, use Datadog API
|
| 511 |
+
print(f"Sending to Datadog: {formatted_metrics}")
|
| 512 |
+
|
| 513 |
+
return {'status': 'sent', 'count': len(metrics)}
|
| 514 |
+
|
| 515 |
+
# Usage
|
| 516 |
+
# integration = MonitoringPlatformIntegration('prometheus')
|
| 517 |
+
#
|
| 518 |
+
# metrics = {
|
| 519 |
+
# 'model_latency_ms': 145.2,
|
| 520 |
+
# 'prediction_count': 1000,
|
| 521 |
+
# 'error_rate': 0.02
|
| 522 |
+
# }
|
| 523 |
+
#
|
| 524 |
+
# integration.send_metrics(metrics, 'http://pushgateway:9091')
|
| 525 |
+
```
|
| 526 |
+
|
| 527 |
+
## Constraints
|
| 528 |
+
- **Performance Overhead**: Monitoring should not significantly impact system performance
|
| 529 |
+
- **Storage Costs**: Extensive logging can generate large amounts of data
|
| 530 |
+
- **Privacy**: Ensure sensitive data is not logged or is properly anonymized
|
| 531 |
+
- **Real-time Requirements**: Balance between real-time monitoring and batch processing
|
| 532 |
+
- **Alert Fatigue**: Configure thresholds to avoid excessive false alarms
|
| 533 |
+
- **Data Retention**: Implement proper data retention policies for logs and metrics
|
| 534 |
+
|
| 535 |
+
## Expected Output
|
| 536 |
+
Comprehensive monitoring and observability for AI systems with real-time performance tracking, drift detection, error analysis, and system health monitoring for reliable production deployment.
|
TRAE-Skills/ai_engineering/AI_Pipeline_Automation.md
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Pipeline Automation
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To create automated end-to-end machine learning pipelines that handle data ingestion, preprocessing, training, evaluation, and deployment.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building production ML systems
|
| 8 |
+
- When implementing continuous training and deployment
|
| 9 |
+
- When scaling ML workflows
|
| 10 |
+
- When ensuring reproducibility in ML experiments
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Pipeline Framework Setup
|
| 15 |
+
Create a modular pipeline framework.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from abc import ABC, abstractmethod
|
| 19 |
+
from typing import Any, Dict, List
|
| 20 |
+
import logging
|
| 21 |
+
from datetime import datetime
|
| 22 |
+
|
| 23 |
+
class PipelineStep(ABC):
|
| 24 |
+
"""Abstract base class for pipeline steps."""
|
| 25 |
+
|
| 26 |
+
def __init__(self, name: str):
|
| 27 |
+
self.name = name
|
| 28 |
+
self.logger = logging.getLogger(f"Pipeline.{name}")
|
| 29 |
+
|
| 30 |
+
@abstractmethod
|
| 31 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 32 |
+
"""Execute the pipeline step."""
|
| 33 |
+
pass
|
| 34 |
+
|
| 35 |
+
def __call__(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 36 |
+
"""Execute with logging."""
|
| 37 |
+
self.logger.info(f"Starting step: {self.name}")
|
| 38 |
+
start_time = datetime.now()
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
result = self.execute(context)
|
| 42 |
+
duration = (datetime.now() - start_time).total_seconds()
|
| 43 |
+
self.logger.info(f"Completed step: {self.name} in {duration:.2f}s")
|
| 44 |
+
return result
|
| 45 |
+
except Exception as e:
|
| 46 |
+
self.logger.error(f"Failed step: {self.name} - {str(e)}")
|
| 47 |
+
raise
|
| 48 |
+
|
| 49 |
+
class Pipeline:
|
| 50 |
+
"""Machine learning pipeline orchestrator."""
|
| 51 |
+
|
| 52 |
+
def __init__(self, name: str, steps: List[PipelineStep]):
|
| 53 |
+
self.name = name
|
| 54 |
+
self.steps = steps
|
| 55 |
+
self.logger = logging.getLogger(f"Pipeline.{name}")
|
| 56 |
+
self.context = {}
|
| 57 |
+
|
| 58 |
+
def add_step(self, step: PipelineStep):
|
| 59 |
+
"""Add a step to the pipeline."""
|
| 60 |
+
self.steps.append(step)
|
| 61 |
+
|
| 62 |
+
def run(self, initial_context: Dict[str, Any] = None) -> Dict[str, Any]:
|
| 63 |
+
"""Run the entire pipeline."""
|
| 64 |
+
self.context = initial_context or {}
|
| 65 |
+
|
| 66 |
+
self.logger.info(f"Starting pipeline: {self.name}")
|
| 67 |
+
pipeline_start = datetime.now()
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
for step in self.steps:
|
| 71 |
+
self.context = step(self.context)
|
| 72 |
+
|
| 73 |
+
duration = (datetime.now() - pipeline_start).total_seconds()
|
| 74 |
+
self.logger.info(f"Pipeline {self.name} completed in {duration:.2f}s")
|
| 75 |
+
|
| 76 |
+
return self.context
|
| 77 |
+
|
| 78 |
+
except Exception as e:
|
| 79 |
+
self.logger.error(f"Pipeline {self.name} failed: {str(e)}")
|
| 80 |
+
raise
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### 2. Data Ingestion Step
|
| 84 |
+
Automated data collection and loading.
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
import pandas as pd
|
| 88 |
+
from sqlalchemy import create_engine
|
| 89 |
+
import requests
|
| 90 |
+
from io import StringIO
|
| 91 |
+
|
| 92 |
+
class DataIngestionStep(PipelineStep):
|
| 93 |
+
"""Ingest data from various sources."""
|
| 94 |
+
|
| 95 |
+
def __init__(self, sources: Dict[str, Any]):
|
| 96 |
+
super().__init__("data_ingestion")
|
| 97 |
+
self.sources = sources
|
| 98 |
+
|
| 99 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 100 |
+
"""Ingest data from configured sources."""
|
| 101 |
+
data = {}
|
| 102 |
+
|
| 103 |
+
for source_name, source_config in self.sources.items():
|
| 104 |
+
source_type = source_config.get('type')
|
| 105 |
+
|
| 106 |
+
if source_type == 'csv':
|
| 107 |
+
data[source_name] = pd.read_csv(source_config['path'])
|
| 108 |
+
|
| 109 |
+
elif source_type == 'database':
|
| 110 |
+
engine = create_engine(source_config['connection_string'])
|
| 111 |
+
query = source_config['query']
|
| 112 |
+
data[source_name] = pd.read_sql(query, engine)
|
| 113 |
+
|
| 114 |
+
elif source_type == 'api':
|
| 115 |
+
response = requests.get(source_config['url'])
|
| 116 |
+
response.raise_for_status()
|
| 117 |
+
json_data = response.json()
|
| 118 |
+
data[source_name] = pd.DataFrame(json_data)
|
| 119 |
+
|
| 120 |
+
elif source_type == 'json':
|
| 121 |
+
data[source_name] = pd.read_json(source_config['path'])
|
| 122 |
+
|
| 123 |
+
self.logger.info(f"Ingested {len(data[source_name])} rows from {source_name}")
|
| 124 |
+
|
| 125 |
+
context['raw_data'] = data
|
| 126 |
+
return context
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### 3. Data Preprocessing Step
|
| 130 |
+
Automated data cleaning and feature engineering.
|
| 131 |
+
|
| 132 |
+
```python
|
| 133 |
+
from sklearn.preprocessing import StandardScaler, LabelEncoder
|
| 134 |
+
from sklearn.model_selection import train_test_split
|
| 135 |
+
import numpy as np
|
| 136 |
+
|
| 137 |
+
class DataPreprocessingStep(PipelineStep):
|
| 138 |
+
"""Preprocess and prepare data for training."""
|
| 139 |
+
|
| 140 |
+
def __init__(self, preprocessing_config: Dict[str, Any]):
|
| 141 |
+
super().__init__("data_preprocessing")
|
| 142 |
+
self.config = preprocessing_config
|
| 143 |
+
|
| 144 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 145 |
+
"""Preprocess data according to configuration."""
|
| 146 |
+
raw_data = context['raw_data']
|
| 147 |
+
processed_data = {}
|
| 148 |
+
|
| 149 |
+
for data_name, df in raw_data.items():
|
| 150 |
+
# Handle missing values
|
| 151 |
+
if self.config.get('drop_missing', False):
|
| 152 |
+
df = df.dropna()
|
| 153 |
+
elif self.config.get('fill_missing'):
|
| 154 |
+
df = df.fillna(self.config['fill_missing'])
|
| 155 |
+
|
| 156 |
+
# Encode categorical variables
|
| 157 |
+
if self.config.get('encode_categorical', False):
|
| 158 |
+
categorical_cols = df.select_dtypes(include=['object']).columns
|
| 159 |
+
for col in categorical_cols:
|
| 160 |
+
le = LabelEncoder()
|
| 161 |
+
df[col] = le.fit_transform(df[col].astype(str))
|
| 162 |
+
|
| 163 |
+
# Feature scaling
|
| 164 |
+
if self.config.get('scale_features', False):
|
| 165 |
+
numeric_cols = df.select_dtypes(include=[np.number]).columns
|
| 166 |
+
scaler = StandardScaler()
|
| 167 |
+
df[numeric_cols] = scaler.fit_transform(df[numeric_cols])
|
| 168 |
+
|
| 169 |
+
# Store scaler for later use
|
| 170 |
+
context[f'{data_name}_scaler'] = scaler
|
| 171 |
+
|
| 172 |
+
processed_data[data_name] = df
|
| 173 |
+
self.logger.info(f"Preprocessed {data_name}: {df.shape}")
|
| 174 |
+
|
| 175 |
+
context['processed_data'] = processed_data
|
| 176 |
+
return context
|
| 177 |
+
|
| 178 |
+
class DataSplitStep(PipelineStep):
|
| 179 |
+
"""Split data into train, validation, and test sets."""
|
| 180 |
+
|
| 181 |
+
def __init__(self, target_column: str, test_size: float = 0.2, val_size: float = 0.1):
|
| 182 |
+
super().__init__("data_split")
|
| 183 |
+
self.target_column = target_column
|
| 184 |
+
self.test_size = test_size
|
| 185 |
+
self.val_size = val_size
|
| 186 |
+
|
| 187 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 188 |
+
"""Split data for training."""
|
| 189 |
+
processed_data = context['processed_data']
|
| 190 |
+
|
| 191 |
+
for data_name, df in processed_data.items():
|
| 192 |
+
if self.target_column not in df.columns:
|
| 193 |
+
self.logger.warning(f"Target column {self.target_column} not in {data_name}")
|
| 194 |
+
continue
|
| 195 |
+
|
| 196 |
+
X = df.drop(columns=[self.target_column])
|
| 197 |
+
y = df[self.target_column]
|
| 198 |
+
|
| 199 |
+
# First split: separate test set
|
| 200 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
| 201 |
+
X, y, test_size=self.test_size, random_state=42
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
# Second split: separate validation set from training
|
| 205 |
+
val_ratio = self.val_size / (1 - self.test_size)
|
| 206 |
+
X_train, X_val, y_train, y_val = train_test_split(
|
| 207 |
+
X_train, y_train, test_size=val_ratio, random_state=42
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
context[f'{data_name}_train'] = (X_train, y_train)
|
| 211 |
+
context[f'{data_name}_val'] = (X_val, y_val)
|
| 212 |
+
context[f'{data_name}_test'] = (X_test, y_test)
|
| 213 |
+
|
| 214 |
+
self.logger.info(f"Split {data_name}: train={len(X_train)}, val={len(X_val)}, test={len(X_test)}")
|
| 215 |
+
|
| 216 |
+
return context
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
### 4. Model Training Step
|
| 220 |
+
Automated model training with hyperparameter tuning.
|
| 221 |
+
|
| 222 |
+
```python
|
| 223 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 224 |
+
from sklearn.linear_model import LogisticRegression
|
| 225 |
+
from sklearn.model_selection import GridSearchCV
|
| 226 |
+
import joblib
|
| 227 |
+
|
| 228 |
+
class ModelTrainingStep(PipelineStep):
|
| 229 |
+
"""Train machine learning models."""
|
| 230 |
+
|
| 231 |
+
def __init__(self, model_type: str, hyperparameters: Dict[str, Any] = None):
|
| 232 |
+
super().__init__("model_training")
|
| 233 |
+
self.model_type = model_type
|
| 234 |
+
self.hyperparameters = hyperparameters or {}
|
| 235 |
+
|
| 236 |
+
def _get_model(self):
|
| 237 |
+
"""Get model instance based on type."""
|
| 238 |
+
models = {
|
| 239 |
+
'random_forest': RandomForestClassifier,
|
| 240 |
+
'logistic_regression': LogisticRegression
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
if self.model_type not in models:
|
| 244 |
+
raise ValueError(f"Unknown model type: {self.model_type}")
|
| 245 |
+
|
| 246 |
+
return models[self.model_type](**self.hyperparameters)
|
| 247 |
+
|
| 248 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 249 |
+
"""Train the model."""
|
| 250 |
+
# Find training data
|
| 251 |
+
train_key = None
|
| 252 |
+
for key in context.keys():
|
| 253 |
+
if key.endswith('_train'):
|
| 254 |
+
train_key = key
|
| 255 |
+
break
|
| 256 |
+
|
| 257 |
+
if not train_key:
|
| 258 |
+
raise ValueError("No training data found in context")
|
| 259 |
+
|
| 260 |
+
X_train, y_train = context[train_key]
|
| 261 |
+
|
| 262 |
+
# Get validation data if available
|
| 263 |
+
val_key = train_key.replace('_train', '_val')
|
| 264 |
+
X_val = y_val = None
|
| 265 |
+
if val_key in context:
|
| 266 |
+
X_val, y_val = context[val_key]
|
| 267 |
+
|
| 268 |
+
# Create and train model
|
| 269 |
+
model = self._get_model()
|
| 270 |
+
|
| 271 |
+
self.logger.info(f"Training {self.model_type} model...")
|
| 272 |
+
model.fit(X_train, y_train)
|
| 273 |
+
|
| 274 |
+
# Evaluate on validation set
|
| 275 |
+
if X_val is not None:
|
| 276 |
+
val_score = model.score(X_val, y_val)
|
| 277 |
+
self.logger.info(f"Validation score: {val_score:.4f}")
|
| 278 |
+
context['val_score'] = val_score
|
| 279 |
+
|
| 280 |
+
# Store model
|
| 281 |
+
context['model'] = model
|
| 282 |
+
context['model_type'] = self.model_type
|
| 283 |
+
|
| 284 |
+
return context
|
| 285 |
+
|
| 286 |
+
class HyperparameterTuningStep(PipelineStep):
|
| 287 |
+
"""Perform hyperparameter tuning."""
|
| 288 |
+
|
| 289 |
+
def __init__(self, model_type: str, param_grid: Dict[str, List], cv: int = 5):
|
| 290 |
+
super().__init__("hyperparameter_tuning")
|
| 291 |
+
self.model_type = model_type
|
| 292 |
+
self.param_grid = param_grid
|
| 293 |
+
self.cv = cv
|
| 294 |
+
|
| 295 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 296 |
+
"""Perform grid search for hyperparameters."""
|
| 297 |
+
train_key = None
|
| 298 |
+
for key in context.keys():
|
| 299 |
+
if key.endswith('_train'):
|
| 300 |
+
train_key = key
|
| 301 |
+
break
|
| 302 |
+
|
| 303 |
+
X_train, y_train = context[train_key]
|
| 304 |
+
|
| 305 |
+
# Get base model
|
| 306 |
+
if self.model_type == 'random_forest':
|
| 307 |
+
base_model = RandomForestClassifier()
|
| 308 |
+
elif self.model_type == 'logistic_regression':
|
| 309 |
+
base_model = LogisticRegression(max_iter=1000)
|
| 310 |
+
else:
|
| 311 |
+
raise ValueError(f"Unknown model type: {self.model_type}")
|
| 312 |
+
|
| 313 |
+
# Perform grid search
|
| 314 |
+
grid_search = GridSearchCV(
|
| 315 |
+
base_model,
|
| 316 |
+
self.param_grid,
|
| 317 |
+
cv=self.cv,
|
| 318 |
+
scoring='accuracy',
|
| 319 |
+
n_jobs=-1,
|
| 320 |
+
verbose=1
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
self.logger.info("Starting hyperparameter tuning...")
|
| 324 |
+
grid_search.fit(X_train, y_train)
|
| 325 |
+
|
| 326 |
+
# Store results
|
| 327 |
+
context['model'] = grid_search.best_estimator_
|
| 328 |
+
context['best_params'] = grid_search.best_params_
|
| 329 |
+
context['best_score'] = grid_search.best_score_
|
| 330 |
+
|
| 331 |
+
self.logger.info(f"Best parameters: {grid_search.best_params_}")
|
| 332 |
+
self.logger.info(f"Best cross-validation score: {grid_search.best_score_:.4f}")
|
| 333 |
+
|
| 334 |
+
return context
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
### 5. Model Evaluation Step
|
| 338 |
+
Comprehensive model evaluation and reporting.
|
| 339 |
+
|
| 340 |
+
```python
|
| 341 |
+
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score
|
| 342 |
+
import matplotlib.pyplot as plt
|
| 343 |
+
import seaborn as sns
|
| 344 |
+
|
| 345 |
+
class ModelEvaluationStep(PipelineStep):
|
| 346 |
+
"""Evaluate trained models."""
|
| 347 |
+
|
| 348 |
+
def __init__(self, save_plots: bool = True, output_dir: str = './output'):
|
| 349 |
+
super().__init__("model_evaluation")
|
| 350 |
+
self.save_plots = save_plots
|
| 351 |
+
self.output_dir = output_dir
|
| 352 |
+
|
| 353 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 354 |
+
"""Evaluate model on test set."""
|
| 355 |
+
model = context.get('model')
|
| 356 |
+
if not model:
|
| 357 |
+
raise ValueError("No model found in context")
|
| 358 |
+
|
| 359 |
+
# Find test data
|
| 360 |
+
test_key = None
|
| 361 |
+
for key in context.keys():
|
| 362 |
+
if key.endswith('_test'):
|
| 363 |
+
test_key = key
|
| 364 |
+
break
|
| 365 |
+
|
| 366 |
+
if not test_key:
|
| 367 |
+
raise ValueError("No test data found in context")
|
| 368 |
+
|
| 369 |
+
X_test, y_test = context[test_key]
|
| 370 |
+
|
| 371 |
+
# Make predictions
|
| 372 |
+
y_pred = model.predict(X_test)
|
| 373 |
+
test_score = model.score(X_test, y_test)
|
| 374 |
+
|
| 375 |
+
# Calculate metrics
|
| 376 |
+
report = classification_report(y_test, y_pred, output_dict=True)
|
| 377 |
+
conf_matrix = confusion_matrix(y_test, y_pred)
|
| 378 |
+
|
| 379 |
+
# Store results
|
| 380 |
+
context['test_score'] = test_score
|
| 381 |
+
context['classification_report'] = report
|
| 382 |
+
context['confusion_matrix'] = conf_matrix
|
| 383 |
+
|
| 384 |
+
self.logger.info(f"Test score: {test_score:.4f}")
|
| 385 |
+
|
| 386 |
+
# Generate plots
|
| 387 |
+
if self.save_plots:
|
| 388 |
+
self._save_confusion_matrix(conf_matrix, context.get('model_type', 'model'))
|
| 389 |
+
self._save_classification_report(report, context.get('model_type', 'model'))
|
| 390 |
+
|
| 391 |
+
return context
|
| 392 |
+
|
| 393 |
+
def _save_confusion_matrix(self, conf_matrix, model_name):
|
| 394 |
+
"""Save confusion matrix plot."""
|
| 395 |
+
plt.figure(figsize=(8, 6))
|
| 396 |
+
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues')
|
| 397 |
+
plt.title(f'Confusion Matrix - {model_name}')
|
| 398 |
+
plt.ylabel('True Label')
|
| 399 |
+
plt.xlabel('Predicted Label')
|
| 400 |
+
plt.savefig(f'{self.output_dir}/{model_name}_confusion_matrix.png')
|
| 401 |
+
plt.close()
|
| 402 |
+
|
| 403 |
+
def _save_classification_report(self, report, model_name):
|
| 404 |
+
"""Save classification report."""
|
| 405 |
+
with open(f'{self.output_dir}/{model_name}_report.txt', 'w') as f:
|
| 406 |
+
f.write(f"Classification Report - {model_name}\n")
|
| 407 |
+
f.write("=" * 50 + "\n\n")
|
| 408 |
+
|
| 409 |
+
for class_name, metrics in report.items():
|
| 410 |
+
if isinstance(metrics, dict):
|
| 411 |
+
f.write(f"Class: {class_name}\n")
|
| 412 |
+
for metric, value in metrics.items():
|
| 413 |
+
f.write(f" {metric}: {value:.4f}\n")
|
| 414 |
+
f.write("\n")
|
| 415 |
+
```
|
| 416 |
+
|
| 417 |
+
### 6. Model Deployment Step
|
| 418 |
+
Automated model deployment and versioning.
|
| 419 |
+
|
| 420 |
+
```python
|
| 421 |
+
import hashlib
|
| 422 |
+
import json
|
| 423 |
+
from datetime import datetime
|
| 424 |
+
|
| 425 |
+
class ModelDeploymentStep(PipelineStep):
|
| 426 |
+
"""Deploy trained models."""
|
| 427 |
+
|
| 428 |
+
def __init__(self, deployment_config: Dict[str, Any]):
|
| 429 |
+
super().__init__("model_deployment")
|
| 430 |
+
self.config = deployment_config
|
| 431 |
+
|
| 432 |
+
def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 433 |
+
"""Deploy model to production."""
|
| 434 |
+
model = context.get('model')
|
| 435 |
+
if not model:
|
| 436 |
+
raise ValueError("No model found in context")
|
| 437 |
+
|
| 438 |
+
# Generate model version
|
| 439 |
+
model_version = self._generate_version(context)
|
| 440 |
+
|
| 441 |
+
# Save model
|
| 442 |
+
model_path = f"{self.config['model_dir']}/model_{model_version}.joblib"
|
| 443 |
+
joblib.dump(model, model_path)
|
| 444 |
+
|
| 445 |
+
# Save metadata
|
| 446 |
+
metadata = {
|
| 447 |
+
'version': model_version,
|
| 448 |
+
'model_type': context.get('model_type', 'unknown'),
|
| 449 |
+
'test_score': context.get('test_score', 0),
|
| 450 |
+
'best_params': context.get('best_params', {}),
|
| 451 |
+
'timestamp': datetime.now().isoformat(),
|
| 452 |
+
'model_path': model_path
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
metadata_path = f"{self.config['model_dir']}/model_{model_version}_metadata.json"
|
| 456 |
+
with open(metadata_path, 'w') as f:
|
| 457 |
+
json.dump(metadata, f, indent=2)
|
| 458 |
+
|
| 459 |
+
# Update model registry
|
| 460 |
+
self._update_model_registry(metadata)
|
| 461 |
+
|
| 462 |
+
context['deployed_model'] = metadata
|
| 463 |
+
self.logger.info(f"Model deployed with version: {model_version}")
|
| 464 |
+
|
| 465 |
+
return context
|
| 466 |
+
|
| 467 |
+
def _generate_version(self, context: Dict[str, Any]) -> str:
|
| 468 |
+
"""Generate unique model version."""
|
| 469 |
+
data_string = f"{context.get('model_type')}{context.get('test_score')}{datetime.now().timestamp()}"
|
| 470 |
+
return hashlib.md5(data_string.encode()).hexdigest()[:8]
|
| 471 |
+
|
| 472 |
+
def _update_model_registry(self, metadata: Dict[str, Any]):
|
| 473 |
+
"""Update model registry with new deployment."""
|
| 474 |
+
registry_path = f"{self.config['model_dir']}/model_registry.json"
|
| 475 |
+
|
| 476 |
+
# Load existing registry
|
| 477 |
+
try:
|
| 478 |
+
with open(registry_path, 'r') as f:
|
| 479 |
+
registry = json.load(f)
|
| 480 |
+
except FileNotFoundError:
|
| 481 |
+
registry = {'models': [], 'current': None}
|
| 482 |
+
|
| 483 |
+
# Add new model
|
| 484 |
+
registry['models'].append(metadata)
|
| 485 |
+
registry['current'] = metadata['version']
|
| 486 |
+
|
| 487 |
+
# Save updated registry
|
| 488 |
+
with open(registry_path, 'w') as f:
|
| 489 |
+
json.dump(registry, f, indent=2)
|
| 490 |
+
```
|
| 491 |
+
|
| 492 |
+
### 7. Complete Pipeline Example
|
| 493 |
+
Assemble and run the complete pipeline.
|
| 494 |
+
|
| 495 |
+
```python
|
| 496 |
+
def create_ml_pipeline() -> Pipeline:
|
| 497 |
+
"""Create a complete ML pipeline."""
|
| 498 |
+
|
| 499 |
+
# Define configuration
|
| 500 |
+
sources = {
|
| 501 |
+
'training_data': {
|
| 502 |
+
'type': 'csv',
|
| 503 |
+
'path': './data/training_data.csv'
|
| 504 |
+
}
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
preprocessing_config = {
|
| 508 |
+
'drop_missing': True,
|
| 509 |
+
'encode_categorical': True,
|
| 510 |
+
'scale_features': True
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
deployment_config = {
|
| 514 |
+
'model_dir': './models'
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
# Create pipeline steps
|
| 518 |
+
steps = [
|
| 519 |
+
DataIngestionStep(sources),
|
| 520 |
+
DataPreprocessingStep(preprocessing_config),
|
| 521 |
+
DataSplitStep(target_column='target', test_size=0.2, val_size=0.1),
|
| 522 |
+
HyperparameterTuningStep(
|
| 523 |
+
model_type='random_forest',
|
| 524 |
+
param_grid={
|
| 525 |
+
'n_estimators': [50, 100, 200],
|
| 526 |
+
'max_depth': [5, 10, 15],
|
| 527 |
+
'min_samples_split': [2, 5, 10]
|
| 528 |
+
},
|
| 529 |
+
cv=5
|
| 530 |
+
),
|
| 531 |
+
ModelEvaluationStep(save_plots=True, output_dir='./output'),
|
| 532 |
+
ModelDeploymentStep(deployment_config)
|
| 533 |
+
]
|
| 534 |
+
|
| 535 |
+
return Pipeline(name="ml_training_pipeline", steps=steps)
|
| 536 |
+
|
| 537 |
+
# Usage
|
| 538 |
+
if __name__ == "__main__":
|
| 539 |
+
# Setup logging
|
| 540 |
+
logging.basicConfig(
|
| 541 |
+
level=logging.INFO,
|
| 542 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
# Create and run pipeline
|
| 546 |
+
pipeline = create_ml_pipeline()
|
| 547 |
+
result = pipeline.run()
|
| 548 |
+
|
| 549 |
+
print("Pipeline completed successfully!")
|
| 550 |
+
print(f"Test score: {result.get('test_score', 'N/A')}")
|
| 551 |
+
```
|
| 552 |
+
|
| 553 |
+
## Constraints
|
| 554 |
+
- **Data Quality**: Garbage in, garbage out - ensure data quality
|
| 555 |
+
- **Pipeline Complexity**: Balance between automation and flexibility
|
| 556 |
+
- **Resource Management**: Monitor computational resources during training
|
| 557 |
+
- **Version Control**: Track all pipeline steps and configurations
|
| 558 |
+
- **Error Handling**: Implement robust error handling and recovery
|
| 559 |
+
- **Scalability**: Design for data and computational scaling
|
| 560 |
+
|
| 561 |
+
## Expected Output
|
| 562 |
+
Automated, reproducible ML pipelines that handle data ingestion, preprocessing, training, evaluation, and deployment with proper monitoring and version control.
|
TRAE-Skills/ai_engineering/AI_Safety_Ethics.md
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Safety and Ethics
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To implement AI systems that are safe, fair, transparent, and aligned with human values while minimizing potential harms and biases.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying AI systems that affect people's lives
|
| 8 |
+
- When working with sensitive data or protected characteristics
|
| 9 |
+
- When implementing automated decision-making systems
|
| 10 |
+
- When ensuring regulatory compliance (GDPR, AI Act, etc.)
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Bias Detection and Mitigation
|
| 15 |
+
Identify and reduce biases in AI systems.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import pandas as pd
|
| 19 |
+
from sklearn.metrics import confusion_matrix
|
| 20 |
+
from aif360.datasets import BinaryLabelDataset
|
| 21 |
+
from aif360.metrics import BinaryLabelDatasetMetric
|
| 22 |
+
from aif360.algorithms.preprocessing import Reweighing
|
| 23 |
+
|
| 24 |
+
def detect_bias(df, protected_attribute, label, privileged_groups, unprivileged_groups):
|
| 25 |
+
"""Detect bias in dataset."""
|
| 26 |
+
# Convert to AIF360 dataset
|
| 27 |
+
dataset = BinaryLabelDataset(
|
| 28 |
+
df=df,
|
| 29 |
+
label_names=[label],
|
| 30 |
+
protected_attribute_names=[protected_attribute],
|
| 31 |
+
favorable_label=1,
|
| 32 |
+
unfavorable_label=0
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Calculate fairness metrics
|
| 36 |
+
metric = BinaryLabelDatasetMetric(
|
| 37 |
+
dataset,
|
| 38 |
+
unprivileged_groups=unprivileged_groups,
|
| 39 |
+
privileged_groups=privileged_groups
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
return {
|
| 43 |
+
'disparate_impact': metric.disparate_impact(),
|
| 44 |
+
'statistical_parity_difference': metric.statistical_parity_difference()
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
def mitigate_bias(df, protected_attribute, label, privileged_groups, unprivileged_groups):
|
| 48 |
+
"""Apply bias mitigation techniques."""
|
| 49 |
+
# Original dataset
|
| 50 |
+
dataset = BinaryLabelDataset(
|
| 51 |
+
df=df,
|
| 52 |
+
label_names=[label],
|
| 53 |
+
protected_attribute_names=[protected_attribute],
|
| 54 |
+
favorable_label=1,
|
| 55 |
+
unfavorable_label=0
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# Apply reweighing
|
| 59 |
+
reweigher = Reweighing(
|
| 60 |
+
unprivileged_groups=unprivileged_groups,
|
| 61 |
+
privileged_groups=privileged_groups
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
dataset_transformed = reweigher.fit_transform(dataset)
|
| 65 |
+
|
| 66 |
+
return dataset_transformed.convert_to_dataframe()[0]
|
| 67 |
+
|
| 68 |
+
# Example usage
|
| 69 |
+
# df = pd.read_csv('loan_applications.csv')
|
| 70 |
+
# protected_attribute = 'gender'
|
| 71 |
+
# label = 'loan_approved'
|
| 72 |
+
#
|
| 73 |
+
# bias_metrics = detect_bias(df, protected_attribute, label,
|
| 74 |
+
# privileged_groups=[{'gender': 1}],
|
| 75 |
+
# unprivileged_groups=[{'gender': 0}])
|
| 76 |
+
#
|
| 77 |
+
# print(f"Disparate Impact: {bias_metrics['disparate_impact']}")
|
| 78 |
+
#
|
| 79 |
+
# # Mitigate bias
|
| 80 |
+
# fair_df = mitigate_bias(df, protected_attribute, label,
|
| 81 |
+
# privileged_groups=[{'gender': 1}],
|
| 82 |
+
# unprivileged_groups=[{'gender': 0}])
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### 2. Content Moderation and Safety
|
| 86 |
+
Implement safety filters for AI content.
|
| 87 |
+
|
| 88 |
+
```python
|
| 89 |
+
import openai
|
| 90 |
+
|
| 91 |
+
class SafeContentGenerator:
|
| 92 |
+
def __init__(self):
|
| 93 |
+
self.client = openai.OpenAI()
|
| 94 |
+
self.forbidden_categories = [
|
| 95 |
+
"hate speech",
|
| 96 |
+
"violence",
|
| 97 |
+
"self-harm",
|
| 98 |
+
"sexual content",
|
| 99 |
+
"illegal activities"
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
def check_safety(self, content):
|
| 103 |
+
"""Check if content is safe."""
|
| 104 |
+
moderation_response = self.client.moderations.create(input=content)
|
| 105 |
+
result = moderation_response.results[0]
|
| 106 |
+
|
| 107 |
+
return {
|
| 108 |
+
'flagged': result.flagged,
|
| 109 |
+
'categories': result.categories,
|
| 110 |
+
'category_scores': result.category_scores
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
def generate_safe_content(self, prompt, max_retries=3):
|
| 114 |
+
"""Generate content with safety checks."""
|
| 115 |
+
for attempt in range(max_retries):
|
| 116 |
+
response = self.client.chat.completions.create(
|
| 117 |
+
model="gpt-4",
|
| 118 |
+
messages=[{"role": "user", "content": prompt}],
|
| 119 |
+
temperature=0.7
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
content = response.choices[0].message.content
|
| 123 |
+
safety_check = self.check_safety(content)
|
| 124 |
+
|
| 125 |
+
if not safety_check['flagged']:
|
| 126 |
+
return content
|
| 127 |
+
|
| 128 |
+
print(f"Attempt {attempt + 1}: Content flagged as unsafe")
|
| 129 |
+
prompt = f"Generate content that is completely safe and appropriate: {prompt}"
|
| 130 |
+
|
| 131 |
+
raise Exception("Failed to generate safe content after multiple attempts")
|
| 132 |
+
|
| 133 |
+
# Usage
|
| 134 |
+
generator = SafeContentGenerator()
|
| 135 |
+
safe_content = generator.generate_safe_content("Write a story about teamwork")
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### 3. Transparency and Explainability
|
| 139 |
+
Implement explainability for AI decisions.
|
| 140 |
+
|
| 141 |
+
```python
|
| 142 |
+
import shap
|
| 143 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 144 |
+
|
| 145 |
+
class ExplainableAI:
|
| 146 |
+
def __init__(self, model, feature_names):
|
| 147 |
+
self.model = model
|
| 148 |
+
self.feature_names = feature_names
|
| 149 |
+
self.explainer = None
|
| 150 |
+
|
| 151 |
+
def fit_explainer(self, X_train):
|
| 152 |
+
"""Fit SHAP explainer."""
|
| 153 |
+
self.explainer = shap.Explainer(self.model, X_train)
|
| 154 |
+
|
| 155 |
+
def explain_prediction(self, instance):
|
| 156 |
+
"""Explain individual prediction."""
|
| 157 |
+
shap_values = self.explainer(instance)
|
| 158 |
+
|
| 159 |
+
# Get feature importance
|
| 160 |
+
feature_importance = list(zip(
|
| 161 |
+
self.feature_names,
|
| 162 |
+
shap_values.values[0]
|
| 163 |
+
))
|
| 164 |
+
|
| 165 |
+
# Sort by absolute importance
|
| 166 |
+
feature_importance.sort(key=lambda x: abs(x[1]), reverse=True)
|
| 167 |
+
|
| 168 |
+
return feature_importance[:10] # Top 10 features
|
| 169 |
+
|
| 170 |
+
def generate_explanation_text(self, instance, prediction, feature_importance):
|
| 171 |
+
"""Generate human-readable explanation."""
|
| 172 |
+
explanation = f"Prediction: {prediction}\n\n"
|
| 173 |
+
explanation += "Key factors influencing this decision:\n"
|
| 174 |
+
|
| 175 |
+
for feature, importance in feature_importance:
|
| 176 |
+
direction = "increases" if importance > 0 else "decreases"
|
| 177 |
+
explanation += f"- {feature}: {direction} likelihood (impact: {abs(importance):.3f})\n"
|
| 178 |
+
|
| 179 |
+
return explanation
|
| 180 |
+
|
| 181 |
+
# Example usage
|
| 182 |
+
# model = RandomForestClassifier()
|
| 183 |
+
# model.fit(X_train, y_train)
|
| 184 |
+
#
|
| 185 |
+
# explainable_ai = ExplainableAI(model, feature_names)
|
| 186 |
+
# explainable_ai.fit_explainer(X_train)
|
| 187 |
+
#
|
| 188 |
+
# instance = X_test[0]
|
| 189 |
+
# prediction = model.predict([instance])[0]
|
| 190 |
+
# feature_importance = explainable_ai.explain_prediction(instance)
|
| 191 |
+
#
|
| 192 |
+
# explanation = explainable_ai.generate_explanation_text(
|
| 193 |
+
# instance, prediction, feature_importance
|
| 194 |
+
# )
|
| 195 |
+
# print(explanation)
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
### 4. Privacy-Preserving AI
|
| 199 |
+
Implement privacy protection in AI systems.
|
| 200 |
+
|
| 201 |
+
```python
|
| 202 |
+
import numpy as np
|
| 203 |
+
from sklearn.preprocessing import StandardScaler
|
| 204 |
+
|
| 205 |
+
class PrivacyPreservingML:
|
| 206 |
+
def __init__(self, epsilon=1.0):
|
| 207 |
+
self.epsilon = epsilon
|
| 208 |
+
self.scaler = StandardScaler()
|
| 209 |
+
|
| 210 |
+
def add_laplace_noise(self, data, sensitivity):
|
| 211 |
+
"""Add Laplace noise for differential privacy."""
|
| 212 |
+
scale = sensitivity / self.epsilon
|
| 213 |
+
noise = np.random.laplace(0, scale, data.shape)
|
| 214 |
+
return data + noise
|
| 215 |
+
|
| 216 |
+
def private_aggregation(self, data):
|
| 217 |
+
"""Perform differentially private aggregation."""
|
| 218 |
+
sensitivity = 1.0 # Depends on the query
|
| 219 |
+
noisy_mean = self.add_laplace_noise(data, sensitivity)
|
| 220 |
+
return np.mean(noisy_mean)
|
| 221 |
+
|
| 222 |
+
def anonymize_data(self, df, sensitive_columns):
|
| 223 |
+
"""Anonymize sensitive data."""
|
| 224 |
+
df_anon = df.copy()
|
| 225 |
+
|
| 226 |
+
for column in sensitive_columns:
|
| 227 |
+
# Generalize or remove sensitive information
|
| 228 |
+
if df[column].dtype == 'object':
|
| 229 |
+
# Categorical: use frequency encoding
|
| 230 |
+
freq = df[column].value_counts(normalize=True)
|
| 231 |
+
df_anon[column] = df[column].map(freq)
|
| 232 |
+
else:
|
| 233 |
+
# Numerical: add noise
|
| 234 |
+
df_anon[column] = self.add_laplace_noise(
|
| 235 |
+
df[column].values,
|
| 236 |
+
sensitivity=df[column].max()
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
return df_anon
|
| 240 |
+
|
| 241 |
+
def federated_learning_step(self, local_models, global_model):
|
| 242 |
+
"""Simulate federated learning update."""
|
| 243 |
+
# Aggregate local model updates
|
| 244 |
+
averaged_weights = []
|
| 245 |
+
|
| 246 |
+
for weights_list in zip(*[model.get_weights() for model in local_models]):
|
| 247 |
+
averaged_weights.append(np.mean(weights_list, axis=0))
|
| 248 |
+
|
| 249 |
+
# Update global model
|
| 250 |
+
global_model.set_weights(averaged_weights)
|
| 251 |
+
return global_model
|
| 252 |
+
|
| 253 |
+
# Usage
|
| 254 |
+
# privacy_ml = PrivacyPreservingML(epsilon=0.5)
|
| 255 |
+
#
|
| 256 |
+
# # Anonymize data
|
| 257 |
+
# sensitive_data = df[['age', 'income', 'zip_code']]
|
| 258 |
+
# df_anon = privacy_ml.anonymize_data(df, ['age', 'income'])
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
### 5. Ethical Review Framework
|
| 262 |
+
Implement ethical review for AI systems.
|
| 263 |
+
|
| 264 |
+
```python
|
| 265 |
+
class EthicalReviewer:
|
| 266 |
+
def __init__(self):
|
| 267 |
+
self.checklist = {
|
| 268 |
+
'fairness': [
|
| 269 |
+
'Have we tested for bias across demographic groups?',
|
| 270 |
+
'Are the training datasets representative?',
|
| 271 |
+
'Have we implemented fairness metrics?'
|
| 272 |
+
],
|
| 273 |
+
'transparency': [
|
| 274 |
+
'Can we explain individual decisions?',
|
| 275 |
+
'Are users informed about AI involvement?',
|
| 276 |
+
'Is the system\'s limitations documented?'
|
| 277 |
+
],
|
| 278 |
+
'accountability': [
|
| 279 |
+
'Is there a human in the loop?',
|
| 280 |
+
'Can decisions be appealed?',
|
| 281 |
+
'Are logs maintained for audit?'
|
| 282 |
+
],
|
| 283 |
+
'safety': [
|
| 284 |
+
'Have we implemented safety guards?',
|
| 285 |
+
'Is there content moderation?',
|
| 286 |
+
'Are there fail-safe mechanisms?'
|
| 287 |
+
],
|
| 288 |
+
'privacy': [
|
| 289 |
+
'Is user data protected?',
|
| 290 |
+
'Have we obtained proper consent?',
|
| 291 |
+
'Is data minimization applied?'
|
| 292 |
+
]
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
def review_system(self, responses):
|
| 296 |
+
"""Review AI system against ethical checklist."""
|
| 297 |
+
results = {}
|
| 298 |
+
|
| 299 |
+
for category, questions in self.checklist.items():
|
| 300 |
+
category_score = 0
|
| 301 |
+
category_responses = []
|
| 302 |
+
|
| 303 |
+
for i, question in enumerate(questions):
|
| 304 |
+
response = responses.get(f"{category}_{i}", "not_answered")
|
| 305 |
+
|
| 306 |
+
if response.lower() in ['yes', 'implemented', 'yes_implemented']:
|
| 307 |
+
category_score += 1
|
| 308 |
+
status = "✓"
|
| 309 |
+
elif response.lower() == 'partially':
|
| 310 |
+
category_score += 0.5
|
| 311 |
+
status = "~"
|
| 312 |
+
else:
|
| 313 |
+
status = "✗"
|
| 314 |
+
|
| 315 |
+
category_responses.append({
|
| 316 |
+
'question': question,
|
| 317 |
+
'response': response,
|
| 318 |
+
'status': status
|
| 319 |
+
})
|
| 320 |
+
|
| 321 |
+
results[category] = {
|
| 322 |
+
'score': category_score / len(questions),
|
| 323 |
+
'responses': category_responses
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
return results
|
| 327 |
+
|
| 328 |
+
def generate_report(self, review_results):
|
| 329 |
+
"""Generate ethical review report."""
|
| 330 |
+
report = "AI System Ethical Review Report\n"
|
| 331 |
+
report += "=" * 50 + "\n\n"
|
| 332 |
+
|
| 333 |
+
for category, data in review_results.items():
|
| 334 |
+
score_percent = data['score'] * 100
|
| 335 |
+
report += f"{category.upper()}: {score_percent:.0f}%\n"
|
| 336 |
+
|
| 337 |
+
for response in data['responses']:
|
| 338 |
+
report += f" {response['status']} {response['question']}\n"
|
| 339 |
+
report += f" Response: {response['response']}\n"
|
| 340 |
+
|
| 341 |
+
report += "\n"
|
| 342 |
+
|
| 343 |
+
overall_score = np.mean([data['score'] for data in review_results.values()])
|
| 344 |
+
report += f"\nOVERALL ETHICAL SCORE: {overall_score * 100:.0f}%\n"
|
| 345 |
+
|
| 346 |
+
if overall_score >= 0.8:
|
| 347 |
+
report += "Status: PASS - System meets ethical standards\n"
|
| 348 |
+
elif overall_score >= 0.6:
|
| 349 |
+
report += "Status: CONDITIONAL - Address identified concerns\n"
|
| 350 |
+
else:
|
| 351 |
+
report += "Status: FAIL - Significant ethical concerns\n"
|
| 352 |
+
|
| 353 |
+
return report
|
| 354 |
+
|
| 355 |
+
# Usage
|
| 356 |
+
# reviewer = EthicalReviewer()
|
| 357 |
+
#
|
| 358 |
+
# responses = {
|
| 359 |
+
# 'fairness_0': 'yes',
|
| 360 |
+
# 'fairness_1': 'partially',
|
| 361 |
+
# 'transparency_0': 'yes',
|
| 362 |
+
# # ... more responses
|
| 363 |
+
# }
|
| 364 |
+
#
|
| 365 |
+
# results = reviewer.review_system(responses)
|
| 366 |
+
# report = reviewer.generate_report(results)
|
| 367 |
+
# print(report)
|
| 368 |
+
```
|
| 369 |
+
|
| 370 |
+
## Constraints
|
| 371 |
+
- **Legal Compliance**: Ensure compliance with relevant laws and regulations
|
| 372 |
+
- **Context Dependency**: Ethical considerations vary by application and culture
|
| 373 |
+
- **Trade-offs**: Balance between competing ethical principles may be necessary
|
| 374 |
+
- **Continuous Monitoring**: Ethical behavior requires ongoing monitoring and updates
|
| 375 |
+
- **Stakeholder Involvement**: Include diverse stakeholders in ethical assessments
|
| 376 |
+
- **Transparency Limits**: Some models are inherently difficult to explain
|
| 377 |
+
|
| 378 |
+
## Expected Output
|
| 379 |
+
AI systems that are safe, fair, transparent, and aligned with ethical principles, with proper documentation and monitoring for responsible deployment.
|
TRAE-Skills/ai_engineering/AI_Testing_Evaluation.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: AI Testing and Evaluation
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To systematically evaluate AI model performance, reliability, and safety using comprehensive testing methodologies and metrics.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When validating AI models before deployment
|
| 8 |
+
- When comparing different models or approaches
|
| 9 |
+
- When monitoring model performance in production
|
| 10 |
+
- When ensuring AI system reliability and safety
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Model Performance Metrics
|
| 15 |
+
Calculate comprehensive performance metrics.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import numpy as np
|
| 19 |
+
from sklearn.metrics import (
|
| 20 |
+
accuracy_score, precision_score, recall_score, f1_score,
|
| 21 |
+
confusion_matrix, roc_auc_score, classification_report
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
class ModelEvaluator:
|
| 25 |
+
def __init__(self):
|
| 26 |
+
self.metrics = {}
|
| 27 |
+
|
| 28 |
+
def evaluate_classification(self, y_true, y_pred, y_prob=None):
|
| 29 |
+
"""Evaluate classification model performance."""
|
| 30 |
+
self.metrics['accuracy'] = accuracy_score(y_true, y_pred)
|
| 31 |
+
self.metrics['precision'] = precision_score(y_true, y_pred, average='weighted')
|
| 32 |
+
self.metrics['recall'] = recall_score(y_true, y_pred, average='weighted')
|
| 33 |
+
self.metrics['f1_score'] = f1_score(y_true, y_pred, average='weighted')
|
| 34 |
+
|
| 35 |
+
if y_prob is not None:
|
| 36 |
+
self.metrics['roc_auc'] = roc_auc_score(y_true, y_prob, multi_class='ovr')
|
| 37 |
+
|
| 38 |
+
# Confusion matrix
|
| 39 |
+
self.metrics['confusion_matrix'] = confusion_matrix(y_true, y_pred)
|
| 40 |
+
|
| 41 |
+
return self.metrics
|
| 42 |
+
|
| 43 |
+
def evaluate_regression(self, y_true, y_pred):
|
| 44 |
+
"""Evaluate regression model performance."""
|
| 45 |
+
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
|
| 46 |
+
|
| 47 |
+
self.metrics['mse'] = mean_squared_error(y_true, y_pred)
|
| 48 |
+
self.metrics['rmse'] = np.sqrt(self.metrics['mse'])
|
| 49 |
+
self.metrics['mae'] = mean_absolute_error(y_true, y_pred)
|
| 50 |
+
self.metrics['r2_score'] = r2_score(y_true, y_pred)
|
| 51 |
+
|
| 52 |
+
return self.metrics
|
| 53 |
+
|
| 54 |
+
def generate_report(self):
|
| 55 |
+
"""Generate evaluation report."""
|
| 56 |
+
report = "Model Evaluation Report\n"
|
| 57 |
+
report += "=" * 40 + "\n"
|
| 58 |
+
|
| 59 |
+
for metric, value in self.metrics.items():
|
| 60 |
+
if metric != 'confusion_matrix':
|
| 61 |
+
if isinstance(value, float):
|
| 62 |
+
report += f"{metric}: {value:.4f}\n"
|
| 63 |
+
else:
|
| 64 |
+
report += f"{metric}: {value}\n"
|
| 65 |
+
|
| 66 |
+
return report
|
| 67 |
+
|
| 68 |
+
# Usage
|
| 69 |
+
# evaluator = ModelEvaluator()
|
| 70 |
+
#
|
| 71 |
+
# y_true = [0, 1, 2, 1, 0]
|
| 72 |
+
# y_pred = [0, 2, 2, 1, 0]
|
| 73 |
+
#
|
| 74 |
+
# metrics = evaluator.evaluate_classification(y_true, y_pred)
|
| 75 |
+
# print(evaluator.generate_report())
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### 2. LLM Quality Evaluation
|
| 79 |
+
Evaluate language model outputs.
|
| 80 |
+
|
| 81 |
+
```python
|
| 82 |
+
from openai import OpenAI
|
| 83 |
+
import json
|
| 84 |
+
|
| 85 |
+
class LLMEvaluator:
|
| 86 |
+
def __init__(self, model="gpt-4"):
|
| 87 |
+
self.client = OpenAI()
|
| 88 |
+
self.model = model
|
| 89 |
+
|
| 90 |
+
def evaluate_relevance(self, question, answer, reference_answer=None):
|
| 91 |
+
"""Evaluate answer relevance."""
|
| 92 |
+
prompt = f"""
|
| 93 |
+
Question: {question}
|
| 94 |
+
Answer: {answer}
|
| 95 |
+
|
| 96 |
+
Rate the relevance of this answer to the question on a scale of 1-10.
|
| 97 |
+
Consider: Does it directly address the question? Is it comprehensive?
|
| 98 |
+
|
| 99 |
+
Provide rating as JSON: {{"relevance_score": <number>, "reasoning": "<explanation>"}}
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
response = self.client.chat.completions.create(
|
| 103 |
+
model=self.model,
|
| 104 |
+
messages=[{"role": "user", "content": prompt}],
|
| 105 |
+
temperature=0
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
result = json.loads(response.choices[0].message.content)
|
| 110 |
+
return result
|
| 111 |
+
except:
|
| 112 |
+
return {"relevance_score": 0, "reasoning": "Failed to parse evaluation"}
|
| 113 |
+
|
| 114 |
+
def evaluate_accuracy(self, question, answer, ground_truth):
|
| 115 |
+
"""Evaluate factual accuracy."""
|
| 116 |
+
prompt = f"""
|
| 117 |
+
Question: {question}
|
| 118 |
+
Generated Answer: {answer}
|
| 119 |
+
Ground Truth: {ground_truth}
|
| 120 |
+
|
| 121 |
+
Compare the generated answer with the ground truth.
|
| 122 |
+
Rate factual accuracy on a scale of 1-10.
|
| 123 |
+
|
| 124 |
+
Provide rating as JSON: {{"accuracy_score": <number>, "errors": [<list of factual errors>]}}
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
response = self.client.chat.completions.create(
|
| 128 |
+
model=self.model,
|
| 129 |
+
messages=[{"role": "user", "content": prompt}],
|
| 130 |
+
temperature=0
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
result = json.loads(response.choices[0].message.content)
|
| 135 |
+
return result
|
| 136 |
+
except:
|
| 137 |
+
return {"accuracy_score": 0, "errors": ["Failed to parse evaluation"]}
|
| 138 |
+
|
| 139 |
+
def evaluate_toxicity(self, text):
|
| 140 |
+
"""Check for toxic content."""
|
| 141 |
+
moderation_response = self.client.moderations.create(input=text)
|
| 142 |
+
result = moderation_response.results[0]
|
| 143 |
+
|
| 144 |
+
return {
|
| 145 |
+
'flagged': result.flagged,
|
| 146 |
+
'categories': result.categories,
|
| 147 |
+
'category_scores': result.category_scores
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
def batch_evaluate(self, test_cases):
|
| 151 |
+
"""Evaluate multiple test cases."""
|
| 152 |
+
results = []
|
| 153 |
+
|
| 154 |
+
for case in test_cases:
|
| 155 |
+
result = {
|
| 156 |
+
'question': case['question'],
|
| 157 |
+
'answer': case['answer']
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
# Run evaluations
|
| 161 |
+
result['relevance'] = self.evaluate_relevance(
|
| 162 |
+
case['question'],
|
| 163 |
+
case['answer']
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
if 'ground_truth' in case:
|
| 167 |
+
result['accuracy'] = self.evaluate_accuracy(
|
| 168 |
+
case['question'],
|
| 169 |
+
case['answer'],
|
| 170 |
+
case['ground_truth']
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
result['toxicity'] = self.evaluate_toxicity(case['answer'])
|
| 174 |
+
|
| 175 |
+
results.append(result)
|
| 176 |
+
|
| 177 |
+
return results
|
| 178 |
+
|
| 179 |
+
def calculate_aggregate_metrics(self, evaluation_results):
|
| 180 |
+
"""Calculate aggregate metrics across all test cases."""
|
| 181 |
+
metrics = {
|
| 182 |
+
'avg_relevance': np.mean([r['relevance']['relevance_score'] for r in evaluation_results]),
|
| 183 |
+
'flagged_count': sum([1 for r in evaluation_results if r['toxicity']['flagged']]),
|
| 184 |
+
'total_cases': len(evaluation_results)
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
if 'accuracy' in evaluation_results[0]:
|
| 188 |
+
metrics['avg_accuracy'] = np.mean([r['accuracy']['accuracy_score'] for r in evaluation_results])
|
| 189 |
+
|
| 190 |
+
return metrics
|
| 191 |
+
|
| 192 |
+
# Usage
|
| 193 |
+
# evaluator = LLMEvaluator()
|
| 194 |
+
#
|
| 195 |
+
# test_cases = [
|
| 196 |
+
# {
|
| 197 |
+
# 'question': 'What is machine learning?',
|
| 198 |
+
# 'answer': 'Machine learning is a subset of AI that enables systems to learn from data.',
|
| 199 |
+
# 'ground_truth': 'Machine learning involves training algorithms to make predictions or decisions based on data.'
|
| 200 |
+
# }
|
| 201 |
+
# ]
|
| 202 |
+
#
|
| 203 |
+
# results = evaluator.batch_evaluate(test_cases)
|
| 204 |
+
# aggregate = evaluator.calculate_aggregate_metrics(results)
|
| 205 |
+
# print(aggregate)
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
### 3. Robustness Testing
|
| 209 |
+
Test model robustness against adversarial inputs.
|
| 210 |
+
|
| 211 |
+
```python
|
| 212 |
+
class RobustnessTester:
|
| 213 |
+
def __init__(self, model):
|
| 214 |
+
self.model = model
|
| 215 |
+
|
| 216 |
+
def test_typos(self, text, num_variations=5):
|
| 217 |
+
"""Test model with typographical variations."""
|
| 218 |
+
variations = []
|
| 219 |
+
|
| 220 |
+
# Common typos
|
| 221 |
+
common_typos = {
|
| 222 |
+
'the': 'teh',
|
| 223 |
+
'and': 'adn',
|
| 224 |
+
'is': 'si',
|
| 225 |
+
'to': 'ot',
|
| 226 |
+
'of': 'fo'
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
for _ in range(num_variations):
|
| 230 |
+
variation = text
|
| 231 |
+
for correct, typo in common_typos.items():
|
| 232 |
+
variation = variation.replace(correct, typo)
|
| 233 |
+
variations.append(variation)
|
| 234 |
+
|
| 235 |
+
return variations
|
| 236 |
+
|
| 237 |
+
def test_adversarial_examples(self, texts, labels, attack_method='textbugger'):
|
| 238 |
+
"""Test against adversarial attacks."""
|
| 239 |
+
# Simplified adversarial example generation
|
| 240 |
+
adversarial_examples = []
|
| 241 |
+
|
| 242 |
+
for text, label in zip(texts, labels):
|
| 243 |
+
# Add slight perturbations
|
| 244 |
+
words = text.split()
|
| 245 |
+
if len(words) > 0:
|
| 246 |
+
# Duplicate a word
|
| 247 |
+
words.append(words[0])
|
| 248 |
+
adversarial = ' '.join(words)
|
| 249 |
+
adversarial_examples.append((adversarial, label))
|
| 250 |
+
|
| 251 |
+
return adversarial_examples
|
| 252 |
+
|
| 253 |
+
def test_out_of_distribution(self, texts):
|
| 254 |
+
"""Test with out-of-distribution inputs."""
|
| 255 |
+
ood_cases = [
|
| 256 |
+
# Very short inputs
|
| 257 |
+
"Hi",
|
| 258 |
+
"A",
|
| 259 |
+
"",
|
| 260 |
+
|
| 261 |
+
# Very long inputs
|
| 262 |
+
"word " * 1000,
|
| 263 |
+
|
| 264 |
+
# Special characters
|
| 265 |
+
"!@#$%^&*()",
|
| 266 |
+
|
| 267 |
+
# Mixed languages
|
| 268 |
+
"Hello 你好 Bonjour",
|
| 269 |
+
|
| 270 |
+
# Malformed inputs
|
| 271 |
+
"...test...",
|
| 272 |
+
"123456789"
|
| 273 |
+
]
|
| 274 |
+
|
| 275 |
+
return ood_cases
|
| 276 |
+
|
| 277 |
+
def evaluate_robustness(self, test_function):
|
| 278 |
+
"""Evaluate model robustness."""
|
| 279 |
+
test_cases = {
|
| 280 |
+
'typos': self.test_typos("What is the meaning of life?"),
|
| 281 |
+
'ood': self.test_out_of_distribution([]),
|
| 282 |
+
'adversarial': self.test_adversarial_examples(["Hello world"], [1])
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
results = {}
|
| 286 |
+
|
| 287 |
+
for category, cases in test_cases.items():
|
| 288 |
+
results[category] = []
|
| 289 |
+
|
| 290 |
+
for case in cases:
|
| 291 |
+
try:
|
| 292 |
+
response = test_function(case)
|
| 293 |
+
results[category].append({
|
| 294 |
+
'input': case,
|
| 295 |
+
'status': 'success',
|
| 296 |
+
'response': response
|
| 297 |
+
})
|
| 298 |
+
except Exception as e:
|
| 299 |
+
results[category].append({
|
| 300 |
+
'input': case,
|
| 301 |
+
'status': 'failed',
|
| 302 |
+
'error': str(e)
|
| 303 |
+
})
|
| 304 |
+
|
| 305 |
+
return results
|
| 306 |
+
|
| 307 |
+
# Usage
|
| 308 |
+
# def mock_llm(text):
|
| 309 |
+
# return f"Response to: {text[:50]}"
|
| 310 |
+
#
|
| 311 |
+
# tester = RobustnessTester(model=None)
|
| 312 |
+
# robustness_results = tester.evaluate_robustness(mock_llm)
|
| 313 |
+
```
|
| 314 |
+
|
| 315 |
+
### 4. A/B Testing Framework
|
| 316 |
+
Implement A/B testing for model comparison.
|
| 317 |
+
|
| 318 |
+
```python
|
| 319 |
+
from scipy import stats
|
| 320 |
+
|
| 321 |
+
class ABTestFramework:
|
| 322 |
+
def __init__(self):
|
| 323 |
+
self.results = {'A': [], 'B': []}
|
| 324 |
+
|
| 325 |
+
def add_result(self, group, metric_value):
|
| 326 |
+
"""Add a result for a group."""
|
| 327 |
+
if group in ['A', 'B']:
|
| 328 |
+
self.results[group].append(metric_value)
|
| 329 |
+
|
| 330 |
+
def calculate_significance(self, metric='accuracy'):
|
| 331 |
+
"""Calculate statistical significance."""
|
| 332 |
+
group_a = self.results['A']
|
| 333 |
+
group_b = self.results['B']
|
| 334 |
+
|
| 335 |
+
# T-test
|
| 336 |
+
t_statistic, p_value = stats.ttest_ind(group_a, group_b)
|
| 337 |
+
|
| 338 |
+
# Effect size (Cohen's d)
|
| 339 |
+
pooled_std = np.sqrt((np.std(group_a)**2 + np.std(group_b)**2) / 2)
|
| 340 |
+
cohens_d = (np.mean(group_a) - np.mean(group_b)) / pooled_std
|
| 341 |
+
|
| 342 |
+
return {
|
| 343 |
+
'group_a_mean': np.mean(group_a),
|
| 344 |
+
'group_b_mean': np.mean(group_b),
|
| 345 |
+
't_statistic': t_statistic,
|
| 346 |
+
'p_value': p_value,
|
| 347 |
+
'significant': p_value < 0.05,
|
| 348 |
+
'cohens_d': cohens_d
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
def generate_report(self):
|
| 352 |
+
"""Generate A/B test report."""
|
| 353 |
+
stats = self.calculate_significance()
|
| 354 |
+
|
| 355 |
+
report = f"""
|
| 356 |
+
A/B Test Results Report
|
| 357 |
+
{'=' * 50}
|
| 358 |
+
|
| 359 |
+
Group A: n={len(self.results['A'])}, mean={stats['group_a_mean']:.4f}
|
| 360 |
+
Group B: n={len(self.results['B'])}, mean={stats['group_b_mean']:.4f}
|
| 361 |
+
|
| 362 |
+
Statistical Analysis:
|
| 363 |
+
- t-statistic: {stats['t_statistic']:.4f}
|
| 364 |
+
- p-value: {stats['p_value']:.4f}
|
| 365 |
+
- Significant: {'Yes' if stats['significant'] else 'No'}
|
| 366 |
+
- Effect size (Cohen's d): {stats['cohens_d']:.4f}
|
| 367 |
+
|
| 368 |
+
Conclusion:
|
| 369 |
+
"""
|
| 370 |
+
if stats['significant']:
|
| 371 |
+
if stats['group_a_mean'] > stats['group_b_mean']:
|
| 372 |
+
report += "Group A performs significantly better than Group B."
|
| 373 |
+
else:
|
| 374 |
+
report += "Group B performs significantly better than Group A."
|
| 375 |
+
else:
|
| 376 |
+
report += "No significant difference found between groups."
|
| 377 |
+
|
| 378 |
+
return report
|
| 379 |
+
|
| 380 |
+
# Usage
|
| 381 |
+
# ab_test = ABTestFramework()
|
| 382 |
+
#
|
| 383 |
+
# # Add results (in practice, these come from user interactions)
|
| 384 |
+
# for _ in range(100):
|
| 385 |
+
# ab_test.add_result('A', np.random.normal(0.7, 0.1))
|
| 386 |
+
# ab_test.add_result('B', np.random.normal(0.75, 0.1))
|
| 387 |
+
#
|
| 388 |
+
# print(ab_test.generate_report())
|
| 389 |
+
```
|
| 390 |
+
|
| 391 |
+
## Constraints
|
| 392 |
+
- **Ground Truth**: High-quality ground truth data is essential for accurate evaluation
|
| 393 |
+
- **Bias in Evaluation**: Evaluation metrics themselves may contain biases
|
| 394 |
+
- **Context Dependency**: Performance may vary significantly across different contexts
|
| 395 |
+
- **Cost**: Comprehensive evaluation can be expensive, especially for LLMs
|
| 396 |
+
- **Subjectivity**: Some metrics (like quality) are inherently subjective
|
| 397 |
+
- **Dynamic Performance**: Model performance may degrade over time
|
| 398 |
+
|
| 399 |
+
## Expected Output
|
| 400 |
+
Comprehensive evaluation of AI systems with detailed metrics, robustness testing results, and statistical analysis to ensure reliable and safe deployment.
|
TRAE-Skills/ai_engineering/Chain_of_Thought_Prompting.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Chain of Thought Prompting
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To improve LLM reasoning performance by encouraging models to show their work and think through problems step-by-step before arriving at an answer.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When solving complex math or logic problems
|
| 8 |
+
- When reasoning through multi-step questions
|
| 9 |
+
- When you need to verify the model's thinking process
|
| 10 |
+
- When working with problems requiring inference
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Basic Chain of Thought
|
| 15 |
+
Structure your prompt to encourage step-by-step reasoning.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
|
| 20 |
+
client = OpenAI()
|
| 21 |
+
|
| 22 |
+
prompt = """
|
| 23 |
+
Question: If a store sells apples for $2 each and oranges for $3 each,
|
| 24 |
+
and you buy 5 apples and 3 oranges, how much do you spend?
|
| 25 |
+
|
| 26 |
+
Let's think step by step:
|
| 27 |
+
1. Calculate the cost of apples: 5 apples × $2 = $10
|
| 28 |
+
2. Calculate the cost of oranges: 3 oranges × $3 = $9
|
| 29 |
+
3. Add both amounts: $10 + $9 = $19
|
| 30 |
+
|
| 31 |
+
Answer: $19
|
| 32 |
+
|
| 33 |
+
Question: A train travels 120 miles in 2 hours. If it maintains the same speed,
|
| 34 |
+
how far will it travel in 5 hours?
|
| 35 |
+
|
| 36 |
+
Let's think step by step:
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
response = client.chat.completions.create(
|
| 40 |
+
model="gpt-4",
|
| 41 |
+
messages=[{"role": "user", "content": prompt}]
|
| 42 |
+
)
|
| 43 |
+
print(response.choices[0].message.content)
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 2. Zero-Shot Chain of Thought
|
| 47 |
+
Simply add "Let's think step by step" to your prompt.
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
def zero_shot_cot(question):
|
| 51 |
+
prompt = f"""Question: {question}
|
| 52 |
+
|
| 53 |
+
Let's think step by step:"""
|
| 54 |
+
|
| 55 |
+
response = client.chat.completions.create(
|
| 56 |
+
model="gpt-4",
|
| 57 |
+
messages=[{"role": "user", "content": prompt}]
|
| 58 |
+
)
|
| 59 |
+
return response.choices[0].message.content
|
| 60 |
+
|
| 61 |
+
# Example
|
| 62 |
+
question = "If 3 workers can build a wall in 4 days, how many days will 6 workers need?"
|
| 63 |
+
print(zero_shot_cot(question))
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 3. Few-Shot Chain of Thought
|
| 67 |
+
Provide examples of the reasoning process.
|
| 68 |
+
|
| 69 |
+
```python
|
| 70 |
+
few_shot_prompt = """
|
| 71 |
+
Q: Roger has 5 tennis balls. He buys 2 more cans of 3 tennis balls each.
|
| 72 |
+
How many tennis balls does he have now?
|
| 73 |
+
A: Roger started with 5 balls. 2 cans of 3 tennis balls each is 6 tennis balls.
|
| 74 |
+
5 + 6 = 11. The answer is 11.
|
| 75 |
+
|
| 76 |
+
Q: A restaurant had 23 apples. If they used 20 to make lunch and bought 6 more,
|
| 77 |
+
how many apples do they have?
|
| 78 |
+
A: They had 23 apples and used 20, so 23 - 20 = 3. Then they bought 6 more,
|
| 79 |
+
so 3 + 6 = 9. The answer is 9.
|
| 80 |
+
|
| 81 |
+
Q: If a car travels 60 miles per hour for 3 hours, how far does it travel?
|
| 82 |
+
A:"""
|
| 83 |
+
|
| 84 |
+
response = client.chat.completions.create(
|
| 85 |
+
model="gpt-4",
|
| 86 |
+
messages=[{"role": "user", "content": few_shot_prompt}]
|
| 87 |
+
)
|
| 88 |
+
print(response.choices[0].message.content)
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 4. Self-Consistency with Chain of Thought
|
| 92 |
+
Generate multiple reasoning paths and take the majority answer.
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
import numpy as np
|
| 96 |
+
|
| 97 |
+
def self_consistent_cot(question, num_samples=5):
|
| 98 |
+
responses = []
|
| 99 |
+
for _ in range(num_samples):
|
| 100 |
+
response = client.chat.completions.create(
|
| 101 |
+
model="gpt-4",
|
| 102 |
+
messages=[{"role": "user", "content": f"Question: {question}\nLet's think step by step:"}],
|
| 103 |
+
temperature=0.7 # Add randomness for diverse paths
|
| 104 |
+
)
|
| 105 |
+
responses.append(response.choices[0].message.content)
|
| 106 |
+
|
| 107 |
+
# Parse final answers from responses
|
| 108 |
+
answers = [r.split("Answer:")[-1].strip() if "Answer:" in r else r for r in responses]
|
| 109 |
+
|
| 110 |
+
# Return most common answer
|
| 111 |
+
from collections import Counter
|
| 112 |
+
most_common = Counter(answers).most_common(1)[0][0]
|
| 113 |
+
return most_common
|
| 114 |
+
|
| 115 |
+
# Example
|
| 116 |
+
question = "A bat and ball cost $1.10 in total. The bat costs $1.00 more than the ball. How much does the ball cost?"
|
| 117 |
+
answer = self_consistent_cot(question)
|
| 118 |
+
print(f"Final answer: {answer}")
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### 5. Structured Chain of Thought
|
| 122 |
+
Use specific reasoning templates for different problem types.
|
| 123 |
+
|
| 124 |
+
```python
|
| 125 |
+
math_template = """
|
| 126 |
+
Solve this math problem systematically:
|
| 127 |
+
|
| 128 |
+
1. Identify the given information
|
| 129 |
+
2. Identify what needs to be found
|
| 130 |
+
3. Determine the appropriate formula or approach
|
| 131 |
+
4. Execute the calculation
|
| 132 |
+
5. Verify the answer
|
| 133 |
+
|
| 134 |
+
Question: {question}
|
| 135 |
+
|
| 136 |
+
Answer:"""
|
| 137 |
+
|
| 138 |
+
coding_template = """
|
| 139 |
+
Debug this code step by step:
|
| 140 |
+
|
| 141 |
+
1. Understand what the code should do
|
| 142 |
+
2. Trace through the execution line by line
|
| 143 |
+
3. Identify where the behavior differs from expectations
|
| 144 |
+
4. Propose and test fixes
|
| 145 |
+
|
| 146 |
+
Code: {code}
|
| 147 |
+
|
| 148 |
+
Issue: {issue}
|
| 149 |
+
|
| 150 |
+
Fix:"""
|
| 151 |
+
|
| 152 |
+
def structured_cot(template, **kwargs):
|
| 153 |
+
prompt = template.format(**kwargs)
|
| 154 |
+
response = client.chat.completions.create(
|
| 155 |
+
model="gpt-4",
|
| 156 |
+
messages=[{"role": "user", "content": prompt}]
|
| 157 |
+
)
|
| 158 |
+
return response.choices[0].message.content
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
## Constraints
|
| 162 |
+
- **Token Usage**: Chain of thought increases token consumption significantly
|
| 163 |
+
- **Latency**: More tokens = longer response times
|
| 164 |
+
- **Model Selection**: Works best with larger models (GPT-4, Claude)
|
| 165 |
+
- **Temperature**: Use lower temperature (0-0.3) for more consistent reasoning
|
| 166 |
+
- **Verification**: Always verify the reasoning steps, especially for critical applications
|
| 167 |
+
|
| 168 |
+
## Expected Output
|
| 169 |
+
Improved reasoning performance on complex problems that require multi-step thinking, with transparent thought processes that can be verified and debugged.
|
TRAE-Skills/ai_engineering/Computer_Vision_Object_Detection.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Computer Vision - Object Detection
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To locate and identify multiple objects within an image or video stream by drawing bounding boxes and assigning class labels with confidence scores.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When counting objects (e.g., people in a crowd, cars on a road)
|
| 8 |
+
- When building autonomous navigation systems or robotics
|
| 9 |
+
- When performing automated quality inspection in manufacturing
|
| 10 |
+
- When extracting specific elements from documents (e.g., tables, signatures)
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Choose the Architecture
|
| 15 |
+
Select an architecture based on the trade-off between speed and accuracy:
|
| 16 |
+
- **YOLO (You Only Look Once)**: Best for real-time inference (YOLOv8, YOLOv10). Very fast and highly accurate.
|
| 17 |
+
- **Faster R-CNN**: Slower but highly accurate, especially for small objects. Good for medical imaging.
|
| 18 |
+
- **DETR (DEtection TRansformer)**: Transformer-based approach that eliminates the need for non-maximum suppression (NMS) and anchor boxes.
|
| 19 |
+
|
| 20 |
+
### 2. Dataset Preparation
|
| 21 |
+
Ensure the dataset is properly formatted. The most common formats are:
|
| 22 |
+
- **COCO JSON**: A single JSON file containing images, annotations (bounding boxes, polygons), and categories.
|
| 23 |
+
- **YOLO TXT**: One text file per image containing `class_id x_center y_center width height` (normalized between 0 and 1).
|
| 24 |
+
|
| 25 |
+
**Data Augmentation**:
|
| 26 |
+
Apply augmentations to improve robustness using libraries like `albumentations`:
|
| 27 |
+
```python
|
| 28 |
+
import albumentations as A
|
| 29 |
+
|
| 30 |
+
transform = A.Compose([
|
| 31 |
+
A.HorizontalFlip(p=0.5),
|
| 32 |
+
A.RandomBrightnessContrast(p=0.2),
|
| 33 |
+
A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.1, rotate_limit=45, p=0.2),
|
| 34 |
+
], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### 3. YOLOv8 Implementation Example
|
| 38 |
+
Using the Ultralytics library for training and inference.
|
| 39 |
+
|
| 40 |
+
**Installation**:
|
| 41 |
+
```bash
|
| 42 |
+
pip install ultralytics
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
**Training**:
|
| 46 |
+
Create a `data.yaml` file defining the dataset paths and classes:
|
| 47 |
+
```yaml
|
| 48 |
+
train: ../train/images
|
| 49 |
+
val: ../valid/images
|
| 50 |
+
|
| 51 |
+
nc: 3 # number of classes
|
| 52 |
+
names: ['car', 'pedestrian', 'traffic_light']
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
```python
|
| 56 |
+
from ultralytics import YOLO
|
| 57 |
+
|
| 58 |
+
# Load a pre-trained model (recommended for transfer learning)
|
| 59 |
+
model = YOLO('yolov8n.pt') # 'n' for nano, 's' for small, 'm' for medium, etc.
|
| 60 |
+
|
| 61 |
+
# Train the model on your custom dataset
|
| 62 |
+
results = model.train(data='data.yaml', epochs=100, imgsz=640, batch=16, device=0)
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Inference**:
|
| 66 |
+
```python
|
| 67 |
+
from ultralytics import YOLO
|
| 68 |
+
import cv2
|
| 69 |
+
|
| 70 |
+
# Load the fine-tuned model
|
| 71 |
+
model = YOLO('runs/detect/train/weights/best.pt')
|
| 72 |
+
|
| 73 |
+
# Perform inference on an image
|
| 74 |
+
results = model('test_image.jpg')
|
| 75 |
+
|
| 76 |
+
# View results
|
| 77 |
+
for result in results:
|
| 78 |
+
boxes = result.boxes # Bounding boxes object
|
| 79 |
+
for box in boxes:
|
| 80 |
+
# Extract coordinates, confidence, and class id
|
| 81 |
+
x1, y1, x2, y2 = box.xyxy[0]
|
| 82 |
+
conf = box.conf[0]
|
| 83 |
+
cls_id = int(box.cls[0])
|
| 84 |
+
print(f"Class: {model.names[cls_id]}, Confidence: {conf:.2f}, Box: [{x1}, {y1}, {x2}, {y2}]")
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### 4. Evaluation Metrics
|
| 88 |
+
Understand the standard metrics for object detection:
|
| 89 |
+
- **IoU (Intersection over Union)**: Measures the overlap between the predicted bounding box and the ground truth.
|
| 90 |
+
- **mAP (Mean Average Precision)**: The primary metric. Often calculated at different IoU thresholds (e.g., mAP@0.5, mAP@0.5:0.95).
|
| 91 |
+
|
| 92 |
+
## Best Practices
|
| 93 |
+
- Ensure a balanced dataset across all classes to prevent the model from ignoring rare objects.
|
| 94 |
+
- Pay attention to image size (`imgsz`). Larger sizes detect smaller objects better but require more memory and slow down inference.
|
| 95 |
+
- Utilize pre-trained weights (Transfer Learning) instead of training from scratch whenever possible.
|
TRAE-Skills/ai_engineering/Data_Drift_Detection.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Data Drift Detection
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To monitor production machine learning models for performance degradation caused by changes in the statistical properties of the input data (data drift) or the relationship between features and the target variable (concept drift) over time.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying models to production environments where data changes frequently (e.g., e-commerce, fraud detection, recommendation systems)
|
| 8 |
+
- When establishing a continuous training pipeline (MLOps)
|
| 9 |
+
- When debugging silent failures in model accuracy without corresponding system errors
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Identify Drift Types
|
| 14 |
+
Distinguish between the types of drift occurring in the data:
|
| 15 |
+
- **Covariate Shift (Data Drift)**: The distribution of input features ($P(X)$) changes, but the relationship with the target remains the same.
|
| 16 |
+
- **Prior Probability Shift**: The distribution of the target variable ($P(Y)$) changes.
|
| 17 |
+
- **Concept Drift**: The relationship between features and the target ($P(Y|X)$) changes (e.g., a new type of fraud emerges).
|
| 18 |
+
|
| 19 |
+
### 2. Choose Detection Methods
|
| 20 |
+
Select appropriate statistical tests to compare a reference dataset (usually the training data) with the current production data:
|
| 21 |
+
- **Numerical Features**: Kolmogorov-Smirnov (K-S) test, Wasserstein distance, Population Stability Index (PSI).
|
| 22 |
+
- **Categorical Features**: Chi-Square test, Jensen-Shannon distance.
|
| 23 |
+
- **Multivariate**: Domain Classifier (train a model to distinguish between reference and current data).
|
| 24 |
+
|
| 25 |
+
### 3. Implementation Example (Evidently AI)
|
| 26 |
+
Evidently is an open-source library for monitoring ML models.
|
| 27 |
+
|
| 28 |
+
**Installation**:
|
| 29 |
+
```bash
|
| 30 |
+
pip install evidently
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
**Generate a Data Drift Report**:
|
| 34 |
+
```python
|
| 35 |
+
import pandas as pd
|
| 36 |
+
from evidently.report import Report
|
| 37 |
+
from evidently.metric_preset import DataDriftPreset
|
| 38 |
+
|
| 39 |
+
# Load reference data (e.g., training set) and current data (e.g., last week's production data)
|
| 40 |
+
reference_data = pd.read_csv('train.csv')
|
| 41 |
+
current_data = pd.read_csv('production_week1.csv')
|
| 42 |
+
|
| 43 |
+
# Initialize the report with the DataDriftPreset
|
| 44 |
+
data_drift_report = Report(metrics=[DataDriftPreset()])
|
| 45 |
+
|
| 46 |
+
# Calculate metrics
|
| 47 |
+
data_drift_report.run(reference_data=reference_data, current_data=current_data)
|
| 48 |
+
|
| 49 |
+
# Save report as HTML
|
| 50 |
+
data_drift_report.save_html('data_drift_report.html')
|
| 51 |
+
|
| 52 |
+
# Get JSON output for programmatic integration (e.g., Airflow, Prefect)
|
| 53 |
+
drift_json = data_drift_report.json()
|
| 54 |
+
print(drift_json)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
**Custom Tests and Thresholds**:
|
| 58 |
+
You can define specific tests for different features:
|
| 59 |
+
```python
|
| 60 |
+
from evidently.test_suite import TestSuite
|
| 61 |
+
from evidently.tests import TestNumberOfDriftedColumns, TestShareOfDriftedColumns
|
| 62 |
+
from evidently.tests.base_test import generate_column_tests
|
| 63 |
+
from evidently.tests import TestColumnDrift
|
| 64 |
+
|
| 65 |
+
suite = TestSuite(tests=[
|
| 66 |
+
TestNumberOfDriftedColumns(lt=3), # Fail if more than 2 columns drift
|
| 67 |
+
TestShareOfDriftedColumns(lt=0.3), # Fail if >30% of columns drift
|
| 68 |
+
generate_column_tests(TestColumnDrift, columns=['age', 'income'])
|
| 69 |
+
])
|
| 70 |
+
|
| 71 |
+
suite.run(reference_data=reference_data, current_data=current_data)
|
| 72 |
+
suite.save_html('test_suite.html')
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### 4. Alerting and Retraining Strategy
|
| 76 |
+
Establish a workflow when drift is detected:
|
| 77 |
+
1. **Alerting**: Send notifications via Slack, PagerDuty, or email when drift metrics exceed thresholds.
|
| 78 |
+
2. **Investigation**: Data scientists analyze the report to determine if the drift is benign (e.g., seasonal change) or harmful.
|
| 79 |
+
3. **Retraining**: Trigger a CI/CD pipeline to retrain the model on the new data, validate the new model against a holdout set, and deploy it if it outperforms the current model.
|
| 80 |
+
|
| 81 |
+
## Best Practices
|
| 82 |
+
- **Define a Baseline**: Always establish a solid baseline using the training dataset or a validated holdout set.
|
| 83 |
+
- **Choose the Right Window Size**: The timeframe for the "current" data depends on the business context. Daily, weekly, or monthly windows are common.
|
| 84 |
+
- **Monitor the Monitor**: Ensure the drift detection system itself is robust and doesn't generate excessive false positive alerts (alert fatigue).
|
TRAE-Skills/ai_engineering/Distributed_Training_Horovod.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Distributed Training (Horovod)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To train large machine learning models faster by distributing the computational workload across multiple GPUs or nodes, using data parallelism or model parallelism techniques. Horovod (by Uber) provides a simple and efficient framework for distributed deep learning.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When training large models (e.g., Transformers, high-res CNNs) that exceed a single GPU's memory or take too long to converge
|
| 8 |
+
- When scaling out training to a cluster of machines
|
| 9 |
+
- When utilizing multi-GPU instances in the cloud (AWS p4d, GCP A100)
|
| 10 |
+
- When migrating single-GPU PyTorch or TensorFlow code to multi-GPU without extensive rewrites
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Installation
|
| 15 |
+
Install Horovod with the necessary framework support (PyTorch, TensorFlow, etc.).
|
| 16 |
+
```bash
|
| 17 |
+
# Requires MPI (Message Passing Interface) installed on the system
|
| 18 |
+
HOROVOD_WITH_PYTORCH=1 pip install horovod[pytorch]
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
### 2. PyTorch Integration Example
|
| 22 |
+
Convert a standard PyTorch training script to a distributed one.
|
| 23 |
+
|
| 24 |
+
**Initialize Horovod**:
|
| 25 |
+
```python
|
| 26 |
+
import torch
|
| 27 |
+
import horovod.torch as hvd
|
| 28 |
+
|
| 29 |
+
# 1. Initialize Horovod
|
| 30 |
+
hvd.init()
|
| 31 |
+
|
| 32 |
+
# 2. Pin GPU to local rank (ensure each process uses a different GPU)
|
| 33 |
+
if torch.cuda.is_available():
|
| 34 |
+
torch.cuda.set_device(hvd.local_rank())
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
**Data Loading**:
|
| 38 |
+
Partition the dataset among workers using a `DistributedSampler`.
|
| 39 |
+
```python
|
| 40 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 41 |
+
|
| 42 |
+
dataset = MyDataset()
|
| 43 |
+
# 3. Partition data
|
| 44 |
+
sampler = DistributedSampler(dataset, num_replicas=hvd.size(), rank=hvd.rank())
|
| 45 |
+
train_loader = torch.utils.data.DataLoader(dataset, batch_size=32, sampler=sampler)
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**Optimizer and Broadcasting**:
|
| 49 |
+
Scale the learning rate by the number of workers and wrap the optimizer.
|
| 50 |
+
```python
|
| 51 |
+
model = MyModel().cuda()
|
| 52 |
+
# 4. Scale learning rate
|
| 53 |
+
optimizer = torch.optim.SGD(model.parameters(), lr=0.01 * hvd.size())
|
| 54 |
+
|
| 55 |
+
# 5. Add Horovod Distributed Optimizer
|
| 56 |
+
optimizer = hvd.DistributedOptimizer(
|
| 57 |
+
optimizer, named_parameters=model.named_parameters(),
|
| 58 |
+
op=hvd.Adsum # Default is hvd.Average
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# 6. Broadcast initial parameters and optimizer state from rank 0 to all other processes
|
| 62 |
+
hvd.broadcast_parameters(model.state_dict(), root_rank=0)
|
| 63 |
+
hvd.broadcast_optimizer_state(optimizer, root_rank=0)
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 3. Execution
|
| 67 |
+
Run the script using `horovodrun` or `mpirun`.
|
| 68 |
+
|
| 69 |
+
**Local Multi-GPU (e.g., 4 GPUs on one machine)**:
|
| 70 |
+
```bash
|
| 71 |
+
horovodrun -np 4 -H localhost:4 python train.py
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
**Multi-Node (e.g., 2 machines, 4 GPUs each)**:
|
| 75 |
+
```bash
|
| 76 |
+
horovodrun -np 8 -H server1:4,server2:4 python train.py
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Best Practices
|
| 80 |
+
- **Learning Rate Scaling**: Always scale the learning rate linearly with the number of workers (`lr * hvd.size()`), and consider a warmup period for the first few epochs.
|
| 81 |
+
- **Checkpointing**: Only save checkpoints on `hvd.rank() == 0` to prevent file corruption from concurrent writes.
|
| 82 |
+
- **Batch Size**: The effective batch size becomes `batch_size_per_worker * hvd.size()`. Adjust accordingly to maintain convergence.
|
TRAE-Skills/ai_engineering/Embedding_Techniques.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Embedding Techniques
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To transform text, images, or other data into dense vector representations that capture semantic meaning for tasks like semantic search, clustering, and recommendation systems.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building semantic search systems
|
| 8 |
+
- When implementing RAG (Retrieval-Augmented Generation)
|
| 9 |
+
- When creating recommendation engines
|
| 10 |
+
- When performing document similarity analysis
|
| 11 |
+
- When clustering content based on meaning
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Choose Your Embedding Model
|
| 16 |
+
Select based on your use case and resource constraints.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
from sentence_transformers import SentenceTransformer
|
| 20 |
+
|
| 21 |
+
# For general purpose semantic search
|
| 22 |
+
model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 23 |
+
|
| 24 |
+
# For multilingual content
|
| 25 |
+
multilingual_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
|
| 26 |
+
|
| 27 |
+
# For code-specific embeddings
|
| 28 |
+
code_model = SentenceTransformer('microsoft/codebert-base')
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
### 2. Generate Embeddings
|
| 32 |
+
Transform your documents into vectors.
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
documents = [
|
| 36 |
+
"Machine learning is a subset of artificial intelligence",
|
| 37 |
+
"Deep learning uses neural networks with multiple layers",
|
| 38 |
+
"Natural language processing deals with text understanding"
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
embeddings = model.encode(documents)
|
| 42 |
+
|
| 43 |
+
print(f"Shape: {embeddings.shape}") # (3, 384) for MiniLM
|
| 44 |
+
print(f"Dimension: {len(embeddings[0])}") # 384 dimensions
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### 3. Store in Vector Database
|
| 48 |
+
Persist embeddings for efficient similarity search.
|
| 49 |
+
|
| 50 |
+
```python
|
| 51 |
+
import faiss
|
| 52 |
+
import numpy as np
|
| 53 |
+
|
| 54 |
+
# Create FAISS index
|
| 55 |
+
dimension = embeddings.shape[1]
|
| 56 |
+
index = faiss.IndexFlatL2(dimension)
|
| 57 |
+
|
| 58 |
+
# Add embeddings
|
| 59 |
+
index.add(embeddings.astype('float32'))
|
| 60 |
+
|
| 61 |
+
# Save index
|
| 62 |
+
faiss.write_index(index, 'document_embeddings.index')
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### 4. Semantic Search
|
| 66 |
+
Find similar documents using vector similarity.
|
| 67 |
+
|
| 68 |
+
```python
|
| 69 |
+
query = "AI and neural networks"
|
| 70 |
+
query_embedding = model.encode([query])
|
| 71 |
+
|
| 72 |
+
# Search for top-k similar documents
|
| 73 |
+
k = 3
|
| 74 |
+
distances, indices = index.search(query_embedding.astype('float32'), k)
|
| 75 |
+
|
| 76 |
+
results = [(documents[i], distances[0][j]) for j, i in enumerate(indices[0])]
|
| 77 |
+
for doc, score in results:
|
| 78 |
+
print(f"Similarity: {score:.4f} | {doc}")
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### 5. Batch Processing for Large Datasets
|
| 82 |
+
Process large document collections efficiently.
|
| 83 |
+
|
| 84 |
+
```python
|
| 85 |
+
from tqdm import tqdm
|
| 86 |
+
|
| 87 |
+
def batch_encode(documents, batch_size=32):
|
| 88 |
+
embeddings = []
|
| 89 |
+
for i in tqdm(range(0, len(documents), batch_size)):
|
| 90 |
+
batch = documents[i:i + batch_size]
|
| 91 |
+
batch_embeddings = model.encode(batch)
|
| 92 |
+
embeddings.extend(batch_embeddings)
|
| 93 |
+
return np.array(embeddings)
|
| 94 |
+
|
| 95 |
+
# Usage
|
| 96 |
+
large_corpus = load_large_dataset() # Your data loading function
|
| 97 |
+
embeddings = batch_encode(large_corpus, batch_size=64)
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
## Constraints
|
| 101 |
+
- **Dimensionality**: Higher dimensions = better quality but more storage/computation
|
| 102 |
+
- **Batch Size**: Adjust based on available GPU memory
|
| 103 |
+
- **Model Selection**: Consider trade-offs between quality, speed, and model size
|
| 104 |
+
- **Multilingual**: Use specialized models for non-English content
|
| 105 |
+
- **Domain-Specific**: Fine-tune or use domain-specific models for technical content
|
| 106 |
+
|
| 107 |
+
## Expected Output
|
| 108 |
+
High-quality vector representations that capture semantic meaning, enabling powerful similarity search and retrieval operations.
|
TRAE-Skills/ai_engineering/Federated_Learning.md
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Federated Learning
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To train machine learning models across decentralized edge devices while keeping data local, preserving privacy and reducing data transfer requirements.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When training on sensitive data that cannot leave devices
|
| 8 |
+
- When building healthcare or financial applications with privacy requirements
|
| 9 |
+
- When reducing data transfer costs in distributed systems
|
| 10 |
+
- When compliance with data protection regulations (GDPR, HIPAA)
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Federated Learning Architecture
|
| 15 |
+
Set up the federated learning framework.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
import torch.optim as optim
|
| 21 |
+
from copy import deepcopy
|
| 22 |
+
import numpy as np
|
| 23 |
+
|
| 24 |
+
class FederatedLearningServer:
|
| 25 |
+
def __init__(self, global_model, learning_rate=0.01):
|
| 26 |
+
self.global_model = global_model
|
| 27 |
+
self.learning_rate = learning_rate
|
| 28 |
+
self.client_weights = []
|
| 29 |
+
|
| 30 |
+
def aggregate_models(self, client_models, client_weights=None):
|
| 31 |
+
"""Aggregate client models using FedAvg algorithm."""
|
| 32 |
+
if client_weights is None:
|
| 33 |
+
# Equal weights for all clients
|
| 34 |
+
client_weights = [1.0 / len(client_models)] * len(client_models)
|
| 35 |
+
|
| 36 |
+
# Get global model state dict
|
| 37 |
+
global_state = self.global_model.state_dict()
|
| 38 |
+
|
| 39 |
+
# Initialize aggregated state
|
| 40 |
+
aggregated_state = deepcopy(global_state)
|
| 41 |
+
for key in aggregated_state.keys():
|
| 42 |
+
aggregated_state[key] = torch.zeros_like(aggregated_state[key])
|
| 43 |
+
|
| 44 |
+
# Aggregate weights from all clients
|
| 45 |
+
for client_model, weight in zip(client_models, client_weights):
|
| 46 |
+
client_state = client_model.state_dict()
|
| 47 |
+
for key in aggregated_state.keys():
|
| 48 |
+
aggregated_state[key] += weight * client_state[key]
|
| 49 |
+
|
| 50 |
+
# Update global model
|
| 51 |
+
self.global_model.load_state_dict(aggregated_state)
|
| 52 |
+
|
| 53 |
+
return self.global_model
|
| 54 |
+
|
| 55 |
+
def distribute_model(self):
|
| 56 |
+
"""Send global model to clients."""
|
| 57 |
+
return deepcopy(self.global_model)
|
| 58 |
+
|
| 59 |
+
class FederatedLearningClient:
|
| 60 |
+
def __init__(self, local_model, local_data, optimizer_type='adam', learning_rate=0.01):
|
| 61 |
+
self.local_model = local_model
|
| 62 |
+
self.local_data = local_data
|
| 63 |
+
self.optimizer = optim.Adam(self.local_model.parameters(), lr=learning_rate)
|
| 64 |
+
self.criterion = nn.CrossEntropyLoss()
|
| 65 |
+
|
| 66 |
+
def train_local(self, epochs=5, batch_size=32):
|
| 67 |
+
"""Train model on local data."""
|
| 68 |
+
self.local_model.train()
|
| 69 |
+
|
| 70 |
+
# Assume local_data is a DataLoader
|
| 71 |
+
for epoch in range(epochs):
|
| 72 |
+
for batch_x, batch_y in self.local_data:
|
| 73 |
+
self.optimizer.zero_grad()
|
| 74 |
+
|
| 75 |
+
outputs = self.local_model(batch_x)
|
| 76 |
+
loss = self.criterion(outputs, batch_y)
|
| 77 |
+
|
| 78 |
+
loss.backward()
|
| 79 |
+
self.optimizer.step()
|
| 80 |
+
|
| 81 |
+
return self.local_model
|
| 82 |
+
|
| 83 |
+
def update_model(self, global_model):
|
| 84 |
+
"""Update local model with global model."""
|
| 85 |
+
self.local_model.load_state_dict(global_model.state_dict())
|
| 86 |
+
return self.local_model
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
### 2. Differential Privacy Integration
|
| 90 |
+
Add privacy guarantees to federated learning.
|
| 91 |
+
|
| 92 |
+
```python
|
| 93 |
+
import torch.nn.functional as F
|
| 94 |
+
|
| 95 |
+
class DifferentialPrivacyClient:
|
| 96 |
+
def __init__(self, model, local_data, clip_norm=1.0, noise_multiplier=0.1):
|
| 97 |
+
self.model = model
|
| 98 |
+
self.local_data = local_data
|
| 99 |
+
self.clip_norm = clip_norm
|
| 100 |
+
self.noise_multiplier = noise_multiplier
|
| 101 |
+
self.optimizer = optim.Adam(self.model.parameters(), lr=0.01)
|
| 102 |
+
self.criterion = nn.CrossEntropyLoss()
|
| 103 |
+
|
| 104 |
+
def clip_gradients(self):
|
| 105 |
+
"""Clip gradients to bound sensitivity."""
|
| 106 |
+
torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.clip_norm)
|
| 107 |
+
|
| 108 |
+
def add_noise(self, parameters):
|
| 109 |
+
"""Add Gaussian noise to gradients."""
|
| 110 |
+
with torch.no_grad():
|
| 111 |
+
for param in parameters:
|
| 112 |
+
noise = torch.randn_like(param) * self.noise_multiplier * self.clip_norm
|
| 113 |
+
param.grad += noise
|
| 114 |
+
|
| 115 |
+
def train_with_dp(self, epochs=5):
|
| 116 |
+
"""Train with differential privacy."""
|
| 117 |
+
self.model.train()
|
| 118 |
+
|
| 119 |
+
for epoch in range(epochs):
|
| 120 |
+
for batch_x, batch_y in self.local_data:
|
| 121 |
+
self.optimizer.zero_grad()
|
| 122 |
+
|
| 123 |
+
outputs = self.model(batch_x)
|
| 124 |
+
loss = self.criterion(outputs, batch_y)
|
| 125 |
+
|
| 126 |
+
loss.backward()
|
| 127 |
+
|
| 128 |
+
# Apply differential privacy
|
| 129 |
+
self.clip_gradients()
|
| 130 |
+
self.add_noise(self.model.parameters())
|
| 131 |
+
|
| 132 |
+
self.optimizer.step()
|
| 133 |
+
|
| 134 |
+
return self.model
|
| 135 |
+
|
| 136 |
+
def compute_privacy_spent(self, epochs, noise_multiplier, sample_rate):
|
| 137 |
+
"""Compute privacy budget spent."""
|
| 138 |
+
# Simplified privacy accounting
|
| 139 |
+
# In practice, use moments accountant or RDP accountant
|
| 140 |
+
epsilon = epochs * sample_rate / noise_multiplier
|
| 141 |
+
return epsilon
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
### 3. Secure Aggregation
|
| 145 |
+
Implement secure aggregation protocols.
|
| 146 |
+
|
| 147 |
+
```python
|
| 148 |
+
import hashlib
|
| 149 |
+
import random
|
| 150 |
+
|
| 151 |
+
class SecureAggregationServer:
|
| 152 |
+
def __init__(self, global_model):
|
| 153 |
+
self.global_model = global_model
|
| 154 |
+
self.client_seeds = {}
|
| 155 |
+
|
| 156 |
+
def distribute_seeds(self, client_ids):
|
| 157 |
+
"""Distribute random seeds to clients."""
|
| 158 |
+
seeds = {}
|
| 159 |
+
for client_id in client_ids:
|
| 160 |
+
seeds[client_id] = random.randint(0, 1000000)
|
| 161 |
+
self.client_seeds[client_id] = seeds[client_id]
|
| 162 |
+
return seeds
|
| 163 |
+
|
| 164 |
+
def secure_aggregate(self, client_updates):
|
| 165 |
+
"""Aggregate updates with one-time masking."""
|
| 166 |
+
# In real implementation, use cryptographic protocols
|
| 167 |
+
# This is a simplified version
|
| 168 |
+
|
| 169 |
+
aggregated_update = {}
|
| 170 |
+
|
| 171 |
+
# Remove masks (in real FL, clients mask each other's updates)
|
| 172 |
+
for client_id, update in client_updates.items():
|
| 173 |
+
for key, value in update.items():
|
| 174 |
+
if key not in aggregated_update:
|
| 175 |
+
aggregated_update[key] = value
|
| 176 |
+
else:
|
| 177 |
+
aggregated_update[key] += value
|
| 178 |
+
|
| 179 |
+
# Average the updates
|
| 180 |
+
num_clients = len(client_updates)
|
| 181 |
+
for key in aggregated_update:
|
| 182 |
+
aggregated_update[key] /= num_clients
|
| 183 |
+
|
| 184 |
+
# Update global model
|
| 185 |
+
global_state = self.global_model.state_dict()
|
| 186 |
+
for key, value in aggregated_update.items():
|
| 187 |
+
if key in global_state:
|
| 188 |
+
global_state[key] += value
|
| 189 |
+
|
| 190 |
+
self.global_model.load_state_dict(global_state)
|
| 191 |
+
return self.global_model
|
| 192 |
+
|
| 193 |
+
class SecureAggregationClient:
|
| 194 |
+
def __init__(self, model, data):
|
| 195 |
+
self.model = model
|
| 196 |
+
self.data = data
|
| 197 |
+
|
| 198 |
+
def compute_model_update(self, global_model):
|
| 199 |
+
"""Compute model update (difference from global model)."""
|
| 200 |
+
update = {}
|
| 201 |
+
global_state = global_model.state_dict()
|
| 202 |
+
local_state = self.model.state_dict()
|
| 203 |
+
|
| 204 |
+
for key in global_state:
|
| 205 |
+
update[key] = local_state[key] - global_state[key]
|
| 206 |
+
|
| 207 |
+
return update
|
| 208 |
+
|
| 209 |
+
def apply_mask(self, update, seed):
|
| 210 |
+
"""Apply random mask to update."""
|
| 211 |
+
random.seed(seed)
|
| 212 |
+
masked_update = {}
|
| 213 |
+
|
| 214 |
+
for key, value in update.items():
|
| 215 |
+
# Generate random mask
|
| 216 |
+
mask = torch.randn_like(value) * 0.01
|
| 217 |
+
masked_update[key] = value + mask
|
| 218 |
+
|
| 219 |
+
return masked_update
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
### 4. Federated Learning Simulation
|
| 223 |
+
Simulate federated learning across multiple clients.
|
| 224 |
+
|
| 225 |
+
```python
|
| 226 |
+
from torch.utils.data import DataLoader, TensorDataset
|
| 227 |
+
import torchvision
|
| 228 |
+
import torchvision.transforms as transforms
|
| 229 |
+
|
| 230 |
+
def create_client_datasets(num_clients=10, samples_per_client=1000):
|
| 231 |
+
"""Create local datasets for each client."""
|
| 232 |
+
# Load MNIST dataset
|
| 233 |
+
transform = transforms.Compose([transforms.ToTensor()])
|
| 234 |
+
mnist = torchvision.datasets.MNIST(root='./data', train=True,
|
| 235 |
+
download=True, transform=transform)
|
| 236 |
+
|
| 237 |
+
# Split data among clients (non-IID)
|
| 238 |
+
client_datasets = []
|
| 239 |
+
data_per_client = len(mnist) // num_clients
|
| 240 |
+
|
| 241 |
+
for i in range(num_clients):
|
| 242 |
+
start_idx = i * data_per_client
|
| 243 |
+
end_idx = (i + 1) * data_per_client
|
| 244 |
+
|
| 245 |
+
# Create non-IID distribution by sorting labels
|
| 246 |
+
data_subset = torch.utils.data.Subset(mnist, range(start_idx, end_idx))
|
| 247 |
+
client_datasets.append(DataLoader(data_subset, batch_size=32, shuffle=True))
|
| 248 |
+
|
| 249 |
+
return client_datasets
|
| 250 |
+
|
| 251 |
+
def run_federated_learning(num_rounds=10, num_clients=10, local_epochs=5):
|
| 252 |
+
"""Run federated learning simulation."""
|
| 253 |
+
# Create global model
|
| 254 |
+
global_model = nn.Sequential(
|
| 255 |
+
nn.Flatten(),
|
| 256 |
+
nn.Linear(784, 128),
|
| 257 |
+
nn.ReLU(),
|
| 258 |
+
nn.Linear(128, 10)
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
# Create server
|
| 262 |
+
server = FederatedLearningServer(global_model)
|
| 263 |
+
|
| 264 |
+
# Create clients
|
| 265 |
+
client_datasets = create_client_datasets(num_clients)
|
| 266 |
+
clients = []
|
| 267 |
+
|
| 268 |
+
for data in client_datasets:
|
| 269 |
+
client_model = deepcopy(global_model)
|
| 270 |
+
client = FederatedLearningClient(client_model, data)
|
| 271 |
+
clients.append(client)
|
| 272 |
+
|
| 273 |
+
# Training rounds
|
| 274 |
+
for round_num in range(num_rounds):
|
| 275 |
+
print(f"Round {round_num + 1}/{num_rounds}")
|
| 276 |
+
|
| 277 |
+
# Distribute global model
|
| 278 |
+
global_model_state = server.distribute_model()
|
| 279 |
+
|
| 280 |
+
# Select subset of clients (client sampling)
|
| 281 |
+
selected_clients = random.sample(clients, min(5, len(clients)))
|
| 282 |
+
|
| 283 |
+
# Local training
|
| 284 |
+
client_models = []
|
| 285 |
+
for client in selected_clients:
|
| 286 |
+
client.update_model(global_model_state)
|
| 287 |
+
updated_model = client.train_local(epochs=local_epochs)
|
| 288 |
+
client_models.append(updated_model)
|
| 289 |
+
|
| 290 |
+
# Aggregate models
|
| 291 |
+
server.aggregate_models(client_models)
|
| 292 |
+
|
| 293 |
+
# Evaluate global model (simplified)
|
| 294 |
+
print(f" Completed training with {len(client_models)} clients")
|
| 295 |
+
|
| 296 |
+
return global_model
|
| 297 |
+
|
| 298 |
+
# Usage
|
| 299 |
+
# global_model = run_federated_learning(num_rounds=10, num_clients=10, local_epochs=5)
|
| 300 |
+
# print("Federated learning completed!")
|
| 301 |
+
```
|
| 302 |
+
|
| 303 |
+
### 5. Monitoring and Evaluation
|
| 304 |
+
Monitor federated learning progress.
|
| 305 |
+
|
| 306 |
+
```python
|
| 307 |
+
class FederatedLearningMonitor:
|
| 308 |
+
def __init__(self):
|
| 309 |
+
self.metrics = {
|
| 310 |
+
'round': [],
|
| 311 |
+
'client_accuracies': [],
|
| 312 |
+
'global_accuracy': [],
|
| 313 |
+
'communication_cost': [],
|
| 314 |
+
'privacy_budget': []
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
def log_round(self, round_num, client_metrics, global_accuracy, comm_cost, privacy_epsilon):
|
| 318 |
+
"""Log metrics for a round."""
|
| 319 |
+
self.metrics['round'].append(round_num)
|
| 320 |
+
self.metrics['client_accuracies'].append(client_metrics)
|
| 321 |
+
self.metrics['global_accuracy'].append(global_accuracy)
|
| 322 |
+
self.metrics['communication_cost'].append(comm_cost)
|
| 323 |
+
self.metrics['privacy_budget'].append(privacy_epsilon)
|
| 324 |
+
|
| 325 |
+
def evaluate_global_model(self, model, test_loader):
|
| 326 |
+
"""Evaluate global model on test data."""
|
| 327 |
+
model.eval()
|
| 328 |
+
correct = 0
|
| 329 |
+
total = 0
|
| 330 |
+
|
| 331 |
+
with torch.no_grad():
|
| 332 |
+
for batch_x, batch_y in test_loader:
|
| 333 |
+
outputs = model(batch_x)
|
| 334 |
+
_, predicted = torch.max(outputs.data, 1)
|
| 335 |
+
total += batch_y.size(0)
|
| 336 |
+
correct += (predicted == batch_y).sum().item()
|
| 337 |
+
|
| 338 |
+
accuracy = 100 * correct / total
|
| 339 |
+
return accuracy
|
| 340 |
+
|
| 341 |
+
def generate_report(self):
|
| 342 |
+
"""Generate training report."""
|
| 343 |
+
import matplotlib.pyplot as plt
|
| 344 |
+
|
| 345 |
+
# Plot global accuracy over rounds
|
| 346 |
+
plt.figure(figsize=(12, 4))
|
| 347 |
+
|
| 348 |
+
plt.subplot(1, 3, 1)
|
| 349 |
+
plt.plot(self.metrics['round'], self.metrics['global_accuracy'])
|
| 350 |
+
plt.xlabel('Round')
|
| 351 |
+
plt.ylabel('Global Accuracy (%)')
|
| 352 |
+
plt.title('Global Model Performance')
|
| 353 |
+
|
| 354 |
+
plt.subplot(1, 3, 2)
|
| 355 |
+
plt.plot(self.metrics['round'], self.metrics['communication_cost'])
|
| 356 |
+
plt.xlabel('Round')
|
| 357 |
+
plt.ylabel('Communication Cost (MB)')
|
| 358 |
+
plt.title('Communication Overhead')
|
| 359 |
+
|
| 360 |
+
plt.subplot(1, 3, 3)
|
| 361 |
+
plt.plot(self.metrics['round'], self.metrics['privacy_budget'])
|
| 362 |
+
plt.xlabel('Round')
|
| 363 |
+
plt.ylabel('Privacy Budget (ε)')
|
| 364 |
+
plt.title('Privacy Budget Consumption')
|
| 365 |
+
|
| 366 |
+
plt.tight_layout()
|
| 367 |
+
plt.savefig('federated_learning_metrics.png')
|
| 368 |
+
plt.close()
|
| 369 |
+
|
| 370 |
+
return "Federated learning metrics saved to federated_learning_metrics.png"
|
| 371 |
+
|
| 372 |
+
# Usage
|
| 373 |
+
# monitor = FederatedLearningMonitor()
|
| 374 |
+
# monitor.log_round(1, [85.2, 87.1, 86.5], 86.0, 5.2, 0.1)
|
| 375 |
+
# monitor.generate_report()
|
| 376 |
+
```
|
| 377 |
+
|
| 378 |
+
## Constraints
|
| 379 |
+
- **Communication Overhead**: Frequent model updates can be bandwidth-intensive
|
| 380 |
+
- **Heterogeneity**: Non-IID data across clients can hurt convergence
|
| 381 |
+
- **Privacy-Utility Tradeoff**: Stronger privacy protection may reduce model accuracy
|
| 382 |
+
- **System Complexity**: Federated learning systems are complex to implement and maintain
|
| 383 |
+
- **Client Availability**: Clients may be unavailable or have varying capabilities
|
| 384 |
+
- **Scalability**: Large numbers of clients present coordination challenges
|
| 385 |
+
|
| 386 |
+
## Expected Output
|
| 387 |
+
A privacy-preserving machine learning system that trains models across decentralized devices while maintaining data locality and providing strong privacy guarantees.
|
TRAE-Skills/ai_engineering/Fine_Tuning_Custom_Models.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Fine-Tuning Custom Models
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To adapt pre-trained LLMs to specific domains, tasks, or styles by training on custom datasets for improved performance on specialized use cases.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When you need domain-specific knowledge (legal, medical, technical)
|
| 8 |
+
- When you want a specific writing style or tone
|
| 9 |
+
- When you have a large dataset of example inputs/outputs
|
| 10 |
+
- When you need consistent formatting or structure in outputs
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Prepare Your Dataset
|
| 15 |
+
Create properly formatted training data.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import json
|
| 19 |
+
|
| 20 |
+
# Training data format for OpenAI fine-tuning
|
| 21 |
+
training_data = [
|
| 22 |
+
{
|
| 23 |
+
"messages": [
|
| 24 |
+
{"role": "system", "content": "You are a legal assistant specializing in contract review."},
|
| 25 |
+
{"role": "user", "content": "Review this clause: 'The company may terminate at any time.'"},
|
| 26 |
+
{"role": "assistant", "content": "This clause is one-sided. Consider adding: 'either party may terminate with 30 days notice.'"}
|
| 27 |
+
]
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"messages": [
|
| 31 |
+
{"role": "system", "content": "You are a legal assistant specializing in contract review."},
|
| 32 |
+
{"role": "user", "content": "Is this confidentiality clause standard?"},
|
| 33 |
+
{"role": "assistant", "content": "The clause appears standard. It includes mutual confidentiality obligations and defines exceptions appropriately."}
|
| 34 |
+
]
|
| 35 |
+
}
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
# Save to JSONL file
|
| 39 |
+
with open('training_data.jsonl', 'w') as f:
|
| 40 |
+
for entry in training_data:
|
| 41 |
+
f.write(json.dumps(entry) + '\n')
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### 2. Validate Your Data
|
| 45 |
+
Ensure data quality before training.
|
| 46 |
+
|
| 47 |
+
```python
|
| 48 |
+
import json
|
| 49 |
+
from collections import defaultdict
|
| 50 |
+
|
| 51 |
+
def validate_finetuning_data(file_path):
|
| 52 |
+
"""Validate fine-tuning dataset."""
|
| 53 |
+
errors = []
|
| 54 |
+
stats = defaultdict(int)
|
| 55 |
+
|
| 56 |
+
with open(file_path, 'r') as f:
|
| 57 |
+
for line_num, line in enumerate(f, 1):
|
| 58 |
+
try:
|
| 59 |
+
entry = json.loads(line)
|
| 60 |
+
stats['total_entries'] += 1
|
| 61 |
+
|
| 62 |
+
# Check required fields
|
| 63 |
+
if 'messages' not in entry:
|
| 64 |
+
errors.append(f"Line {line_num}: Missing 'messages' field")
|
| 65 |
+
continue
|
| 66 |
+
|
| 67 |
+
messages = entry['messages']
|
| 68 |
+
stats['total_messages'] += len(messages)
|
| 69 |
+
|
| 70 |
+
# Validate message structure
|
| 71 |
+
for msg in messages:
|
| 72 |
+
if 'role' not in msg or 'content' not in msg:
|
| 73 |
+
errors.append(f"Line {line_num}: Invalid message structure")
|
| 74 |
+
|
| 75 |
+
if msg['role'] not in ['system', 'user', 'assistant']:
|
| 76 |
+
errors.append(f"Line {line_num}: Invalid role: {msg['role']}")
|
| 77 |
+
|
| 78 |
+
stats[f"role_{msg['role']}"] += 1
|
| 79 |
+
|
| 80 |
+
except json.JSONDecodeError:
|
| 81 |
+
errors.append(f"Line {line_num}: Invalid JSON")
|
| 82 |
+
|
| 83 |
+
return {
|
| 84 |
+
'errors': errors,
|
| 85 |
+
'stats': dict(stats)
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
# Usage
|
| 89 |
+
validation_result = validate_finetuning_data('training_data.jsonl')
|
| 90 |
+
print(f"Validation complete: {validation_result['stats']['total_entries']} entries")
|
| 91 |
+
if validation_result['errors']:
|
| 92 |
+
print(f"Errors found: {len(validation_result['errors'])}")
|
| 93 |
+
for error in validation_result['errors'][:10]: # Show first 10 errors
|
| 94 |
+
print(f" - {error}")
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### 3. Upload and Prepare Training
|
| 98 |
+
Upload data to OpenAI and start fine-tuning.
|
| 99 |
+
|
| 100 |
+
```python
|
| 101 |
+
from openai import OpenAI
|
| 102 |
+
|
| 103 |
+
client = OpenAI()
|
| 104 |
+
|
| 105 |
+
# Upload training file
|
| 106 |
+
with open('training_data.jsonl', 'rb') as f:
|
| 107 |
+
upload_response = client.files.create(
|
| 108 |
+
file=f,
|
| 109 |
+
purpose='fine-tune'
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
training_file_id = upload_response.id
|
| 113 |
+
print(f"File uploaded: {training_file_id}")
|
| 114 |
+
|
| 115 |
+
# Create fine-tuning job
|
| 116 |
+
fine_tune_job = client.fine_tuning.jobs.create(
|
| 117 |
+
training_file=training_file_id,
|
| 118 |
+
model="gpt-3.5-turbo",
|
| 119 |
+
hyperparameters={
|
| 120 |
+
"n_epochs": 3,
|
| 121 |
+
"batch_size": 4,
|
| 122 |
+
"learning_rate_multiplier": 0.1
|
| 123 |
+
}
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
job_id = fine_tune_job.id
|
| 127 |
+
print(f"Fine-tuning job started: {job_id}")
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### 4. Monitor Training Progress
|
| 131 |
+
Track the fine-tuning process.
|
| 132 |
+
|
| 133 |
+
```python
|
| 134 |
+
def check_finetuning_status(job_id):
|
| 135 |
+
"""Check the status of a fine-tuning job."""
|
| 136 |
+
job = client.fine_tuning.jobs.retrieve(job_id)
|
| 137 |
+
|
| 138 |
+
status = {
|
| 139 |
+
'status': job.status,
|
| 140 |
+
'created_at': job.created_at,
|
| 141 |
+
'finished_at': job.finished_at,
|
| 142 |
+
'model': job.fine_tuned_model,
|
| 143 |
+
'error': job.error
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
if job.result_files:
|
| 147 |
+
# Retrieve training metrics
|
| 148 |
+
result_file = client.files.retrieve(job.result_files[0])
|
| 149 |
+
print(f"Results file: {result_file.id}")
|
| 150 |
+
|
| 151 |
+
return status
|
| 152 |
+
|
| 153 |
+
# Usage
|
| 154 |
+
import time
|
| 155 |
+
|
| 156 |
+
while True:
|
| 157 |
+
status = check_finetuning_status(job_id)
|
| 158 |
+
print(f"Status: {status['status']}")
|
| 159 |
+
|
| 160 |
+
if status['status'] in ['succeeded', 'failed', 'cancelled']:
|
| 161 |
+
break
|
| 162 |
+
|
| 163 |
+
time.sleep(60) # Check every minute
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
### 5. Use the Fine-Tuned Model
|
| 167 |
+
Deploy and use your custom model.
|
| 168 |
+
|
| 169 |
+
```python
|
| 170 |
+
def use_finetuned_model(model_id, prompt):
|
| 171 |
+
"""Use the fine-tuned model for inference."""
|
| 172 |
+
response = client.chat.completions.create(
|
| 173 |
+
model=model_id,
|
| 174 |
+
messages=[{"role": "user", "content": prompt}]
|
| 175 |
+
)
|
| 176 |
+
return response.choices[0].message.content
|
| 177 |
+
|
| 178 |
+
# After training completes
|
| 179 |
+
fine_tuned_model = status['model']
|
| 180 |
+
|
| 181 |
+
# Test the model
|
| 182 |
+
test_prompt = "Review this contract clause: 'Employee shall not compete for 2 years after termination.'"
|
| 183 |
+
result = use_finetuned_model(fine_tuned_model, test_prompt)
|
| 184 |
+
print(result)
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
### 6. Evaluate Model Performance
|
| 188 |
+
Assess the quality of your fine-tuned model.
|
| 189 |
+
|
| 190 |
+
```python
|
| 191 |
+
def evaluate_model(model_id, test_data):
|
| 192 |
+
"""Evaluate fine-tuned model on test set."""
|
| 193 |
+
results = []
|
| 194 |
+
|
| 195 |
+
for test_case in test_data:
|
| 196 |
+
# Get model response
|
| 197 |
+
response = use_finetuned_model(model_id, test_case['input'])
|
| 198 |
+
|
| 199 |
+
# Compare with expected output
|
| 200 |
+
results.append({
|
| 201 |
+
'input': test_case['input'],
|
| 202 |
+
'expected': test_case['expected'],
|
| 203 |
+
'actual': response,
|
| 204 |
+
'match': test_case['expected'].lower() in response.lower()
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
# Calculate metrics
|
| 208 |
+
accuracy = sum(r['match'] for r in results) / len(results)
|
| 209 |
+
|
| 210 |
+
return {
|
| 211 |
+
'accuracy': accuracy,
|
| 212 |
+
'results': results
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
# Test dataset
|
| 216 |
+
test_data = [
|
| 217 |
+
{
|
| 218 |
+
'input': 'Is this termination clause fair?',
|
| 219 |
+
'expected': 'fair' # or 'unfair'
|
| 220 |
+
}
|
| 221 |
+
# ... more test cases
|
| 222 |
+
]
|
| 223 |
+
|
| 224 |
+
evaluation = evaluate_model(fine_tuned_model, test_data)
|
| 225 |
+
print(f"Accuracy: {evaluation['accuracy']:.2%}")
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
## Constraints
|
| 229 |
+
- **Minimum Data**: Need at least 10-100 examples for meaningful fine-tuning
|
| 230 |
+
- **Data Quality**: Garbage in, garbage out - ensure high-quality training data
|
| 231 |
+
- **Cost**: Fine-tuning can be expensive, especially with large datasets
|
| 232 |
+
- **Overfitting**: Monitor for overfitting to training data
|
| 233 |
+
- **Hallucination**: Fine-tuned models may still hallucinate facts
|
| 234 |
+
- **Maintenance**: Models may need periodic retraining as data evolves
|
| 235 |
+
|
| 236 |
+
## Expected Output
|
| 237 |
+
A specialized model that performs better on your specific domain tasks compared to base models, with consistent formatting and domain-specific knowledge.
|
TRAE-Skills/ai_engineering/Fine_tuning_Basics.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Fine-tuning Basics
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To adapt a pre-trained LLM to a specific task, tone, or domain by training it on a specialized dataset, ensuring rigid adherence to format or style.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When "Prompt Engineering" fails to produce the desired format consistently.
|
| 8 |
+
- When you need to mimic a very specific brand voice or writing style.
|
| 9 |
+
- To reduce latency and costs by using a smaller model (e.g., GPT-4o-mini) that performs like a larger one on a specific task.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Data Preparation (JSONL)
|
| 14 |
+
Create a dataset in the specific format required by the provider (OpenAI example).
|
| 15 |
+
|
| 16 |
+
```json
|
| 17 |
+
// training_data.jsonl
|
| 18 |
+
{"messages": [{"role": "system", "content": "You are a code reviewer."}, {"role": "user", "content": "Review this: const x = 1;"}, {"role": "assistant", "content": "LGTM. Consider using 'let' if reassigning."}]}
|
| 19 |
+
{"messages": [{"role": "system", "content": "You are a code reviewer."}, {"role": "user", "content": "Review this: alert('hi');"}, {"role": "assistant", "content": "Avoid 'alert' in production code."}]}
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
### 2. Validation Script (Python)
|
| 23 |
+
Always validate your JSONL before uploading to avoid costly training failures.
|
| 24 |
+
|
| 25 |
+
```python
|
| 26 |
+
import json
|
| 27 |
+
|
| 28 |
+
def validate_data(file_path):
|
| 29 |
+
with open(file_path, 'r') as f:
|
| 30 |
+
for line in f:
|
| 31 |
+
try:
|
| 32 |
+
data = json.loads(line)
|
| 33 |
+
if "messages" not in data:
|
| 34 |
+
print("Missing 'messages' key")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"Error parsing line: {e}")
|
| 37 |
+
|
| 38 |
+
validate_data("training_data.jsonl")
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### 3. Starting the Fine-tuning Job (OpenAI CLI)
|
| 42 |
+
Upload the file and start the training process.
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
# Install CLI
|
| 46 |
+
pip install openai
|
| 47 |
+
|
| 48 |
+
# Set Key
|
| 49 |
+
export OPENAI_API_KEY="your-key"
|
| 50 |
+
|
| 51 |
+
# Upload file
|
| 52 |
+
openai files create -f training_data.jsonl -p fine-tune
|
| 53 |
+
|
| 54 |
+
# Start training
|
| 55 |
+
openai fine_tuning.jobs.create -t "file-id-from-upload" -m "gpt-4o-mini-2024-07-18"
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### 4. Monitoring & Evaluation
|
| 59 |
+
Check the status and loss metrics.
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
# List jobs
|
| 63 |
+
openai fine_tuning.jobs.list
|
| 64 |
+
|
| 65 |
+
# Retrieve status
|
| 66 |
+
openai fine_tuning.jobs.retrieve -i "ft-job-id"
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 5. Using the Fine-tuned Model
|
| 70 |
+
Once completed, use the new model ID in your application.
|
| 71 |
+
|
| 72 |
+
```typescript
|
| 73 |
+
const completion = await openai.chat.completions.create({
|
| 74 |
+
model: "ft:gpt-4o-mini:your-org:custom-name:id",
|
| 75 |
+
messages: [{ role: "user", content: "Review this: console.log(1);" }],
|
| 76 |
+
});
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Constraints
|
| 80 |
+
- **Overfitting**: Don't train for too many epochs; the model will lose its general intelligence.
|
| 81 |
+
- **Facts vs Style**: **Never** fine-tune to teach new facts. Use RAG for facts. Use fine-tuning for **how** the model speaks.
|
| 82 |
+
- **Minimum Data**: You need at least 50-100 high-quality examples to see any meaningful improvement.
|
| 83 |
+
|
| 84 |
+
## Expected Output
|
| 85 |
+
A specialized model ID that delivers high-performance results on a specific, narrow task.
|
TRAE-Skills/ai_engineering/Function_Calling.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Function Calling
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To enable LLMs to interact with external tools, APIs, and databases by defining structured function specifications that models can intelligently invoke.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building AI assistants that need to perform actions
|
| 8 |
+
- When integrating LLMs with existing APIs
|
| 9 |
+
- When requiring structured data extraction
|
| 10 |
+
- When implementing multi-step workflows with external systems
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Define Function Schemas
|
| 15 |
+
Create structured function definitions with clear descriptions.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
|
| 20 |
+
client = OpenAI()
|
| 21 |
+
|
| 22 |
+
# Define available functions
|
| 23 |
+
functions = [
|
| 24 |
+
{
|
| 25 |
+
"name": "get_weather",
|
| 26 |
+
"description": "Get the current weather for a specific location",
|
| 27 |
+
"parameters": {
|
| 28 |
+
"type": "object",
|
| 29 |
+
"properties": {
|
| 30 |
+
"location": {
|
| 31 |
+
"type": "string",
|
| 32 |
+
"description": "The city and state, e.g. San Francisco, CA"
|
| 33 |
+
},
|
| 34 |
+
"unit": {
|
| 35 |
+
"type": "string",
|
| 36 |
+
"enum": ["celsius", "fahrenheit"],
|
| 37 |
+
"description": "The temperature unit"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"required": ["location"]
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"name": "search_database",
|
| 45 |
+
"description": "Search a database for specific records",
|
| 46 |
+
"parameters": {
|
| 47 |
+
"type": "object",
|
| 48 |
+
"properties": {
|
| 49 |
+
"query": {
|
| 50 |
+
"type": "string",
|
| 51 |
+
"description": "The search query"
|
| 52 |
+
},
|
| 53 |
+
"limit": {
|
| 54 |
+
"type": "integer",
|
| 55 |
+
"description": "Maximum number of results to return"
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
"required": ["query"]
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
]
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### 2. Implement the Functions
|
| 65 |
+
Create actual implementations of your defined functions.
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
import requests
|
| 69 |
+
|
| 70 |
+
def get_weather(location, unit="celsius"):
|
| 71 |
+
"""Get weather data for a location."""
|
| 72 |
+
# Example using a weather API
|
| 73 |
+
api_url = f"https://api.weatherapi.com/v1/current.json"
|
| 74 |
+
params = {
|
| 75 |
+
"key": "your-api-key",
|
| 76 |
+
"q": location,
|
| 77 |
+
"aqi": "no"
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
response = requests.get(api_url, params=params)
|
| 82 |
+
data = response.json()
|
| 83 |
+
|
| 84 |
+
temp = data['current']['temp_c'] if unit == "celsius" else data['current']['temp_f']
|
| 85 |
+
condition = data['current']['condition']['text']
|
| 86 |
+
|
| 87 |
+
return {
|
| 88 |
+
"location": location,
|
| 89 |
+
"temperature": temp,
|
| 90 |
+
"unit": unit,
|
| 91 |
+
"condition": condition
|
| 92 |
+
}
|
| 93 |
+
except Exception as e:
|
| 94 |
+
return {"error": str(e)}
|
| 95 |
+
|
| 96 |
+
def search_database(query, limit=10):
|
| 97 |
+
"""Search database for records."""
|
| 98 |
+
# Example database query
|
| 99 |
+
# In production, use your actual database connection
|
| 100 |
+
results = [
|
| 101 |
+
{"id": 1, "title": f"Result for {query}", "content": "..."},
|
| 102 |
+
{"id": 2, "title": f"Another result for {query}", "content": "..."}
|
| 103 |
+
]
|
| 104 |
+
return results[:limit]
|
| 105 |
+
|
| 106 |
+
# Map function names to implementations
|
| 107 |
+
function_map = {
|
| 108 |
+
"get_weather": get_weather,
|
| 109 |
+
"search_database": search_database
|
| 110 |
+
}
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### 3. Handle Function Calls
|
| 114 |
+
Process LLM responses that request function calls.
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
def execute_function_call(function_call):
|
| 118 |
+
"""Execute a function call from the LLM."""
|
| 119 |
+
function_name = function_call.name
|
| 120 |
+
function_args = json.loads(function_call.arguments)
|
| 121 |
+
|
| 122 |
+
print(f"Calling function: {function_name}")
|
| 123 |
+
print(f"Arguments: {function_args}")
|
| 124 |
+
|
| 125 |
+
if function_name in function_map:
|
| 126 |
+
function_to_call = function_map[function_name]
|
| 127 |
+
return function_to_call(**function_args)
|
| 128 |
+
else:
|
| 129 |
+
return f"Error: Function {function_name} not found"
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### 4. Complete Conversation Flow
|
| 133 |
+
Implement the full interaction loop.
|
| 134 |
+
|
| 135 |
+
```python
|
| 136 |
+
def chat_with_functions(user_message, messages_history=None):
|
| 137 |
+
"""Handle conversation with function calling."""
|
| 138 |
+
if messages_history is None:
|
| 139 |
+
messages_history = []
|
| 140 |
+
|
| 141 |
+
# Add user message to history
|
| 142 |
+
messages_history.append({"role": "user", "content": user_message})
|
| 143 |
+
|
| 144 |
+
# Make API call with functions
|
| 145 |
+
response = client.chat.completions.create(
|
| 146 |
+
model="gpt-4",
|
| 147 |
+
messages=messages_history,
|
| 148 |
+
functions=functions,
|
| 149 |
+
function_call="auto" # Let model decide whether to call functions
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
response_message = response.choices[0].message
|
| 153 |
+
|
| 154 |
+
# Check if model wants to call a function
|
| 155 |
+
if response_message.function_call:
|
| 156 |
+
print(f"Model wants to call: {response_message.function_call.name}")
|
| 157 |
+
|
| 158 |
+
# Execute function call
|
| 159 |
+
function_response = execute_function_call(response_message.function_call)
|
| 160 |
+
|
| 161 |
+
# Add function response to conversation
|
| 162 |
+
messages_history.append(response_message) # Assistant message with function call
|
| 163 |
+
messages_history.append({
|
| 164 |
+
"role": "function",
|
| 165 |
+
"name": response_message.function_call.name,
|
| 166 |
+
"content": json.dumps(function_response)
|
| 167 |
+
})
|
| 168 |
+
|
| 169 |
+
# Get final response from model
|
| 170 |
+
second_response = client.chat.completions.create(
|
| 171 |
+
model="gpt-4",
|
| 172 |
+
messages=messages_history
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
return second_response.choices[0].message.content
|
| 176 |
+
else:
|
| 177 |
+
return response_message.content
|
| 178 |
+
|
| 179 |
+
# Usage
|
| 180 |
+
messages = []
|
| 181 |
+
while True:
|
| 182 |
+
user_input = input("You: ")
|
| 183 |
+
if user_input.lower() in ['quit', 'exit']:
|
| 184 |
+
break
|
| 185 |
+
|
| 186 |
+
response = chat_with_functions(user_input, messages)
|
| 187 |
+
print(f"Assistant: {response}")
|
| 188 |
+
messages.append({"role": "assistant", "content": response})
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
### 5. Advanced: Parallel Function Calls
|
| 192 |
+
Handle multiple function calls in a single request.
|
| 193 |
+
|
| 194 |
+
```python
|
| 195 |
+
def handle_parallel_function_calls(response_message, messages_history):
|
| 196 |
+
"""Handle multiple function calls from the model."""
|
| 197 |
+
# Some models can request multiple function calls at once
|
| 198 |
+
if hasattr(response_message, 'function_calls'):
|
| 199 |
+
function_calls = response_message.function_calls
|
| 200 |
+
else:
|
| 201 |
+
# Single function call (convert to list for uniform handling)
|
| 202 |
+
if response_message.function_call:
|
| 203 |
+
function_calls = [response_message.function_call]
|
| 204 |
+
else:
|
| 205 |
+
return messages_history
|
| 206 |
+
|
| 207 |
+
# Add assistant message with function calls
|
| 208 |
+
messages_history.append(response_message)
|
| 209 |
+
|
| 210 |
+
# Execute all function calls
|
| 211 |
+
for function_call in function_calls:
|
| 212 |
+
function_response = execute_function_call(function_call)
|
| 213 |
+
|
| 214 |
+
# Add each function response
|
| 215 |
+
messages_history.append({
|
| 216 |
+
"role": "function",
|
| 217 |
+
"name": function_call.name,
|
| 218 |
+
"content": json.dumps(function_response)
|
| 219 |
+
})
|
| 220 |
+
|
| 221 |
+
return messages_history
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
### 6. Error Handling and Validation
|
| 225 |
+
Add robust error handling for function calls.
|
| 226 |
+
|
| 227 |
+
```python
|
| 228 |
+
def safe_execute_function_call(function_call):
|
| 229 |
+
"""Execute function call with error handling."""
|
| 230 |
+
function_name = function_call.name
|
| 231 |
+
|
| 232 |
+
try:
|
| 233 |
+
function_args = json.loads(function_call.arguments)
|
| 234 |
+
except json.JSONDecodeError as e:
|
| 235 |
+
return {
|
| 236 |
+
"error": f"Invalid JSON in arguments: {str(e)}",
|
| 237 |
+
"arguments": function_call.arguments
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
# Validate function exists
|
| 241 |
+
if function_name not in function_map:
|
| 242 |
+
return {
|
| 243 |
+
"error": f"Unknown function: {function_name}",
|
| 244 |
+
"available_functions": list(function_map.keys())
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
# Validate required parameters
|
| 248 |
+
function_schema = next((f for f in functions if f["name"] == function_name), None)
|
| 249 |
+
if function_schema:
|
| 250 |
+
required_params = function_schema["parameters"].get("required", [])
|
| 251 |
+
missing_params = [p for p in required_params if p not in function_args]
|
| 252 |
+
|
| 253 |
+
if missing_params:
|
| 254 |
+
return {
|
| 255 |
+
"error": f"Missing required parameters: {', '.join(missing_params)}",
|
| 256 |
+
"provided": list(function_args.keys())
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
# Execute function
|
| 260 |
+
try:
|
| 261 |
+
function_to_call = function_map[function_name]
|
| 262 |
+
return function_to_call(**function_args)
|
| 263 |
+
except Exception as e:
|
| 264 |
+
return {
|
| 265 |
+
"error": f"Function execution failed: {str(e)}",
|
| 266 |
+
"function": function_name,
|
| 267 |
+
"arguments": function_args
|
| 268 |
+
}
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
## Constraints
|
| 272 |
+
- **Function Descriptions**: Clear, detailed descriptions are crucial for model performance
|
| 273 |
+
- **Parameter Validation**: Always validate function arguments before execution
|
| 274 |
+
- **Error Handling**: Gracefully handle function failures and API errors
|
| 275 |
+
- **Security**: Validate and sanitize all inputs, especially for database operations
|
| 276 |
+
- **Rate Limiting**: Implement rate limits for external API calls
|
| 277 |
+
- **Token Limits**: Function calls consume tokens - consider size of function schemas and responses
|
| 278 |
+
|
| 279 |
+
## Expected Output
|
| 280 |
+
An intelligent AI assistant capable of understanding user intent and automatically executing appropriate functions to fulfill requests, with proper error handling and response formatting.
|
TRAE-Skills/ai_engineering/Generative_AI_Image_Synthesis.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Generative AI Image Synthesis
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To create and integrate generative image AI models (Stable Diffusion, DALL-E, Midjourney API) into applications for generating, editing, and manipulating images programmatically.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building applications that require on-demand image generation from text prompts
|
| 8 |
+
- For creating custom image editing tools using AI inpainting/outpainting
|
| 9 |
+
- When implementing AI-powered design tools for creative industries
|
| 10 |
+
- For generating synthetic datasets for computer vision tasks
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Stable Diffusion Integration (Hugging Face)
|
| 15 |
+
Use the Hugging Face Diffusers library for local Stable Diffusion generation.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from diffusers import StableDiffusionPipeline
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
model_id = "runwayml/stable-diffusion-v1-5"
|
| 22 |
+
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
|
| 23 |
+
pipe = pipe.to("cuda")
|
| 24 |
+
|
| 25 |
+
prompt = "A majestic lion standing on a cliff at sunset, photorealistic"
|
| 26 |
+
image = pipe(prompt).images[0]
|
| 27 |
+
image.save("lion_sunset.png")
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### 2. OpenAI DALL-E API Integration
|
| 31 |
+
Use OpenAI's API for cloud-based image generation.
|
| 32 |
+
|
| 33 |
+
```javascript
|
| 34 |
+
import OpenAI from 'openai';
|
| 35 |
+
|
| 36 |
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
| 37 |
+
|
| 38 |
+
async function generateImage(prompt) {
|
| 39 |
+
const response = await openai.images.generate({
|
| 40 |
+
model: 'dall-e-3',
|
| 41 |
+
prompt: prompt,
|
| 42 |
+
n: 1,
|
| 43 |
+
size: '1024x1024',
|
| 44 |
+
quality: 'standard',
|
| 45 |
+
});
|
| 46 |
+
return response.data[0].url;
|
| 47 |
+
}
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 3. Inpainting for Image Editing
|
| 51 |
+
Modify specific regions of existing images.
|
| 52 |
+
|
| 53 |
+
```python
|
| 54 |
+
from diffusers import StableDiffusionInpaintPipeline
|
| 55 |
+
from PIL import Image
|
| 56 |
+
import torch
|
| 57 |
+
|
| 58 |
+
pipe = StableDiffusionInpaintPipeline.from_pretrained(
|
| 59 |
+
"runwayml/stable-diffusion-inpainting",
|
| 60 |
+
torch_dtype=torch.float16
|
| 61 |
+
).to("cuda")
|
| 62 |
+
|
| 63 |
+
image = Image.open("original_image.jpg")
|
| 64 |
+
mask = Image.open("mask_image.png")
|
| 65 |
+
|
| 66 |
+
prompt = "A cute dog sitting on the couch"
|
| 67 |
+
result = pipe(prompt=prompt, image=image, mask_image=mask).images[0]
|
| 68 |
+
result.save("edited_image.jpg")
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 4. Image-to-Image Translation
|
| 72 |
+
Transform existing images based on text prompts.
|
| 73 |
+
|
| 74 |
+
```python
|
| 75 |
+
from diffusers import StableDiffusionImg2ImgPipeline
|
| 76 |
+
from PIL import Image
|
| 77 |
+
import torch
|
| 78 |
+
|
| 79 |
+
pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
|
| 80 |
+
"runwayml/stable-diffusion-v1-5",
|
| 81 |
+
torch_dtype=torch.float16
|
| 82 |
+
).to("cuda")
|
| 83 |
+
|
| 84 |
+
init_image = Image.open("sketch.jpg").convert("RGB")
|
| 85 |
+
prompt = "A realistic watercolor painting of a mountain landscape"
|
| 86 |
+
|
| 87 |
+
result = pipe(prompt=prompt, image=init_image, strength=0.75, guidance_scale=7.5).images[0]
|
| 88 |
+
result.save("watercolor_mountain.jpg")
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
## Best Practices
|
| 92 |
+
- **Prompt Engineering**: Be specific about style, lighting, composition, and artists for better results
|
| 93 |
+
- **Memory Management**: Use float16 and model quantization to reduce GPU memory usage
|
| 94 |
+
- **Caching**: Cache frequently used generated images to reduce API costs
|
| 95 |
+
- **Content Moderation**: Implement safety filters to prevent generation of inappropriate content
|
| 96 |
+
- **Rate Limiting**: Respect API rate limits and implement retry logic with exponential backoff
|
TRAE-Skills/ai_engineering/LLM_Caching_Strategies.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: LLM Caching Strategies
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To reduce API costs and latency by caching LLM responses and avoiding redundant calls for identical or similar prompts.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When users frequently ask similar questions
|
| 8 |
+
- When implementing RAG systems with recurring queries
|
| 9 |
+
- When building applications with repetitive patterns
|
| 10 |
+
- When optimizing for cost reduction
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Simple Exact Match Caching
|
| 15 |
+
Cache responses based on exact prompt matches.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import hashlib
|
| 19 |
+
import json
|
| 20 |
+
from functools import wraps
|
| 21 |
+
from openai import OpenAI
|
| 22 |
+
|
| 23 |
+
client = OpenAI()
|
| 24 |
+
|
| 25 |
+
# Simple in-memory cache
|
| 26 |
+
response_cache = {}
|
| 27 |
+
|
| 28 |
+
def cache_key(prompt, model, temperature=0):
|
| 29 |
+
"""Generate a unique cache key."""
|
| 30 |
+
content = f"{model}:{temperature}:{prompt}"
|
| 31 |
+
return hashlib.sha256(content.encode()).hexdigest()
|
| 32 |
+
|
| 33 |
+
def cached_completion(prompt, model="gpt-4", temperature=0):
|
| 34 |
+
"""Get completion with caching."""
|
| 35 |
+
key = cache_key(prompt, model, temperature)
|
| 36 |
+
|
| 37 |
+
if key in response_cache:
|
| 38 |
+
print("Cache hit!")
|
| 39 |
+
return response_cache[key]
|
| 40 |
+
|
| 41 |
+
print("Cache miss - calling API...")
|
| 42 |
+
response = client.chat.completions.create(
|
| 43 |
+
model=model,
|
| 44 |
+
messages=[{"role": "user", "content": prompt}],
|
| 45 |
+
temperature=temperature
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
result = response.choices[0].message.content
|
| 49 |
+
response_cache[key] = result
|
| 50 |
+
return result
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### 2. Semantic Caching with Embeddings
|
| 54 |
+
Cache responses based on semantic similarity.
|
| 55 |
+
|
| 56 |
+
```python
|
| 57 |
+
import numpy as np
|
| 58 |
+
from sentence_transformers import SentenceTransformer
|
| 59 |
+
|
| 60 |
+
class SemanticCache:
|
| 61 |
+
def __init__(self, similarity_threshold=0.85):
|
| 62 |
+
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
|
| 63 |
+
self.prompts = []
|
| 64 |
+
self.responses = []
|
| 65 |
+
self.embeddings = []
|
| 66 |
+
self.threshold = similarity_threshold
|
| 67 |
+
|
| 68 |
+
def get(self, prompt):
|
| 69 |
+
"""Try to get a cached response based on semantic similarity."""
|
| 70 |
+
if not self.prompts:
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
+
# Embed the query prompt
|
| 74 |
+
query_embedding = self.embedder.encode([prompt])[0]
|
| 75 |
+
|
| 76 |
+
# Calculate similarities with cached prompts
|
| 77 |
+
cached_embeddings = np.array(self.embeddings)
|
| 78 |
+
similarities = np.dot(cached_embeddings, query_embedding) / (
|
| 79 |
+
np.linalg.norm(cached_embeddings, axis=1) * np.linalg.norm(query_embedding)
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# Find the most similar cached prompt
|
| 83 |
+
max_idx = np.argmax(similarities)
|
| 84 |
+
max_similarity = similarities[max_idx]
|
| 85 |
+
|
| 86 |
+
if max_similarity >= self.threshold:
|
| 87 |
+
print(f"Semantic cache hit! Similarity: {max_similarity:.3f}")
|
| 88 |
+
return self.responses[max_idx]
|
| 89 |
+
|
| 90 |
+
print(f"No semantic match found. Max similarity: {max_similarity:.3f}")
|
| 91 |
+
return None
|
| 92 |
+
|
| 93 |
+
def set(self, prompt, response):
|
| 94 |
+
"""Store a new prompt-response pair."""
|
| 95 |
+
self.prompts.append(prompt)
|
| 96 |
+
self.responses.append(response)
|
| 97 |
+
self.embeddings.append(self.embedder.encode([prompt])[0])
|
| 98 |
+
|
| 99 |
+
# Usage
|
| 100 |
+
semantic_cache = SemanticCache(similarity_threshold=0.90)
|
| 101 |
+
|
| 102 |
+
def get_response_with_semantic_cache(prompt):
|
| 103 |
+
# Check cache first
|
| 104 |
+
cached = semantic_cache.get(prompt)
|
| 105 |
+
if cached:
|
| 106 |
+
return cached
|
| 107 |
+
|
| 108 |
+
# Call API and cache the result
|
| 109 |
+
response = client.chat.completions.create(
|
| 110 |
+
model="gpt-4",
|
| 111 |
+
messages=[{"role": "user", "content": prompt}]
|
| 112 |
+
)
|
| 113 |
+
result = response.choices[0].message.content
|
| 114 |
+
|
| 115 |
+
semantic_cache.set(prompt, result)
|
| 116 |
+
return result
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
### 3. Persistent Caching with Redis
|
| 120 |
+
Store cache entries in Redis for persistence across restarts.
|
| 121 |
+
|
| 122 |
+
```python
|
| 123 |
+
import redis
|
| 124 |
+
import pickle
|
| 125 |
+
import hashlib
|
| 126 |
+
|
| 127 |
+
redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
| 128 |
+
|
| 129 |
+
def redis_cache_key(prompt, model="gpt-4"):
|
| 130 |
+
"""Generate Redis cache key."""
|
| 131 |
+
content = f"llm_cache:{model}:{hashlib.sha256(prompt.encode()).hexdigest()}"
|
| 132 |
+
return content
|
| 133 |
+
|
| 134 |
+
def get_with_redis_cache(prompt, model="gpt-4", expire_hours=24):
|
| 135 |
+
"""Get response with Redis caching."""
|
| 136 |
+
key = redis_cache_key(prompt, model)
|
| 137 |
+
|
| 138 |
+
# Try to get from Redis
|
| 139 |
+
cached = redis_client.get(key)
|
| 140 |
+
if cached:
|
| 141 |
+
print("Redis cache hit!")
|
| 142 |
+
return pickle.loads(cached)
|
| 143 |
+
|
| 144 |
+
print("Redis cache miss - calling API...")
|
| 145 |
+
response = client.chat.completions.create(
|
| 146 |
+
model=model,
|
| 147 |
+
messages=[{"role": "user", "content": prompt}]
|
| 148 |
+
)
|
| 149 |
+
result = response.choices[0].message.content
|
| 150 |
+
|
| 151 |
+
# Store in Redis with expiration
|
| 152 |
+
redis_client.setex(key, expire_hours * 3600, pickle.dumps(result))
|
| 153 |
+
return result
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
### 4. Hierarchical Caching
|
| 157 |
+
Combine multiple caching strategies for optimal performance.
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
class HierarchicalCache:
|
| 161 |
+
def __init__(self):
|
| 162 |
+
self.memory_cache = {} # L1: In-memory cache
|
| 163 |
+
self.semantic_cache = SemanticCache() # L2: Semantic cache
|
| 164 |
+
self.redis_client = redis.Redis() # L3: Persistent cache
|
| 165 |
+
|
| 166 |
+
def get(self, prompt, model="gpt-4"):
|
| 167 |
+
# Check L1: Exact match in memory
|
| 168 |
+
key = cache_key(prompt, model)
|
| 169 |
+
if key in self.memory_cache:
|
| 170 |
+
return self.memory_cache[key]
|
| 171 |
+
|
| 172 |
+
# Check L2: Semantic match
|
| 173 |
+
semantic_result = self.semantic_cache.get(prompt)
|
| 174 |
+
if semantic_result:
|
| 175 |
+
self.memory_cache[key] = semantic_result
|
| 176 |
+
return semantic_result
|
| 177 |
+
|
| 178 |
+
# Check L3: Redis persistent cache
|
| 179 |
+
redis_key = redis_cache_key(prompt, model)
|
| 180 |
+
redis_result = self.redis_client.get(redis_key)
|
| 181 |
+
if redis_result:
|
| 182 |
+
result = pickle.loads(redis_result)
|
| 183 |
+
self.memory_cache[key] = result
|
| 184 |
+
self.semantic_cache.set(prompt, result)
|
| 185 |
+
return result
|
| 186 |
+
|
| 187 |
+
# Cache miss - call API
|
| 188 |
+
response = client.chat.completions.create(
|
| 189 |
+
model=model,
|
| 190 |
+
messages=[{"role": "user", "content": prompt}]
|
| 191 |
+
)
|
| 192 |
+
result = response.choices[0].message.content
|
| 193 |
+
|
| 194 |
+
# Store at all levels
|
| 195 |
+
self.memory_cache[key] = result
|
| 196 |
+
self.semantic_cache.set(prompt, result)
|
| 197 |
+
self.redis_client.setex(redis_key, 86400, pickle.dumps(result))
|
| 198 |
+
|
| 199 |
+
return result
|
| 200 |
+
|
| 201 |
+
# Usage
|
| 202 |
+
hierarchical_cache = HierarchicalCache()
|
| 203 |
+
response = hierarchical_cache.get("Explain quantum computing")
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### 5. Cache Statistics and Monitoring
|
| 207 |
+
Track cache performance to optimize strategies.
|
| 208 |
+
|
| 209 |
+
```python
|
| 210 |
+
from collections import defaultdict
|
| 211 |
+
|
| 212 |
+
class CacheWithStats:
|
| 213 |
+
def __init__(self):
|
| 214 |
+
self.cache = {}
|
| 215 |
+
self.stats = defaultdict(int)
|
| 216 |
+
|
| 217 |
+
def get(self, key):
|
| 218 |
+
self.stats['total_requests'] += 1
|
| 219 |
+
|
| 220 |
+
if key in self.cache:
|
| 221 |
+
self.stats['cache_hits'] += 1
|
| 222 |
+
self.stats['memory_cache_hits'] += 1
|
| 223 |
+
return self.cache[key]
|
| 224 |
+
|
| 225 |
+
self.stats['cache_misses'] += 1
|
| 226 |
+
return None
|
| 227 |
+
|
| 228 |
+
def set(self, key, value):
|
| 229 |
+
self.cache[key] = value
|
| 230 |
+
self.stats['items_cached'] += 1
|
| 231 |
+
|
| 232 |
+
def get_stats(self):
|
| 233 |
+
total = self.stats['total_requests']
|
| 234 |
+
hits = self.stats['cache_hits']
|
| 235 |
+
hit_rate = (hits / total * 100) if total > 0 else 0
|
| 236 |
+
|
| 237 |
+
return {
|
| 238 |
+
'total_requests': total,
|
| 239 |
+
'cache_hits': hits,
|
| 240 |
+
'cache_misses': self.stats['cache_misses'],
|
| 241 |
+
'hit_rate': f"{hit_rate:.2f}%",
|
| 242 |
+
'items_cached': self.stats['items_cached']
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
# Usage
|
| 246 |
+
cache = CacheWithStats()
|
| 247 |
+
# ... perform operations ...
|
| 248 |
+
print(cache.get_stats())
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
## Constraints
|
| 252 |
+
- **Memory Usage**: Semantic caching stores embeddings in memory
|
| 253 |
+
- **Staleness**: Cached responses may become outdated
|
| 254 |
+
- **Similarity Threshold**: Tune based on your use case (0.85-0.95)
|
| 255 |
+
- **Cache Size**: Implement cache eviction policies for long-running systems
|
| 256 |
+
- **Cost vs. Freshness**: Balance between caching and getting fresh responses
|
| 257 |
+
|
| 258 |
+
## Expected Output
|
| 259 |
+
Significant reduction in API costs (50-90% in some cases) and improved latency through intelligent caching of LLM responses.
|
TRAE-Skills/ai_engineering/LLM_Function_Calling_Advanced.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Advanced LLM Function Calling
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To implement advanced function calling patterns with Large Language Models, enabling them to interact with external APIs, databases, and tools in a structured, safe way.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building AI agents that need to take real-world actions
|
| 8 |
+
- For integrating LLMs with existing APIs and services
|
| 9 |
+
- When you need structured, deterministic outputs from LLMs
|
| 10 |
+
- For building copilots and assistant applications
|
| 11 |
+
- When implementing multi-step reasoning and tool use
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Function Definition Schema
|
| 16 |
+
Define functions with clear descriptions and schemas.
|
| 17 |
+
|
| 18 |
+
```javascript
|
| 19 |
+
import OpenAI from 'openai';
|
| 20 |
+
|
| 21 |
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
| 22 |
+
|
| 23 |
+
const functions = [
|
| 24 |
+
{
|
| 25 |
+
name: 'get_weather',
|
| 26 |
+
description: 'Get the current weather in a given location',
|
| 27 |
+
parameters: {
|
| 28 |
+
type: 'object',
|
| 29 |
+
properties: {
|
| 30 |
+
location: {
|
| 31 |
+
type: 'string',
|
| 32 |
+
description: 'The city and state, e.g., San Francisco, CA',
|
| 33 |
+
},
|
| 34 |
+
unit: {
|
| 35 |
+
type: 'string',
|
| 36 |
+
enum: ['celsius', 'fahrenheit'],
|
| 37 |
+
description: 'The temperature unit to use',
|
| 38 |
+
},
|
| 39 |
+
},
|
| 40 |
+
required: ['location'],
|
| 41 |
+
},
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
name: 'search_products',
|
| 45 |
+
description: 'Search for products in the catalog',
|
| 46 |
+
parameters: {
|
| 47 |
+
type: 'object',
|
| 48 |
+
properties: {
|
| 49 |
+
query: {
|
| 50 |
+
type: 'string',
|
| 51 |
+
description: 'The search query',
|
| 52 |
+
},
|
| 53 |
+
category: {
|
| 54 |
+
type: 'string',
|
| 55 |
+
enum: ['electronics', 'clothing', 'books'],
|
| 56 |
+
description: 'Product category',
|
| 57 |
+
},
|
| 58 |
+
max_results: {
|
| 59 |
+
type: 'integer',
|
| 60 |
+
description: 'Maximum number of results to return',
|
| 61 |
+
},
|
| 62 |
+
},
|
| 63 |
+
required: ['query'],
|
| 64 |
+
},
|
| 65 |
+
},
|
| 66 |
+
];
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 2. Function Calling Loop
|
| 70 |
+
Implement a loop to handle multiple function calls.
|
| 71 |
+
|
| 72 |
+
```javascript
|
| 73 |
+
async function chatWithFunctions(messages) {
|
| 74 |
+
let response = await openai.chat.completions.create({
|
| 75 |
+
model: 'gpt-4',
|
| 76 |
+
messages,
|
| 77 |
+
functions,
|
| 78 |
+
function_call: 'auto',
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
let message = response.choices[0].message;
|
| 82 |
+
|
| 83 |
+
while (message.function_call) {
|
| 84 |
+
const functionName = message.function_call.name;
|
| 85 |
+
const functionArgs = JSON.parse(message.function_call.arguments);
|
| 86 |
+
|
| 87 |
+
// Execute the function
|
| 88 |
+
let functionResponse;
|
| 89 |
+
switch (functionName) {
|
| 90 |
+
case 'get_weather':
|
| 91 |
+
functionResponse = await getWeather(functionArgs);
|
| 92 |
+
break;
|
| 93 |
+
case 'search_products':
|
| 94 |
+
functionResponse = await searchProducts(functionArgs);
|
| 95 |
+
break;
|
| 96 |
+
default:
|
| 97 |
+
functionResponse = { error: `Unknown function: ${functionName}` };
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// Add the function response to messages
|
| 101 |
+
messages.push(message);
|
| 102 |
+
messages.push({
|
| 103 |
+
role: 'function',
|
| 104 |
+
name: functionName,
|
| 105 |
+
content: JSON.stringify(functionResponse),
|
| 106 |
+
});
|
| 107 |
+
|
| 108 |
+
// Get next response from LLM
|
| 109 |
+
response = await openai.chat.completions.create({
|
| 110 |
+
model: 'gpt-4',
|
| 111 |
+
messages,
|
| 112 |
+
functions,
|
| 113 |
+
function_call: 'auto',
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
message = response.choices[0].message;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
return message.content;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// Usage
|
| 123 |
+
const messages = [
|
| 124 |
+
{ role: 'user', content: 'What is the weather in New York and show me 3 electronics products?' }
|
| 125 |
+
];
|
| 126 |
+
|
| 127 |
+
const result = await chatWithFunctions(messages);
|
| 128 |
+
console.log(result);
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
### 3. Parallel Function Calling
|
| 132 |
+
Execute multiple functions in parallel.
|
| 133 |
+
|
| 134 |
+
```javascript
|
| 135 |
+
async function chatWithParallelFunctions(messages) {
|
| 136 |
+
let response = await openai.chat.completions.create({
|
| 137 |
+
model: 'gpt-4',
|
| 138 |
+
messages,
|
| 139 |
+
functions,
|
| 140 |
+
function_call: 'auto',
|
| 141 |
+
});
|
| 142 |
+
|
| 143 |
+
let message = response.choices[0].message;
|
| 144 |
+
|
| 145 |
+
while (message.function_call || message.tool_calls) {
|
| 146 |
+
const calls = message.tool_calls || [message.function_call];
|
| 147 |
+
|
| 148 |
+
// Execute all functions in parallel
|
| 149 |
+
const functionResponses = await Promise.all(
|
| 150 |
+
calls.map(async (call) => {
|
| 151 |
+
const funcCall = call.type === 'function' ? call.function : call;
|
| 152 |
+
const name = funcCall.name;
|
| 153 |
+
const args = JSON.parse(funcCall.arguments);
|
| 154 |
+
|
| 155 |
+
let result;
|
| 156 |
+
switch (name) {
|
| 157 |
+
case 'get_weather':
|
| 158 |
+
result = await getWeather(args);
|
| 159 |
+
break;
|
| 160 |
+
case 'search_products':
|
| 161 |
+
result = await searchProducts(args);
|
| 162 |
+
break;
|
| 163 |
+
default:
|
| 164 |
+
result = { error: `Unknown function: ${name}` };
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
return {
|
| 168 |
+
id: call.id,
|
| 169 |
+
role: 'tool',
|
| 170 |
+
name: name,
|
| 171 |
+
content: JSON.stringify(result),
|
| 172 |
+
};
|
| 173 |
+
})
|
| 174 |
+
);
|
| 175 |
+
|
| 176 |
+
messages.push(message);
|
| 177 |
+
messages.push(...functionResponses);
|
| 178 |
+
|
| 179 |
+
response = await openai.chat.completions.create({
|
| 180 |
+
model: 'gpt-4',
|
| 181 |
+
messages,
|
| 182 |
+
functions,
|
| 183 |
+
function_call: 'auto',
|
| 184 |
+
});
|
| 185 |
+
|
| 186 |
+
message = response.choices[0].message;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
return message.content;
|
| 190 |
+
}
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
### 4. Safety & Validation
|
| 194 |
+
Validate function inputs before execution.
|
| 195 |
+
|
| 196 |
+
```javascript
|
| 197 |
+
import { z } from 'zod';
|
| 198 |
+
|
| 199 |
+
const GetWeatherSchema = z.object({
|
| 200 |
+
location: z.string().min(2),
|
| 201 |
+
unit: z.enum(['celsius', 'fahrenheit']).optional().default('celsius'),
|
| 202 |
+
});
|
| 203 |
+
|
| 204 |
+
async function getWeatherSafe(args) {
|
| 205 |
+
try {
|
| 206 |
+
const validatedArgs = GetWeatherSchema.parse(args);
|
| 207 |
+
return await getWeather(validatedArgs);
|
| 208 |
+
} catch (error) {
|
| 209 |
+
return { error: 'Invalid arguments', details: error.message };
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// Add authentication and authorization
|
| 214 |
+
async function executeFunction(name, args, userId) {
|
| 215 |
+
// Check if user has permission to use this function
|
| 216 |
+
if (!hasPermission(userId, name)) {
|
| 217 |
+
return { error: 'Permission denied' };
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
// Validate arguments
|
| 221 |
+
// Execute function
|
| 222 |
+
// Log the function call for auditing
|
| 223 |
+
logFunctionCall(userId, name, args);
|
| 224 |
+
}
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
## Best Practices
|
| 228 |
+
- **Clear Descriptions**: Write clear, detailed descriptions for functions and parameters
|
| 229 |
+
- **Input Validation**: Always validate function inputs before execution
|
| 230 |
+
- **Error Handling**: Gracefully handle errors and communicate them back to the LLM
|
| 231 |
+
- **Safety**: Implement authentication, authorization, and rate limiting
|
| 232 |
+
- **Idempotency**: Make functions idempotent when possible
|
| 233 |
+
- **Logging**: Log all function calls for debugging and auditing
|
| 234 |
+
- **Context Limit**: Be mindful of context limits and manage conversation history
|
TRAE-Skills/ai_engineering/LLM_Operations.md
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: LLM Operations (LLMOps)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To operationalize large language models in production environments with proper deployment, scaling, monitoring, and maintenance.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying LLMs to production
|
| 8 |
+
- When managing multiple LLM deployments
|
| 9 |
+
- When optimizing LLM performance and costs
|
| 10 |
+
- When implementing LLM version control and rollback
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Model Deployment Strategy
|
| 15 |
+
Implement robust deployment strategies for LLMs.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from abc import ABC, abstractmethod
|
| 19 |
+
from openai import OpenAI
|
| 20 |
+
import time
|
| 21 |
+
from functools import wraps
|
| 22 |
+
import logging
|
| 23 |
+
|
| 24 |
+
class LLMProvider(ABC):
|
| 25 |
+
"""Abstract base class for LLM providers."""
|
| 26 |
+
|
| 27 |
+
@abstractmethod
|
| 28 |
+
def generate(self, prompt, **kwargs):
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
@abstractmethod
|
| 32 |
+
def health_check(self):
|
| 33 |
+
pass
|
| 34 |
+
|
| 35 |
+
class OpenAIProvider(LLMProvider):
|
| 36 |
+
def __init__(self, api_key, model="gpt-4"):
|
| 37 |
+
self.client = OpenAI(api_key=api_key)
|
| 38 |
+
self.model = model
|
| 39 |
+
self.logger = logging.getLogger(__name__)
|
| 40 |
+
|
| 41 |
+
def generate(self, prompt, temperature=0.7, max_tokens=1000):
|
| 42 |
+
"""Generate text using OpenAI API."""
|
| 43 |
+
try:
|
| 44 |
+
start_time = time.time()
|
| 45 |
+
|
| 46 |
+
response = self.client.chat.completions.create(
|
| 47 |
+
model=self.model,
|
| 48 |
+
messages=[{"role": "user", "content": prompt}],
|
| 49 |
+
temperature=temperature,
|
| 50 |
+
max_tokens=max_tokens
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
latency = time.time() - start_time
|
| 54 |
+
|
| 55 |
+
result = {
|
| 56 |
+
'text': response.choices[0].message.content,
|
| 57 |
+
'tokens_used': response.usage.total_tokens,
|
| 58 |
+
'latency': latency,
|
| 59 |
+
'model': self.model
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
self.logger.info(f"Generated {result['tokens_used']} tokens in {latency:.2f}s")
|
| 63 |
+
return result
|
| 64 |
+
|
| 65 |
+
except Exception as e:
|
| 66 |
+
self.logger.error(f"Generation failed: {str(e)}")
|
| 67 |
+
raise
|
| 68 |
+
|
| 69 |
+
def health_check(self):
|
| 70 |
+
"""Check if the API is accessible."""
|
| 71 |
+
try:
|
| 72 |
+
response = self.client.chat.completions.create(
|
| 73 |
+
model=self.model,
|
| 74 |
+
messages=[{"role": "user", "content": "ping"}],
|
| 75 |
+
max_tokens=5
|
| 76 |
+
)
|
| 77 |
+
return {'status': 'healthy', 'model': self.model}
|
| 78 |
+
except Exception as e:
|
| 79 |
+
return {'status': 'unhealthy', 'error': str(e)}
|
| 80 |
+
|
| 81 |
+
class LocalLLMProvider(LLMProvider):
|
| 82 |
+
"""Provider for locally hosted LLMs (e.g., using Ollama or vLLM)."""
|
| 83 |
+
|
| 84 |
+
def __init__(self, endpoint, model_name):
|
| 85 |
+
self.endpoint = endpoint
|
| 86 |
+
self.model_name = model_name
|
| 87 |
+
self.logger = logging.getLogger(__name__)
|
| 88 |
+
|
| 89 |
+
def generate(self, prompt, temperature=0.7, max_tokens=1000):
|
| 90 |
+
"""Generate text using local LLM."""
|
| 91 |
+
import requests
|
| 92 |
+
|
| 93 |
+
try:
|
| 94 |
+
start_time = time.time()
|
| 95 |
+
|
| 96 |
+
response = requests.post(
|
| 97 |
+
f"{self.endpoint}/generate",
|
| 98 |
+
json={
|
| 99 |
+
"model": self.model_name,
|
| 100 |
+
"prompt": prompt,
|
| 101 |
+
"temperature": temperature,
|
| 102 |
+
"max_tokens": max_tokens
|
| 103 |
+
},
|
| 104 |
+
timeout=30
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
response.raise_for_status()
|
| 108 |
+
data = response.json()
|
| 109 |
+
|
| 110 |
+
latency = time.time() - start_time
|
| 111 |
+
|
| 112 |
+
return {
|
| 113 |
+
'text': data.get('text', ''),
|
| 114 |
+
'tokens_used': data.get('tokens_used', 0),
|
| 115 |
+
'latency': latency,
|
| 116 |
+
'model': self.model_name
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
except Exception as e:
|
| 120 |
+
self.logger.error(f"Local generation failed: {str(e)}")
|
| 121 |
+
raise
|
| 122 |
+
|
| 123 |
+
def health_check(self):
|
| 124 |
+
"""Check if local LLM is running."""
|
| 125 |
+
try:
|
| 126 |
+
import requests
|
| 127 |
+
response = requests.get(f"{self.endpoint}/health", timeout=5)
|
| 128 |
+
response.raise_for_status()
|
| 129 |
+
return {'status': 'healthy', 'model': self.model_name}
|
| 130 |
+
except Exception as e:
|
| 131 |
+
return {'status': 'unhealthy', 'error': str(e)}
|
| 132 |
+
|
| 133 |
+
class LLMOrchestrator:
|
| 134 |
+
"""Orchestrate multiple LLM providers with fallback and load balancing."""
|
| 135 |
+
|
| 136 |
+
def __init__(self, providers):
|
| 137 |
+
self.providers = providers
|
| 138 |
+
self.current_provider = 0
|
| 139 |
+
self.logger = logging.getLogger(__name__)
|
| 140 |
+
|
| 141 |
+
def generate(self, prompt, **kwargs):
|
| 142 |
+
"""Generate with automatic failover."""
|
| 143 |
+
for attempt in range(len(self.providers)):
|
| 144 |
+
provider = self.providers[self.current_provider]
|
| 145 |
+
|
| 146 |
+
try:
|
| 147 |
+
result = provider.generate(prompt, **kwargs)
|
| 148 |
+
result['provider'] = provider.__class__.__name__
|
| 149 |
+
return result
|
| 150 |
+
|
| 151 |
+
except Exception as e:
|
| 152 |
+
self.logger.warning(f"Provider {provider.__class__.__name__} failed: {str(e)}")
|
| 153 |
+
self.current_provider = (self.current_provider + 1) % len(self.providers)
|
| 154 |
+
|
| 155 |
+
raise Exception("All LLM providers failed")
|
| 156 |
+
|
| 157 |
+
def health_check(self):
|
| 158 |
+
"""Health check for all providers."""
|
| 159 |
+
health_status = {}
|
| 160 |
+
for i, provider in enumerate(self.providers):
|
| 161 |
+
health_status[f"provider_{i}"] = provider.health_check()
|
| 162 |
+
return health_status
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
### 2. Rate Limiting and Throttling
|
| 166 |
+
Implement rate limiting for API calls.
|
| 167 |
+
|
| 168 |
+
```python
|
| 169 |
+
import threading
|
| 170 |
+
import time
|
| 171 |
+
from collections import deque
|
| 172 |
+
|
| 173 |
+
class RateLimiter:
|
| 174 |
+
"""Token bucket rate limiter."""
|
| 175 |
+
|
| 176 |
+
def __init__(self, rate, burst):
|
| 177 |
+
"""
|
| 178 |
+
Args:
|
| 179 |
+
rate: Tokens per second
|
| 180 |
+
burst: Maximum burst size
|
| 181 |
+
"""
|
| 182 |
+
self.rate = rate
|
| 183 |
+
self.burst = burst
|
| 184 |
+
self.tokens = burst
|
| 185 |
+
self.last_update = time.time()
|
| 186 |
+
self.lock = threading.Lock()
|
| 187 |
+
|
| 188 |
+
def consume(self, tokens=1):
|
| 189 |
+
"""Consume tokens if available."""
|
| 190 |
+
with self.lock:
|
| 191 |
+
now = time.time()
|
| 192 |
+
elapsed = now - self.last_update
|
| 193 |
+
|
| 194 |
+
# Refill tokens based on elapsed time
|
| 195 |
+
self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
|
| 196 |
+
self.last_update = now
|
| 197 |
+
|
| 198 |
+
if self.tokens >= tokens:
|
| 199 |
+
self.tokens -= tokens
|
| 200 |
+
return True
|
| 201 |
+
else:
|
| 202 |
+
return False
|
| 203 |
+
|
| 204 |
+
def wait_for_token(self, tokens=1):
|
| 205 |
+
"""Wait until tokens are available."""
|
| 206 |
+
while not self.consume(tokens):
|
| 207 |
+
wait_time = (tokens - self.tokens) / self.rate
|
| 208 |
+
time.sleep(wait_time)
|
| 209 |
+
|
| 210 |
+
class LLMWithRateLimit:
|
| 211 |
+
"""LLM wrapper with rate limiting."""
|
| 212 |
+
|
| 213 |
+
def __init__(self, llm_provider, requests_per_second=10):
|
| 214 |
+
self.llm_provider = llm_provider
|
| 215 |
+
self.rate_limiter = RateLimiter(rate=requests_per_second, burst=20)
|
| 216 |
+
|
| 217 |
+
def generate(self, prompt, **kwargs):
|
| 218 |
+
"""Generate with rate limiting."""
|
| 219 |
+
self.rate_limiter.wait_for_token()
|
| 220 |
+
return self.llm_provider.generate(prompt, **kwargs)
|
| 221 |
+
|
| 222 |
+
def generate_batch(self, prompts, **kwargs):
|
| 223 |
+
"""Generate multiple prompts with rate limiting."""
|
| 224 |
+
results = []
|
| 225 |
+
for prompt in prompts:
|
| 226 |
+
result = self.generate(prompt, **kwargs)
|
| 227 |
+
results.append(result)
|
| 228 |
+
return results
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
### 3. Caching and Response Management
|
| 232 |
+
Implement intelligent caching for LLM responses.
|
| 233 |
+
|
| 234 |
+
```python
|
| 235 |
+
import hashlib
|
| 236 |
+
import json
|
| 237 |
+
from typing import Optional
|
| 238 |
+
import redis
|
| 239 |
+
|
| 240 |
+
class LLMCache:
|
| 241 |
+
"""Cache LLM responses."""
|
| 242 |
+
|
| 243 |
+
def __init__(self, redis_client=None, ttl=3600):
|
| 244 |
+
self.redis = redis_client
|
| 245 |
+
self.ttl = ttl
|
| 246 |
+
self.memory_cache = {}
|
| 247 |
+
|
| 248 |
+
def _generate_cache_key(self, prompt, model, **kwargs):
|
| 249 |
+
"""Generate cache key from prompt and parameters."""
|
| 250 |
+
params = f"{prompt}:{model}:{json.dumps(kwargs, sort_keys=True)}"
|
| 251 |
+
return hashlib.sha256(params.encode()).hexdigest()
|
| 252 |
+
|
| 253 |
+
def get(self, prompt, model, **kwargs) -> Optional[str]:
|
| 254 |
+
"""Get cached response."""
|
| 255 |
+
cache_key = self._generate_cache_key(prompt, model, **kwargs)
|
| 256 |
+
|
| 257 |
+
# Check memory cache first
|
| 258 |
+
if cache_key in self.memory_cache:
|
| 259 |
+
return self.memory_cache[cache_key]
|
| 260 |
+
|
| 261 |
+
# Check Redis
|
| 262 |
+
if self.redis:
|
| 263 |
+
cached = self.redis.get(cache_key)
|
| 264 |
+
if cached:
|
| 265 |
+
self.memory_cache[cache_key] = cached
|
| 266 |
+
return cached
|
| 267 |
+
|
| 268 |
+
return None
|
| 269 |
+
|
| 270 |
+
def set(self, prompt, response, model, **kwargs):
|
| 271 |
+
"""Cache response."""
|
| 272 |
+
cache_key = self._generate_cache_key(prompt, model, **kwargs)
|
| 273 |
+
|
| 274 |
+
# Store in memory
|
| 275 |
+
self.memory_cache[cache_key] = response
|
| 276 |
+
|
| 277 |
+
# Store in Redis
|
| 278 |
+
if self.redis:
|
| 279 |
+
self.redis.setex(cache_key, self.ttl, response)
|
| 280 |
+
|
| 281 |
+
class CachedLLM:
|
| 282 |
+
"""LLM with caching capability."""
|
| 283 |
+
|
| 284 |
+
def __init__(self, llm_provider, cache=None):
|
| 285 |
+
self.llm_provider = llm_provider
|
| 286 |
+
self.cache = cache or LLMCache()
|
| 287 |
+
|
| 288 |
+
def generate(self, prompt, use_cache=True, **kwargs):
|
| 289 |
+
"""Generate with optional caching."""
|
| 290 |
+
model = getattr(self.llm_provider, 'model', 'unknown')
|
| 291 |
+
|
| 292 |
+
if use_cache:
|
| 293 |
+
cached_response = self.cache.get(prompt, model, **kwargs)
|
| 294 |
+
if cached_response:
|
| 295 |
+
return {
|
| 296 |
+
'text': cached_response,
|
| 297 |
+
'cached': True,
|
| 298 |
+
'model': model
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
result = self.llm_provider.generate(prompt, **kwargs)
|
| 302 |
+
|
| 303 |
+
if use_cache:
|
| 304 |
+
self.cache.set(prompt, result['text'], model, **kwargs)
|
| 305 |
+
|
| 306 |
+
result['cached'] = False
|
| 307 |
+
return result
|
| 308 |
+
```
|
| 309 |
+
|
| 310 |
+
### 4. Monitoring and Metrics
|
| 311 |
+
Track LLM performance and usage.
|
| 312 |
+
|
| 313 |
+
```python
|
| 314 |
+
from dataclasses import dataclass
|
| 315 |
+
from typing import List
|
| 316 |
+
import statistics
|
| 317 |
+
|
| 318 |
+
@dataclass
|
| 319 |
+
class LLMCallMetrics:
|
| 320 |
+
"""Metrics for individual LLM calls."""
|
| 321 |
+
timestamp: float
|
| 322 |
+
model: str
|
| 323 |
+
tokens_used: int
|
| 324 |
+
latency: float
|
| 325 |
+
success: bool
|
| 326 |
+
error_message: str = ""
|
| 327 |
+
|
| 328 |
+
class LLMMetricsCollector:
|
| 329 |
+
"""Collect and analyze LLM metrics."""
|
| 330 |
+
|
| 331 |
+
def __init__(self, max_metrics=10000):
|
| 332 |
+
self.metrics: List[LLMCallMetrics] = []
|
| 333 |
+
self.max_metrics = max_metrics
|
| 334 |
+
|
| 335 |
+
def record_call(self, model, tokens_used, latency, success, error_message=""):
|
| 336 |
+
"""Record metrics for an LLM call."""
|
| 337 |
+
metric = LLMCallMetrics(
|
| 338 |
+
timestamp=time.time(),
|
| 339 |
+
model=model,
|
| 340 |
+
tokens_used=tokens_used,
|
| 341 |
+
latency=latency,
|
| 342 |
+
success=success,
|
| 343 |
+
error_message=error_message
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
self.metrics.append(metric)
|
| 347 |
+
|
| 348 |
+
# Keep only recent metrics
|
| 349 |
+
if len(self.metrics) > self.max_metrics:
|
| 350 |
+
self.metrics = self.metrics[-self.max_metrics:]
|
| 351 |
+
|
| 352 |
+
def get_statistics(self, model=None, time_window_seconds=None):
|
| 353 |
+
"""Get statistics for LLM calls."""
|
| 354 |
+
filtered_metrics = self.metrics
|
| 355 |
+
|
| 356 |
+
if model:
|
| 357 |
+
filtered_metrics = [m for m in filtered_metrics if m.model == model]
|
| 358 |
+
|
| 359 |
+
if time_window_seconds:
|
| 360 |
+
cutoff = time.time() - time_window_seconds
|
| 361 |
+
filtered_metrics = [m for m in filtered_metrics if m.timestamp > cutoff]
|
| 362 |
+
|
| 363 |
+
if not filtered_metrics:
|
| 364 |
+
return {'error': 'No metrics found'}
|
| 365 |
+
|
| 366 |
+
successful_calls = [m for m in filtered_metrics if m.success]
|
| 367 |
+
|
| 368 |
+
stats = {
|
| 369 |
+
'total_calls': len(filtered_metrics),
|
| 370 |
+
'successful_calls': len(successful_calls),
|
| 371 |
+
'error_rate': (len(filtered_metrics) - len(successful_calls)) / len(filtered_metrics),
|
| 372 |
+
'avg_tokens': statistics.mean([m.tokens_used for m in successful_calls]) if successful_calls else 0,
|
| 373 |
+
'avg_latency': statistics.mean([m.latency for m in successful_calls]) if successful_calls else 0,
|
| 374 |
+
'p50_latency': statistics.median([m.latency for m in successful_calls]) if successful_calls else 0,
|
| 375 |
+
'p95_latency': statistics.quantiles([m.latency for m in successful_calls], n=20)[18] if len(successful_calls) > 20 else 0,
|
| 376 |
+
'total_tokens': sum([m.tokens_used for m in successful_calls])
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
return stats
|
| 380 |
+
|
| 381 |
+
class InstrumentedLLM:
|
| 382 |
+
"""LLM with automatic metrics collection."""
|
| 383 |
+
|
| 384 |
+
def __init__(self, llm_provider, metrics_collector):
|
| 385 |
+
self.llm_provider = llm_provider
|
| 386 |
+
self.metrics_collector = metrics_collector
|
| 387 |
+
|
| 388 |
+
def generate(self, prompt, **kwargs):
|
| 389 |
+
"""Generate with metrics collection."""
|
| 390 |
+
model = getattr(self.llm_provider, 'model', 'unknown')
|
| 391 |
+
start_time = time.time()
|
| 392 |
+
|
| 393 |
+
try:
|
| 394 |
+
result = self.llm_provider.generate(prompt, **kwargs)
|
| 395 |
+
latency = time.time() - start_time
|
| 396 |
+
|
| 397 |
+
self.metrics_collector.record_call(
|
| 398 |
+
model=model,
|
| 399 |
+
tokens_used=result.get('tokens_used', 0),
|
| 400 |
+
latency=latency,
|
| 401 |
+
success=True
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
return result
|
| 405 |
+
|
| 406 |
+
except Exception as e:
|
| 407 |
+
latency = time.time() - start_time
|
| 408 |
+
self.metrics_collector.record_call(
|
| 409 |
+
model=model,
|
| 410 |
+
tokens_used=0,
|
| 411 |
+
latency=latency,
|
| 412 |
+
success=False,
|
| 413 |
+
error_message=str(e)
|
| 414 |
+
)
|
| 415 |
+
raise
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
### 5. Deployment Configuration
|
| 419 |
+
Manage deployment configurations.
|
| 420 |
+
|
| 421 |
+
```python
|
| 422 |
+
from typing import Dict, Any
|
| 423 |
+
import yaml
|
| 424 |
+
|
| 425 |
+
class LLMDeploymentConfig:
|
| 426 |
+
"""Configuration for LLM deployment."""
|
| 427 |
+
|
| 428 |
+
def __init__(self, config_dict: Dict[str, Any]):
|
| 429 |
+
self.config = config_dict
|
| 430 |
+
|
| 431 |
+
@classmethod
|
| 432 |
+
def from_file(cls, config_file: str):
|
| 433 |
+
"""Load configuration from file."""
|
| 434 |
+
with open(config_file, 'r') as f:
|
| 435 |
+
config_dict = yaml.safe_load(f)
|
| 436 |
+
return cls(config_dict)
|
| 437 |
+
|
| 438 |
+
def get_provider_config(self, provider_name: str):
|
| 439 |
+
"""Get configuration for specific provider."""
|
| 440 |
+
return self.config.get('providers', {}).get(provider_name, {})
|
| 441 |
+
|
| 442 |
+
def get_model_config(self, model_name: str):
|
| 443 |
+
"""Get configuration for specific model."""
|
| 444 |
+
return self.config.get('models', {}).get(model_name, {})
|
| 445 |
+
|
| 446 |
+
def get_rate_limits(self) -> Dict[str, int]:
|
| 447 |
+
"""Get rate limit configuration."""
|
| 448 |
+
return self.config.get('rate_limits', {
|
| 449 |
+
'requests_per_second': 10,
|
| 450 |
+
'burst': 20
|
| 451 |
+
})
|
| 452 |
+
|
| 453 |
+
def get_cache_config(self) -> Dict[str, Any]:
|
| 454 |
+
"""Get cache configuration."""
|
| 455 |
+
return self.config.get('cache', {
|
| 456 |
+
'enabled': True,
|
| 457 |
+
'ttl': 3600,
|
| 458 |
+
'redis_url': None
|
| 459 |
+
})
|
| 460 |
+
|
| 461 |
+
# Example configuration file
|
| 462 |
+
example_config = {
|
| 463 |
+
'providers': {
|
| 464 |
+
'openai': {
|
| 465 |
+
'api_key': 'your-api-key',
|
| 466 |
+
'model': 'gpt-4',
|
| 467 |
+
'temperature': 0.7,
|
| 468 |
+
'max_tokens': 1000
|
| 469 |
+
},
|
| 470 |
+
'local': {
|
| 471 |
+
'endpoint': 'http://localhost:11434',
|
| 472 |
+
'model': 'llama2',
|
| 473 |
+
'temperature': 0.7
|
| 474 |
+
}
|
| 475 |
+
},
|
| 476 |
+
'models': {
|
| 477 |
+
'gpt-4': {
|
| 478 |
+
'cost_per_1k_tokens': 0.03,
|
| 479 |
+
'max_tokens': 8192
|
| 480 |
+
},
|
| 481 |
+
'gpt-3.5-turbo': {
|
| 482 |
+
'cost_per_1k_tokens': 0.002,
|
| 483 |
+
'max_tokens': 4096
|
| 484 |
+
}
|
| 485 |
+
},
|
| 486 |
+
'rate_limits': {
|
| 487 |
+
'requests_per_second': 10,
|
| 488 |
+
'burst': 20
|
| 489 |
+
},
|
| 490 |
+
'cache': {
|
| 491 |
+
'enabled': True,
|
| 492 |
+
'ttl': 3600,
|
| 493 |
+
'redis_url': 'redis://localhost:6379'
|
| 494 |
+
}
|
| 495 |
+
}
|
| 496 |
+
```
|
| 497 |
+
|
| 498 |
+
## Constraints
|
| 499 |
+
- **API Costs**: Monitor and control LLM API costs carefully
|
| 500 |
+
- **Latency**: LLM calls can be slow, implement proper timeouts
|
| 501 |
+
- **Rate Limits**: Respect provider rate limits to avoid being blocked
|
| 502 |
+
- **Error Handling**: Implement robust error handling and retry logic
|
| 503 |
+
- **Monitoring**: Track usage and performance for optimization
|
| 504 |
+
- **Scalability**: Design for horizontal scaling when needed
|
| 505 |
+
|
| 506 |
+
## Expected Output
|
| 507 |
+
Production-ready LLM deployment with proper rate limiting, caching, monitoring, and multi-provider orchestration for reliable and cost-effective operations.
|
TRAE-Skills/ai_engineering/LangChain_Basics.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: LangChain Basics
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To utilize the LangChain framework to build complex LLM applications by chaining together components (Models, Prompts, Parsers) into composable workflows.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building complex chains (e.g., Retrieval -> Augmentation -> Generation).
|
| 8 |
+
- When you need to swap LLM providers easily (e.g., OpenAI to Anthropic).
|
| 9 |
+
- When integrating structured output parsing.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Installation
|
| 14 |
+
Install core LangChain packages and the OpenAI integration.
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
npm install @langchain/core @langchain/openai zod
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
### 2. Basic Chain Construction (LCEL)
|
| 21 |
+
Use LangChain Expression Language (LCEL) for declarative chain definitions.
|
| 22 |
+
|
| 23 |
+
```typescript
|
| 24 |
+
import { ChatOpenAI } from "@langchain/openai";
|
| 25 |
+
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
| 26 |
+
import { StringOutputParser } from "@langchain/core/output_parsers";
|
| 27 |
+
|
| 28 |
+
// 1. Initialize Model
|
| 29 |
+
const model = new ChatOpenAI({
|
| 30 |
+
modelName: "gpt-4o",
|
| 31 |
+
temperature: 0,
|
| 32 |
+
apiKey: process.env.OPENAI_API_KEY
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
// 2. Define Prompt
|
| 36 |
+
const prompt = ChatPromptTemplate.fromMessages([
|
| 37 |
+
["system", "You are a technical documentation expert."],
|
| 38 |
+
["user", "Explain {topic} in one sentence."]
|
| 39 |
+
]);
|
| 40 |
+
|
| 41 |
+
// 3. Create Chain
|
| 42 |
+
// Input -> Prompt -> Model -> String Output
|
| 43 |
+
const chain = prompt.pipe(model).pipe(new StringOutputParser());
|
| 44 |
+
|
| 45 |
+
// Usage
|
| 46 |
+
async function runChain() {
|
| 47 |
+
const result = await chain.invoke({ topic: "Dependency Injection" });
|
| 48 |
+
console.log(result);
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### 3. Structured Output Parsing
|
| 53 |
+
Use `StructuredOutputParser` with Zod to guarantee type-safe responses.
|
| 54 |
+
|
| 55 |
+
```typescript
|
| 56 |
+
import { z } from "zod";
|
| 57 |
+
import { StructuredOutputParser } from "@langchain/core/output_parsers";
|
| 58 |
+
|
| 59 |
+
// Define Schema
|
| 60 |
+
const schema = z.object({
|
| 61 |
+
sentiment: z.enum(["positive", "negative", "neutral"]),
|
| 62 |
+
keywords: z.array(z.string()).describe("List of up to 5 keywords"),
|
| 63 |
+
summary: z.string().describe("Brief summary of the text")
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
const parser = StructuredOutputParser.fromZodSchema(schema);
|
| 67 |
+
|
| 68 |
+
const analysisChain = ChatPromptTemplate.fromTemplate(
|
| 69 |
+
"Analyze the following text.\n{format_instructions}\n\nText: {text}"
|
| 70 |
+
).pipe(model).pipe(parser);
|
| 71 |
+
|
| 72 |
+
async function analyzeText(text: string) {
|
| 73 |
+
return await analysisChain.invoke({
|
| 74 |
+
text,
|
| 75 |
+
format_instructions: parser.getFormatInstructions()
|
| 76 |
+
});
|
| 77 |
+
}
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### 4. Memory Integration (RunnableWithMessageHistory)
|
| 81 |
+
Manage conversation history for chatbots.
|
| 82 |
+
|
| 83 |
+
```typescript
|
| 84 |
+
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
|
| 85 |
+
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
|
| 86 |
+
|
| 87 |
+
const messageHistory = new InMemoryChatMessageHistory();
|
| 88 |
+
|
| 89 |
+
const chatChain = new RunnableWithMessageHistory({
|
| 90 |
+
runnable: prompt.pipe(model),
|
| 91 |
+
getMessageHistory: async (sessionId) => messageHistory,
|
| 92 |
+
inputMessagesKey: "input",
|
| 93 |
+
historyMessagesKey: "history",
|
| 94 |
+
});
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
## Constraints
|
| 98 |
+
- **Abstraction Cost**: LangChain adds a layer of abstraction. For very simple calls, the native SDK might be cleaner.
|
| 99 |
+
- **Debugging**: LCEL chains can be harder to debug than imperative code. Use `LangSmith` for tracing if available.
|
| 100 |
+
- **Version Compatibility**: LangChain evolves fast. Lock versions in `package.json`.
|
| 101 |
+
|
| 102 |
+
## Expected Output
|
| 103 |
+
A composable pipeline that reliably transforms inputs into structured outputs, leveraging the power of chained LLM operations.
|
TRAE-Skills/ai_engineering/Local_LLM_Running_Ollama.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Local LLM Running (Ollama)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To run Large Language Models locally using Ollama, ensuring data privacy, zero API costs, and offline capability while providing a standard REST API for applications.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When data privacy is paramount (medical, legal, personal data).
|
| 8 |
+
- For development and testing without incurring API costs.
|
| 9 |
+
- When you need to experiment with open-source models (Llama 3, Mistral, etc.).
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Installation & Model Setup
|
| 14 |
+
Install Ollama and pull the desired model.
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
# Install (macOS/Linux)
|
| 18 |
+
curl -fsSL https://ollama.com/install.sh | sh
|
| 19 |
+
|
| 20 |
+
# Pull a model
|
| 21 |
+
ollama pull llama3:8b
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 2. Basic Usage (CLI)
|
| 25 |
+
Interact with the model directly in your terminal.
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
ollama run llama3:8b "Why is the sky blue?"
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
### 3. Programmatic Integration (Node.js)
|
| 32 |
+
Use the Ollama REST API or official library to integrate into your app.
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
npm install ollama
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
```typescript
|
| 39 |
+
import ollama from 'ollama';
|
| 40 |
+
|
| 41 |
+
async function chat() {
|
| 42 |
+
const response = await ollama.chat({
|
| 43 |
+
model: 'llama3:8b',
|
| 44 |
+
messages: [{ role: 'user', content: 'Explain quantum physics to a 5-year old' }],
|
| 45 |
+
stream: true,
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
for await (const part of response) {
|
| 49 |
+
process.stdout.write(part.message.content);
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 4. Customizing Models (Modelfile)
|
| 55 |
+
Create a specialized version of a model with custom system prompts.
|
| 56 |
+
|
| 57 |
+
1. Create a file named `Modelfile`:
|
| 58 |
+
```dockerfile
|
| 59 |
+
FROM llama3:8b
|
| 60 |
+
|
| 61 |
+
# Set parameters
|
| 62 |
+
PARAMETER temperature 0.1
|
| 63 |
+
PARAMETER top_p 0.9
|
| 64 |
+
|
| 65 |
+
# Set system message
|
| 66 |
+
SYSTEM """
|
| 67 |
+
You are a senior TypeScript developer.
|
| 68 |
+
You provide concise, high-performance code snippets.
|
| 69 |
+
Always use ESM syntax.
|
| 70 |
+
"""
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
2. Create the model:
|
| 74 |
+
```bash
|
| 75 |
+
ollama create ts-expert -f Modelfile
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### 5. Running as a Service (Docker)
|
| 79 |
+
Run Ollama in a container for consistent deployment.
|
| 80 |
+
|
| 81 |
+
```bash
|
| 82 |
+
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Constraints
|
| 86 |
+
- **VRAM Requirements**:
|
| 87 |
+
- 7B/8B models: ~8GB RAM/VRAM.
|
| 88 |
+
- 13B models: ~16GB RAM/VRAM.
|
| 89 |
+
- 70B models: ~48GB+ RAM/VRAM.
|
| 90 |
+
- **Latency**: Local models are significantly slower than GPT-4o unless running on a high-end GPU (RTX 3090/4090 or Apple M2/M3 Max).
|
| 91 |
+
- **Quantization**: Most Ollama models are 4-bit quantized (Q4_K_M) by default, which slightly reduces reasoning capability but saves memory.
|
| 92 |
+
|
| 93 |
+
## Expected Output
|
| 94 |
+
A locally running LLM service accessible via a REST API on `localhost:11434`.
|
TRAE-Skills/ai_engineering/ML_Model_Quantization.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: ML Model Quantization
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To reduce the memory footprint and improve inference speed of Machine Learning models, particularly deep neural networks, by converting high-precision weights (e.g., FP32) to lower-precision representations (e.g., INT8) with minimal loss of accuracy.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When deploying models to edge devices (mobile, IoT) with limited memory or compute
|
| 8 |
+
- When optimizing inference costs on cloud infrastructure
|
| 9 |
+
- When striving for real-time performance in computer vision or NLP tasks
|
| 10 |
+
- When the model size exceeds deployment constraints
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Identify Quantization Strategy
|
| 15 |
+
Choose the appropriate quantization method based on your deployment needs:
|
| 16 |
+
- **Post-Training Quantization (PTQ)**: Applied after training. Easiest to implement. Good for general use cases.
|
| 17 |
+
- **Quantization-Aware Training (QAT)**: Simulates quantization during training. Results in higher accuracy, but requires retraining.
|
| 18 |
+
|
| 19 |
+
### 2. Post-Training Quantization (PyTorch Example)
|
| 20 |
+
|
| 21 |
+
**Dynamic Quantization** (Best for LSTM/RNN or Transformer models):
|
| 22 |
+
```python
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
# 1. Load your pre-trained model
|
| 26 |
+
model = MyTransformerModel()
|
| 27 |
+
model.load_state_dict(torch.load('model_fp32.pth'))
|
| 28 |
+
model.eval()
|
| 29 |
+
|
| 30 |
+
# 2. Apply dynamic quantization to specific layers (e.g., Linear layers)
|
| 31 |
+
quantized_model = torch.quantization.quantize_dynamic(
|
| 32 |
+
model, {torch.nn.Linear}, dtype=torch.qint8
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# 3. Save the quantized model
|
| 36 |
+
torch.save(quantized_model.state_dict(), 'model_int8.pth')
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**Static Quantization** (Best for CNNs):
|
| 40 |
+
Requires a representative dataset to calibrate the activations.
|
| 41 |
+
```python
|
| 42 |
+
import torch
|
| 43 |
+
|
| 44 |
+
# 1. Prepare model for static quantization
|
| 45 |
+
model.eval()
|
| 46 |
+
model.qconfig = torch.quantization.get_default_qconfig('fbgemm')
|
| 47 |
+
torch.quantization.prepare(model, inplace=True)
|
| 48 |
+
|
| 49 |
+
# 2. Calibrate with representative data
|
| 50 |
+
for data, _ in representative_dataloader:
|
| 51 |
+
model(data)
|
| 52 |
+
|
| 53 |
+
# 3. Convert to quantized model
|
| 54 |
+
torch.quantization.convert(model, inplace=True)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### 3. Quantization-Aware Training (QAT)
|
| 58 |
+
If PTQ causes an unacceptable drop in accuracy, use QAT.
|
| 59 |
+
|
| 60 |
+
```python
|
| 61 |
+
import torch
|
| 62 |
+
|
| 63 |
+
# 1. Prepare model for QAT
|
| 64 |
+
model.train()
|
| 65 |
+
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
|
| 66 |
+
torch.quantization.prepare_qat(model, inplace=True)
|
| 67 |
+
|
| 68 |
+
# 2. Fine-tune the model
|
| 69 |
+
for epoch in range(num_epochs):
|
| 70 |
+
for data, target in train_dataloader:
|
| 71 |
+
optimizer.zero_grad()
|
| 72 |
+
output = model(data)
|
| 73 |
+
loss = criterion(output, target)
|
| 74 |
+
loss.backward()
|
| 75 |
+
optimizer.step()
|
| 76 |
+
|
| 77 |
+
# 3. Convert to quantized model for inference
|
| 78 |
+
model.eval()
|
| 79 |
+
torch.quantization.convert(model, inplace=True)
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Best Practices
|
| 83 |
+
- Always benchmark accuracy before and after quantization.
|
| 84 |
+
- For LLMs, consider specialized quantization libraries like `bitsandbytes` (4-bit/8-bit) or formats like GGUF/AWQ.
|
| 85 |
+
- Use the appropriate backend (e.g., `fbgemm` for x86, `qnnpack` for ARM).
|
TRAE-Skills/ai_engineering/MultiModal_AI.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Multi-Modal AI
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To work with AI models that can process and generate multiple types of content including text, images, audio, and video in a unified framework.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building applications that process images and text together
|
| 8 |
+
- When implementing vision-language tasks
|
| 9 |
+
- When generating images from text descriptions
|
| 10 |
+
- When analyzing visual content with natural language queries
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Vision-Language Understanding
|
| 15 |
+
Process images with text queries using GPT-4 Vision.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
import base64
|
| 20 |
+
|
| 21 |
+
client = OpenAI()
|
| 22 |
+
|
| 23 |
+
def encode_image(image_path):
|
| 24 |
+
"""Encode image to base64."""
|
| 25 |
+
with open(image_path, "rb") as image_file:
|
| 26 |
+
return base64.b64encode(image_file.read()).decode('utf-8')
|
| 27 |
+
|
| 28 |
+
def analyze_image(image_path, question):
|
| 29 |
+
"""Analyze image with text query."""
|
| 30 |
+
base64_image = encode_image(image_path)
|
| 31 |
+
|
| 32 |
+
response = client.chat.completions.create(
|
| 33 |
+
model="gpt-4-vision-preview",
|
| 34 |
+
messages=[
|
| 35 |
+
{
|
| 36 |
+
"role": "user",
|
| 37 |
+
"content": [
|
| 38 |
+
{"type": "text", "text": question},
|
| 39 |
+
{
|
| 40 |
+
"type": "image_url",
|
| 41 |
+
"image_url": {
|
| 42 |
+
"url": f"data:image/jpeg;base64,{base64_image}"
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
]
|
| 46 |
+
}
|
| 47 |
+
],
|
| 48 |
+
max_tokens=500
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
return response.choices[0].message.content
|
| 52 |
+
|
| 53 |
+
# Example
|
| 54 |
+
analysis = analyze_image(
|
| 55 |
+
"product.jpg",
|
| 56 |
+
"What products are shown in this image? What are their key features?"
|
| 57 |
+
)
|
| 58 |
+
print(analysis)
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### 2. Image Generation with DALL-E
|
| 62 |
+
Generate images from text descriptions.
|
| 63 |
+
|
| 64 |
+
```python
|
| 65 |
+
def generate_image(prompt, size="1024x1024", quality="standard"):
|
| 66 |
+
"""Generate image from text prompt."""
|
| 67 |
+
response = client.images.generate(
|
| 68 |
+
model="dall-e-3",
|
| 69 |
+
prompt=prompt,
|
| 70 |
+
size=size,
|
| 71 |
+
quality=quality,
|
| 72 |
+
n=1
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
image_url = response.data[0].url
|
| 76 |
+
revised_prompt = response.data[0].revised_prompt
|
| 77 |
+
|
| 78 |
+
return {
|
| 79 |
+
"url": image_url,
|
| 80 |
+
"revised_prompt": revised_prompt
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
# Example
|
| 84 |
+
result = generate_image(
|
| 85 |
+
"A futuristic smart home with voice-controlled devices and automated lighting",
|
| 86 |
+
size="1024x1024"
|
| 87 |
+
)
|
| 88 |
+
print(f"Image URL: {result['url']}")
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 3. Image Editing and Variations
|
| 92 |
+
Modify and create variations of existing images.
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
from PIL import Image
|
| 96 |
+
import io
|
| 97 |
+
|
| 98 |
+
def edit_image(original_image_path, mask_path, prompt):
|
| 99 |
+
"""Edit image with mask and prompt."""
|
| 100 |
+
with open(original_image_path, "rb") as img_file:
|
| 101 |
+
original_image = img_file.read()
|
| 102 |
+
|
| 103 |
+
with open(mask_path, "rb") as mask_file:
|
| 104 |
+
mask_image = mask_file.read()
|
| 105 |
+
|
| 106 |
+
response = client.images.edit(
|
| 107 |
+
model="dall-e-2",
|
| 108 |
+
image=original_image,
|
| 109 |
+
mask=mask_image,
|
| 110 |
+
prompt=prompt,
|
| 111 |
+
n=1,
|
| 112 |
+
size="512x512"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
return response.data[0].url
|
| 116 |
+
|
| 117 |
+
def create_variations(image_path):
|
| 118 |
+
"""Create variations of an image."""
|
| 119 |
+
with open(image_path, "rb") as img_file:
|
| 120 |
+
image_data = img_file.read()
|
| 121 |
+
|
| 122 |
+
response = client.images.create_variation(
|
| 123 |
+
image=image_data,
|
| 124 |
+
n=3,
|
| 125 |
+
size="1024x1024"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
return [img.url for img in response.data]
|
| 129 |
+
|
| 130 |
+
# Example
|
| 131 |
+
edited_url = edit_image(
|
| 132 |
+
"room.jpg",
|
| 133 |
+
"room_mask.png",
|
| 134 |
+
"Add a modern desk with computer setup"
|
| 135 |
+
)
|
| 136 |
+
print(f"Edited image: {edited_url}")
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
### 4. Multi-Modal Document Processing
|
| 140 |
+
Process documents with text and images.
|
| 141 |
+
|
| 142 |
+
```python
|
| 143 |
+
import pdf2image
|
| 144 |
+
import pytesseract
|
| 145 |
+
|
| 146 |
+
def process_multimodal_document(pdf_path):
|
| 147 |
+
"""Extract and analyze content from PDF with images."""
|
| 148 |
+
# Convert PDF to images
|
| 149 |
+
images = pdf2image.convert_from_path(pdf_path)
|
| 150 |
+
|
| 151 |
+
results = []
|
| 152 |
+
|
| 153 |
+
for i, image in enumerate(images):
|
| 154 |
+
# Extract text using OCR
|
| 155 |
+
text = pytesseract.image_to_string(image)
|
| 156 |
+
|
| 157 |
+
# Encode image for GPT-4 Vision analysis
|
| 158 |
+
import io
|
| 159 |
+
import base64
|
| 160 |
+
|
| 161 |
+
img_byte_arr = io.BytesIO()
|
| 162 |
+
image.save(img_byte_arr, format='PNG')
|
| 163 |
+
img_byte_arr = img_byte_arr.getvalue()
|
| 164 |
+
base64_image = base64.b64encode(img_byte_arr).decode('utf-8')
|
| 165 |
+
|
| 166 |
+
# Analyze image content
|
| 167 |
+
vision_response = client.chat.completions.create(
|
| 168 |
+
model="gpt-4-vision-preview",
|
| 169 |
+
messages=[
|
| 170 |
+
{
|
| 171 |
+
"role": "user",
|
| 172 |
+
"content": [
|
| 173 |
+
{"type": "text", "text": "Analyze this document page. What type of content is shown?"},
|
| 174 |
+
{
|
| 175 |
+
"type": "image_url",
|
| 176 |
+
"image_url": {"url": f"data:image/png;base64,{base64_image}"}
|
| 177 |
+
}
|
| 178 |
+
]
|
| 179 |
+
}
|
| 180 |
+
]
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
results.append({
|
| 184 |
+
"page": i + 1,
|
| 185 |
+
"ocr_text": text,
|
| 186 |
+
"visual_analysis": vision_response.choices[0].message.content
|
| 187 |
+
})
|
| 188 |
+
|
| 189 |
+
return results
|
| 190 |
+
|
| 191 |
+
# Example
|
| 192 |
+
document_results = process_multimodal_document("contract.pdf")
|
| 193 |
+
for page in document_results:
|
| 194 |
+
print(f"Page {page['page']}: {page['visual_analysis']}")
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
### 5. Audio and Text Integration
|
| 198 |
+
Process audio with text analysis.
|
| 199 |
+
|
| 200 |
+
```python
|
| 201 |
+
import requests
|
| 202 |
+
|
| 203 |
+
def transcribe_and_analyze(audio_path):
|
| 204 |
+
"""Transcribe audio and analyze with text."""
|
| 205 |
+
# Transcribe audio using Whisper
|
| 206 |
+
with open(audio_path, "rb") as audio_file:
|
| 207 |
+
transcription_response = client.audio.transcriptions.create(
|
| 208 |
+
model="whisper-1",
|
| 209 |
+
file=audio_file,
|
| 210 |
+
response_format="text"
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
transcript = transcription_response
|
| 214 |
+
|
| 215 |
+
# Analyze transcript with GPT-4
|
| 216 |
+
analysis = client.chat.completions.create(
|
| 217 |
+
model="gpt-4",
|
| 218 |
+
messages=[{
|
| 219 |
+
"role": "user",
|
| 220 |
+
"content": f"Analyze this transcript for key topics, sentiment, and action items:\n\n{transcript}"
|
| 221 |
+
}]
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
return {
|
| 225 |
+
"transcript": transcript,
|
| 226 |
+
"analysis": analysis.choices[0].message.content
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
# Example
|
| 230 |
+
result = transcribe_and_analyze("meeting_recording.mp3")
|
| 231 |
+
print(f"Transcript: {result['transcript']}")
|
| 232 |
+
print(f"Analysis: {result['analysis']}")
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
### 6. Multi-Modal RAG System
|
| 236 |
+
Build RAG with image and text retrieval.
|
| 237 |
+
|
| 238 |
+
```python
|
| 239 |
+
from sentence_transformers import SentenceTransformer
|
| 240 |
+
import faiss
|
| 241 |
+
import numpy as np
|
| 242 |
+
|
| 243 |
+
class MultiModalRAG:
|
| 244 |
+
def __init__(self):
|
| 245 |
+
self.text_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 246 |
+
self.image_model = SentenceTransformer('clip-ViT-B-32')
|
| 247 |
+
self.text_index = None
|
| 248 |
+
self.image_index = None
|
| 249 |
+
self.text_docs = []
|
| 250 |
+
self.image_docs = []
|
| 251 |
+
|
| 252 |
+
def add_text_documents(self, documents):
|
| 253 |
+
"""Add text documents to the index."""
|
| 254 |
+
embeddings = self.text_model.encode(documents)
|
| 255 |
+
|
| 256 |
+
if self.text_index is None:
|
| 257 |
+
dimension = embeddings.shape[1]
|
| 258 |
+
self.text_index = faiss.IndexFlatL2(dimension)
|
| 259 |
+
|
| 260 |
+
self.text_index.add(embeddings.astype('float32'))
|
| 261 |
+
self.text_docs.extend(documents)
|
| 262 |
+
|
| 263 |
+
def add_image_documents(self, image_paths, descriptions):
|
| 264 |
+
"""Add images to the index."""
|
| 265 |
+
embeddings = self.image_model.encode(image_paths)
|
| 266 |
+
|
| 267 |
+
if self.image_index is None:
|
| 268 |
+
dimension = embeddings.shape[1]
|
| 269 |
+
self.image_index = faiss.IndexFlatL2(dimension)
|
| 270 |
+
|
| 271 |
+
self.image_index.add(embeddings.astype('float32'))
|
| 272 |
+
self.image_docs.extend(zip(image_paths, descriptions))
|
| 273 |
+
|
| 274 |
+
def search(self, query, k=3):
|
| 275 |
+
"""Search across text and images."""
|
| 276 |
+
# Search text
|
| 277 |
+
if self.text_index:
|
| 278 |
+
query_embedding = self.text_model.encode([query])
|
| 279 |
+
text_distances, text_indices = self.text_index.search(
|
| 280 |
+
query_embedding.astype('float32'), k
|
| 281 |
+
)
|
| 282 |
+
text_results = [
|
| 283 |
+
(self.text_docs[i], text_distances[0][j])
|
| 284 |
+
for j, i in enumerate(text_indices[0])
|
| 285 |
+
]
|
| 286 |
+
else:
|
| 287 |
+
text_results = []
|
| 288 |
+
|
| 289 |
+
# Search images
|
| 290 |
+
if self.image_index:
|
| 291 |
+
query_embedding = self.image_model.encode([query])
|
| 292 |
+
image_distances, image_indices = self.image_index.search(
|
| 293 |
+
query_embedding.astype('float32'), k
|
| 294 |
+
)
|
| 295 |
+
image_results = [
|
| 296 |
+
(self.image_docs[i], image_distances[0][j])
|
| 297 |
+
for j, i in enumerate(image_indices[0])
|
| 298 |
+
]
|
| 299 |
+
else:
|
| 300 |
+
image_results = []
|
| 301 |
+
|
| 302 |
+
return {
|
| 303 |
+
"text_results": text_results,
|
| 304 |
+
"image_results": image_results
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
# Example usage
|
| 308 |
+
rag = MultiModalRAG()
|
| 309 |
+
|
| 310 |
+
rag.add_text_documents([
|
| 311 |
+
"Python is a high-level programming language",
|
| 312 |
+
"JavaScript is used for web development"
|
| 313 |
+
])
|
| 314 |
+
|
| 315 |
+
rag.add_image_documents(
|
| 316 |
+
["python_logo.png", "js_logo.png"],
|
| 317 |
+
["Python programming language logo", "JavaScript logo"]
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
results = rag.search("programming languages")
|
| 321 |
+
print(results)
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
## Constraints
|
| 325 |
+
- **Image Size**: Vision models have limits on image dimensions and file sizes
|
| 326 |
+
- **Cost**: Vision and image generation APIs are more expensive than text-only
|
| 327 |
+
- **Quality**: Generated images may not always match expectations
|
| 328 |
+
- **Processing Time**: Image processing is slower than text-only operations
|
| 329 |
+
- **Accuracy**: OCR accuracy varies based on image quality
|
| 330 |
+
- **Privacy**: Be careful with sensitive visual content
|
| 331 |
+
|
| 332 |
+
## Expected Output
|
| 333 |
+
Comprehensive multi-modal AI applications that can seamlessly process and generate text, images, and audio content with high accuracy and integration.
|
TRAE-Skills/ai_engineering/Natural_Language_to_SQL.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Natural Language to SQL (NL2SQL)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To build systems that convert natural language questions into executable SQL queries, enabling non-technical users to interact with databases.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building analytics dashboards for business users
|
| 8 |
+
- For customer support tools that query databases directly
|
| 9 |
+
- When implementing internal tools for non-technical teams
|
| 10 |
+
- For data exploration applications
|
| 11 |
+
- When building chatbots that need database access
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Simple NL2SQL with OpenAI
|
| 16 |
+
Use LLMs to generate SQL from natural language.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
import openai
|
| 20 |
+
import sqlite3
|
| 21 |
+
|
| 22 |
+
client = openai.OpenAI(api_key="your-api-key")
|
| 23 |
+
|
| 24 |
+
def nl_to_sql(question, table_schema):
|
| 25 |
+
prompt = f"""Given the following table schema:
|
| 26 |
+
{table_schema}
|
| 27 |
+
|
| 28 |
+
Generate a SQLite SQL query to answer this question: {question}
|
| 29 |
+
|
| 30 |
+
Only return the SQL query, no explanation.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
response = client.chat.completions.create(
|
| 34 |
+
model="gpt-4",
|
| 35 |
+
messages=[{"role": "user", "content": prompt}]
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
return response.choices[0].message.content.strip()
|
| 39 |
+
|
| 40 |
+
# Example usage
|
| 41 |
+
table_schema = """
|
| 42 |
+
Table: users
|
| 43 |
+
- id: INTEGER
|
| 44 |
+
- name: TEXT
|
| 45 |
+
- email: TEXT
|
| 46 |
+
- created_at: DATE
|
| 47 |
+
- status: TEXT (active, inactive)
|
| 48 |
+
|
| 49 |
+
Table: orders
|
| 50 |
+
- id: INTEGER
|
| 51 |
+
- user_id: INTEGER
|
| 52 |
+
- total_amount: DECIMAL
|
| 53 |
+
- order_date: DATE
|
| 54 |
+
- status: TEXT (pending, completed, cancelled)
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
question = "Show me all active users who placed orders over $100 in 2024"
|
| 58 |
+
sql_query = nl_to_sql(question, table_schema)
|
| 59 |
+
print(sql_query)
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### 2. Execute Query Safely
|
| 63 |
+
Execute the generated SQL with safety checks.
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
def execute_safe_query(db_path, sql):
|
| 67 |
+
# Safety checks
|
| 68 |
+
dangerous_keywords = ['DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'INSERT', 'UPDATE', 'CREATE']
|
| 69 |
+
for keyword in dangerous_keywords:
|
| 70 |
+
if keyword.upper() in sql.upper():
|
| 71 |
+
raise Exception(f"Query contains forbidden operation: {keyword}")
|
| 72 |
+
|
| 73 |
+
conn = sqlite3.connect(db_path)
|
| 74 |
+
cursor = conn.cursor()
|
| 75 |
+
cursor.execute(sql)
|
| 76 |
+
results = cursor.fetchall()
|
| 77 |
+
columns = [description[0] for description in cursor.description]
|
| 78 |
+
conn.close()
|
| 79 |
+
|
| 80 |
+
return {"columns": columns, "results": results}
|
| 81 |
+
|
| 82 |
+
# Usage
|
| 83 |
+
try:
|
| 84 |
+
result = execute_safe_query("mydb.sqlite", sql_query)
|
| 85 |
+
print("Columns:", result["columns"])
|
| 86 |
+
print("Results:", result["results"])
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print("Error:", e)
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 3. Few-Shot Learning Examples
|
| 92 |
+
Improve accuracy with few-shot examples.
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
few_shot_examples = """
|
| 96 |
+
Example 1:
|
| 97 |
+
Question: How many users are there?
|
| 98 |
+
SQL: SELECT COUNT(*) FROM users;
|
| 99 |
+
|
| 100 |
+
Example 2:
|
| 101 |
+
Question: Show users who signed up in 2024
|
| 102 |
+
SQL: SELECT * FROM users WHERE created_at >= '2024-01-01';
|
| 103 |
+
|
| 104 |
+
Example 3:
|
| 105 |
+
Question: What's the total revenue from completed orders?
|
| 106 |
+
SQL: SELECT SUM(total_amount) FROM orders WHERE status = 'completed';
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
def nl_to_sql_with_examples(question, table_schema):
|
| 110 |
+
prompt = f"""Given the following table schema:
|
| 111 |
+
{table_schema}
|
| 112 |
+
|
| 113 |
+
Examples:
|
| 114 |
+
{few_shot_examples}
|
| 115 |
+
|
| 116 |
+
Generate a SQLite SQL query to answer this question: {question}
|
| 117 |
+
Only return the SQL query.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
response = client.chat.completions.create(
|
| 121 |
+
model="gpt-4",
|
| 122 |
+
messages=[{"role": "user", "content": prompt}]
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
return response.choices[0].message.content.strip()
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
### 4. Using LangChain for NL2SQL
|
| 129 |
+
Use LangChain's SQL database chain.
|
| 130 |
+
|
| 131 |
+
```python
|
| 132 |
+
from langchain_openai import ChatOpenAI
|
| 133 |
+
from langchain_community.utilities import SQLDatabase
|
| 134 |
+
from langchain.chains import create_sql_query_chain
|
| 135 |
+
|
| 136 |
+
db = SQLDatabase.from_uri("sqlite:///mydb.sqlite")
|
| 137 |
+
llm = ChatOpenAI(model="gpt-4", temperature=0)
|
| 138 |
+
chain = create_sql_query_chain(llm, db)
|
| 139 |
+
|
| 140 |
+
question = "Show me the top 5 users by total order amount"
|
| 141 |
+
response = chain.invoke({"question": question})
|
| 142 |
+
print(response)
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
## Best Practices
|
| 146 |
+
- **Whitelist Operations**: Only allow SELECT queries in production
|
| 147 |
+
- **Schema Context**: Always provide clear table schema information
|
| 148 |
+
- **Validation**: Validate generated queries before execution
|
| 149 |
+
- **Few-Shot Learning**: Use examples to improve accuracy
|
| 150 |
+
- **Error Handling**: Gracefully handle query generation failures
|
| 151 |
+
- **Sanitize Inputs**: Prevent SQL injection even from generated queries
|
| 152 |
+
- **Log Everything**: Log questions, queries, and results for debugging
|
TRAE-Skills/ai_engineering/OpenAI_API_Integration.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: OpenAI API Integration
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To correctly integrate OpenAI's models (GPT-4o, GPT-3.5) into Node.js/TypeScript applications, handling authentication, type-safety, streaming, and robust error management.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building applications that rely on OpenAI's completion or chat API.
|
| 8 |
+
- When you need a production-ready wrapper around the official SDK.
|
| 9 |
+
|
| 10 |
+
## Procedure
|
| 11 |
+
|
| 12 |
+
### 1. Installation & Setup
|
| 13 |
+
Install the official library and type definitions.
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
npm install openai zod
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
### 2. Robust Client Implementation
|
| 20 |
+
Create a service class that handles initialization and configuration.
|
| 21 |
+
|
| 22 |
+
```typescript
|
| 23 |
+
// lib/openai-client.ts
|
| 24 |
+
import OpenAI from 'openai';
|
| 25 |
+
|
| 26 |
+
export class OpenAIClient {
|
| 27 |
+
private client: OpenAI;
|
| 28 |
+
|
| 29 |
+
constructor() {
|
| 30 |
+
if (!process.env.OPENAI_API_KEY) {
|
| 31 |
+
throw new Error("Missing OPENAI_API_KEY environment variable");
|
| 32 |
+
}
|
| 33 |
+
this.client = new OpenAI({
|
| 34 |
+
apiKey: process.env.OPENAI_API_KEY,
|
| 35 |
+
maxRetries: 3, // Built-in retry logic
|
| 36 |
+
timeout: 30000, // 30s timeout
|
| 37 |
+
});
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
public getClient() {
|
| 41 |
+
return this.client;
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 3. Chat Completion with Error Handling
|
| 47 |
+
Implement a method for standard chat completions with try/catch blocks for specific API errors.
|
| 48 |
+
|
| 49 |
+
```typescript
|
| 50 |
+
// lib/ai-service.ts
|
| 51 |
+
import { OpenAIClient } from './openai-client';
|
| 52 |
+
import OpenAI from 'openai';
|
| 53 |
+
|
| 54 |
+
export class AIService {
|
| 55 |
+
private openai: OpenAI;
|
| 56 |
+
|
| 57 |
+
constructor() {
|
| 58 |
+
this.openai = new OpenAIClient().getClient();
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
async generateResponse(systemPrompt: string, userMessage: string): Promise<string> {
|
| 62 |
+
try {
|
| 63 |
+
const response = await this.openai.chat.completions.create({
|
| 64 |
+
model: "gpt-4o",
|
| 65 |
+
messages: [
|
| 66 |
+
{ role: "system", content: systemPrompt },
|
| 67 |
+
{ role: "user", content: userMessage },
|
| 68 |
+
],
|
| 69 |
+
temperature: 0.7,
|
| 70 |
+
});
|
| 71 |
+
|
| 72 |
+
return response.choices[0]?.message?.content || "";
|
| 73 |
+
} catch (error) {
|
| 74 |
+
if (error instanceof OpenAI.APIError) {
|
| 75 |
+
console.error(`OpenAI Error: ${error.status} - ${error.code}`);
|
| 76 |
+
// Handle specific codes: 429 (Rate Limit), 400 (Bad Request), 401 (Auth)
|
| 77 |
+
if (error.status === 429) {
|
| 78 |
+
throw new Error("Rate limit exceeded. Please try again later.");
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
throw new Error("Failed to generate AI response");
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### 4. Streaming Response Implementation
|
| 88 |
+
Handle real-time output for better UX.
|
| 89 |
+
|
| 90 |
+
```typescript
|
| 91 |
+
// lib/stream-service.ts
|
| 92 |
+
import { OpenAIClient } from './openai-client';
|
| 93 |
+
|
| 94 |
+
export async function* streamCompletion(prompt: string) {
|
| 95 |
+
const openai = new OpenAIClient().getClient();
|
| 96 |
+
|
| 97 |
+
const stream = await openai.chat.completions.create({
|
| 98 |
+
model: "gpt-4o",
|
| 99 |
+
messages: [{ role: "user", content: prompt }],
|
| 100 |
+
stream: true,
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
+
for await (const chunk of stream) {
|
| 104 |
+
const content = chunk.choices[0]?.delta?.content || "";
|
| 105 |
+
if (content) {
|
| 106 |
+
yield content;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Usage:
|
| 112 |
+
// for await (const token of streamCompletion("Hello")) {
|
| 113 |
+
// process.stdout.write(token);
|
| 114 |
+
// }
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### 5. Structured Outputs (JSON)
|
| 118 |
+
Enforce JSON output for programmatic use.
|
| 119 |
+
|
| 120 |
+
```typescript
|
| 121 |
+
async function extractData(text: string) {
|
| 122 |
+
const openai = new OpenAIClient().getClient();
|
| 123 |
+
const response = await openai.chat.completions.create({
|
| 124 |
+
model: "gpt-4o",
|
| 125 |
+
messages: [
|
| 126 |
+
{ role: "system", content: "You are a data extractor. Output valid JSON." },
|
| 127 |
+
{ role: "user", content: `Extract names from: ${text}` }
|
| 128 |
+
],
|
| 129 |
+
response_format: { type: "json_object" },
|
| 130 |
+
});
|
| 131 |
+
|
| 132 |
+
return JSON.parse(response.choices[0].message.content!);
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
## Constraints
|
| 137 |
+
- **Costs**: GPT-4 is expensive. Cache responses where possible (e.g., using Redis) for identical inputs.
|
| 138 |
+
- **Security**: Never expose the API key on the client-side (browser). Always proxy requests through your backend.
|
| 139 |
+
- **Timeouts**: LLM requests can be slow. Ensure your HTTP server (e.g., Nginx, Vercel) has appropriate timeout settings (often > 10s).
|
| 140 |
+
|
| 141 |
+
## Expected Output
|
| 142 |
+
A secure, reusable service module that reliably communicates with OpenAI, handles rate limits, and provides both streaming and blocking interfaces.
|
TRAE-Skills/ai_engineering/Prompt_Engineering_Basics.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Prompt Engineering Basics
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To apply systematic techniques for crafting inputs (prompts) to Large Language Models (LLMs) to ensure accurate, consistent, and high-quality outputs. This skill focuses on structured prompt management and engineering patterns.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When interacting with LLMs for any task (generation, summarization, extraction).
|
| 8 |
+
- When model outputs are inconsistent, hallucinatory, or vague.
|
| 9 |
+
- When you need to standardize input/output formats for an application.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Structured Prompt Management (TypeScript)
|
| 14 |
+
Avoid hardcoding strings. Use a template system.
|
| 15 |
+
|
| 16 |
+
```typescript
|
| 17 |
+
// prompt-manager.ts
|
| 18 |
+
type PromptVariables = Record<string, string>;
|
| 19 |
+
|
| 20 |
+
interface PromptTemplate {
|
| 21 |
+
system: (vars: PromptVariables) => string;
|
| 22 |
+
user: (vars: PromptVariables) => string;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export const summarizationPrompt: PromptTemplate = {
|
| 26 |
+
system: ({ context }) =>
|
| 27 |
+
`You are an expert technical writer. Your task is to summarize the following context into a concise executive summary.
|
| 28 |
+
|
| 29 |
+
Context: ${context}
|
| 30 |
+
|
| 31 |
+
Constraints:
|
| 32 |
+
- Use bullet points.
|
| 33 |
+
- Max 200 words.
|
| 34 |
+
- Maintain a professional tone.`,
|
| 35 |
+
|
| 36 |
+
user: ({ text }) => `Text to summarize:\n${text}`
|
| 37 |
+
};
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 2. The CO-STAR Framework Implementation
|
| 41 |
+
Implement the CO-STAR framework programmatically to ensure all context is provided.
|
| 42 |
+
|
| 43 |
+
```typescript
|
| 44 |
+
// co-star-prompt.ts
|
| 45 |
+
interface CoStarParams {
|
| 46 |
+
context: string;
|
| 47 |
+
objective: string;
|
| 48 |
+
style: string;
|
| 49 |
+
tone: string;
|
| 50 |
+
audience: string;
|
| 51 |
+
responseFormat: string;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export const buildCoStarSystemMessage = (params: CoStarParams): string => {
|
| 55 |
+
return `
|
| 56 |
+
# CONTEXT
|
| 57 |
+
${params.context}
|
| 58 |
+
|
| 59 |
+
# OBJECTIVE
|
| 60 |
+
${params.objective}
|
| 61 |
+
|
| 62 |
+
# STYLE
|
| 63 |
+
${params.style}
|
| 64 |
+
|
| 65 |
+
# TONE
|
| 66 |
+
${params.tone}
|
| 67 |
+
|
| 68 |
+
# AUDIENCE
|
| 69 |
+
${params.audience}
|
| 70 |
+
|
| 71 |
+
# RESPONSE FORMAT
|
| 72 |
+
${params.responseFormat}
|
| 73 |
+
`;
|
| 74 |
+
};
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### 3. Chain-of-Thought (CoT) & Few-Shot Prompting
|
| 78 |
+
Enhance reasoning by forcing step-by-step logic or providing examples.
|
| 79 |
+
|
| 80 |
+
```typescript
|
| 81 |
+
// reasoning-prompt.ts
|
| 82 |
+
const fewShotExamples = `
|
| 83 |
+
Input: "The server is down."
|
| 84 |
+
Classification: Critical
|
| 85 |
+
Reasoning: Impact on availability is immediate.
|
| 86 |
+
|
| 87 |
+
Input: "The button color is slightly off."
|
| 88 |
+
Classification: Low
|
| 89 |
+
Reasoning: Purely cosmetic issue.
|
| 90 |
+
`;
|
| 91 |
+
|
| 92 |
+
export const classificationPrompt = (input: string) => `
|
| 93 |
+
Classify the severity of the following issue (Critical, High, Medium, Low).
|
| 94 |
+
First, explain your reasoning step-by-step, then provide the final classification.
|
| 95 |
+
|
| 96 |
+
Examples:
|
| 97 |
+
${fewShotExamples}
|
| 98 |
+
|
| 99 |
+
Input: "${input}"
|
| 100 |
+
Output:
|
| 101 |
+
`;
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### 4. Handling Output Formats (JSON Mode)
|
| 105 |
+
Always enforce structure when programmatic consumption is needed.
|
| 106 |
+
|
| 107 |
+
```typescript
|
| 108 |
+
// Ensure your API call sets { response_format: { type: "json_object" } }
|
| 109 |
+
export const jsonExtractionPrompt = (text: string) => `
|
| 110 |
+
Extract the key entities from the text below.
|
| 111 |
+
You must respond with valid JSON only.
|
| 112 |
+
|
| 113 |
+
Schema:
|
| 114 |
+
{
|
| 115 |
+
"names": string[],
|
| 116 |
+
"dates": string[],
|
| 117 |
+
"sentiment": "positive" | "negative" | "neutral"
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
Text: "${text}"
|
| 121 |
+
`;
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
## Constraints
|
| 125 |
+
- **Context Window**: Monitor token count. Truncate inputs if they exceed limits (e.g., using `tiktoken`).
|
| 126 |
+
- **Injection Attacks**: Treat user input as untrusted. Delimit user input (e.g., with `"""` or `###`) to prevent prompt injection.
|
| 127 |
+
- **Determinism**: Set `temperature: 0` for classification/extraction tasks; higher for creative tasks.
|
| 128 |
+
|
| 129 |
+
## Expected Output
|
| 130 |
+
A set of typed, reusable prompt templates that produce consistent, parsed outputs from the LLM.
|
TRAE-Skills/ai_engineering/RAG_System_Architecture.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: RAG System Architecture
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To design and implement a Retrieval-Augmented Generation (RAG) system that grounds LLM responses in specific, external data sources, reducing hallucinations and enabling knowledge on private data.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When the LLM needs to answer questions about proprietary documents (PDFs, internal wikis).
|
| 8 |
+
- When the knowledge base is too large to fit in the context window.
|
| 9 |
+
- When data changes frequently and retraining is not feasible.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Ingestion & Embedding (The ETL Pipeline)
|
| 14 |
+
Process documents into vectors.
|
| 15 |
+
|
| 16 |
+
```typescript
|
| 17 |
+
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
|
| 18 |
+
import { OpenAIEmbeddings } from "@langchain/openai";
|
| 19 |
+
import { MemoryVectorStore } from "langchain/vectorstores/memory";
|
| 20 |
+
|
| 21 |
+
// 1. Text Splitting
|
| 22 |
+
const splitter = new RecursiveCharacterTextSplitter({
|
| 23 |
+
chunkSize: 1000,
|
| 24 |
+
chunkOverlap: 200, // Crucial for context continuity
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
// 2. Embedding Model
|
| 28 |
+
const embeddings = new OpenAIEmbeddings({
|
| 29 |
+
modelName: "text-embedding-3-small", // Efficient & cheap
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
// 3. Vector Store Initialization
|
| 33 |
+
// In production, use Pinecone, Weaviate, or pgvector
|
| 34 |
+
const vectorStore = new MemoryVectorStore(embeddings);
|
| 35 |
+
|
| 36 |
+
export async function ingestDocument(text: string) {
|
| 37 |
+
const docs = await splitter.createDocuments([text]);
|
| 38 |
+
await vectorStore.addDocuments(docs);
|
| 39 |
+
return vectorStore;
|
| 40 |
+
}
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 2. Retrieval Implementation
|
| 44 |
+
Create a retriever that fetches relevant context.
|
| 45 |
+
|
| 46 |
+
```typescript
|
| 47 |
+
// Create a retriever from the store
|
| 48 |
+
const retriever = vectorStore.asRetriever({
|
| 49 |
+
k: 3, // Top 3 results
|
| 50 |
+
searchType: "similarity", // or "mmr" for diversity
|
| 51 |
+
});
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 3. The RAG Chain (Generation)
|
| 55 |
+
Combine retrieval with generation.
|
| 56 |
+
|
| 57 |
+
```typescript
|
| 58 |
+
import { ChatOpenAI } from "@langchain/openai";
|
| 59 |
+
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
| 60 |
+
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
|
| 61 |
+
import { createRetrievalChain } from "langchain/chains/retrieval";
|
| 62 |
+
|
| 63 |
+
const model = new ChatOpenAI({ modelName: "gpt-4o" });
|
| 64 |
+
|
| 65 |
+
const prompt = ChatPromptTemplate.fromTemplate(`
|
| 66 |
+
Answer the user's question based ONLY on the following context:
|
| 67 |
+
|
| 68 |
+
<context>
|
| 69 |
+
{context}
|
| 70 |
+
</context>
|
| 71 |
+
|
| 72 |
+
Question: {input}
|
| 73 |
+
`);
|
| 74 |
+
|
| 75 |
+
// 1. Create the document combining chain
|
| 76 |
+
const combineDocsChain = await createStuffDocumentsChain({
|
| 77 |
+
llm: model,
|
| 78 |
+
prompt,
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
// 2. Create the full retrieval chain
|
| 82 |
+
const ragChain = await createRetrievalChain({
|
| 83 |
+
retriever,
|
| 84 |
+
combineDocsChain,
|
| 85 |
+
});
|
| 86 |
+
|
| 87 |
+
// Usage
|
| 88 |
+
export async function askQuestion(question: string) {
|
| 89 |
+
const response = await ragChain.invoke({
|
| 90 |
+
input: question,
|
| 91 |
+
});
|
| 92 |
+
return response.answer;
|
| 93 |
+
}
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### 4. Advanced: Hybrid Search
|
| 97 |
+
For production, combine keyword search (BM25) with semantic search (Vectors) for better accuracy.
|
| 98 |
+
|
| 99 |
+
```typescript
|
| 100 |
+
// Conceptual example for Supabase/Postgres
|
| 101 |
+
// supabase.rpc('hybrid_search', { query_text: ..., match_threshold: ... })
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
## Constraints
|
| 105 |
+
- **Chunk Quality**: If chunks are too small, context is lost. If too large, noise increases.
|
| 106 |
+
- **Latency**: Embedding + Vector Search + Generation = High Latency. Use caching and streaming.
|
| 107 |
+
- **Relevance**: "Garbage In, Garbage Out". Ensure the retrieved context is actually relevant before passing to LLM.
|
| 108 |
+
|
| 109 |
+
## Expected Output
|
| 110 |
+
A functional RAG pipeline where a user asks a question, the system retrieves relevant docs, and the LLM answers accurately citing the sources.
|
TRAE-Skills/ai_engineering/Recommender_Systems_Collaborative_Filtering.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Recommender Systems with Collaborative Filtering
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To build recommendation systems using collaborative filtering techniques for personalized user experiences.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building product recommendations for e-commerce
|
| 8 |
+
- For movie/music/content recommendations
|
| 9 |
+
- When personalizing user experiences
|
| 10 |
+
- For "Customers who bought this also bought" features
|
| 11 |
+
- When you have user-item interaction data
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. User-Based Collaborative Filtering
|
| 16 |
+
Recommend items based on similar users.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
import numpy as np
|
| 20 |
+
import pandas as pd
|
| 21 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 22 |
+
|
| 23 |
+
# Sample user-item ratings matrix
|
| 24 |
+
ratings_data = {
|
| 25 |
+
'User1': [5, 4, 0, 0, 3],
|
| 26 |
+
'User2': [0, 5, 4, 0, 0],
|
| 27 |
+
'User3': [4, 0, 5, 3, 0],
|
| 28 |
+
'User4': [0, 0, 4, 5, 4],
|
| 29 |
+
'User5': [3, 0, 0, 4, 5]
|
| 30 |
+
}
|
| 31 |
+
items = ['ItemA', 'ItemB', 'ItemC', 'ItemD', 'ItemE']
|
| 32 |
+
|
| 33 |
+
ratings_matrix = pd.DataFrame(ratings_data, index=items).T
|
| 34 |
+
|
| 35 |
+
def user_based_cf(user_id, ratings_matrix, n_recommendations=3):
|
| 36 |
+
# Calculate user similarity
|
| 37 |
+
user_similarity = cosine_similarity(ratings_matrix)
|
| 38 |
+
user_similarity_df = pd.DataFrame(user_similarity, index=ratings_matrix.index, columns=ratings_matrix.index)
|
| 39 |
+
|
| 40 |
+
# Get similar users
|
| 41 |
+
similar_users = user_similarity_df[user_id].sort_values(ascending=False)[1:]
|
| 42 |
+
|
| 43 |
+
# Predict ratings
|
| 44 |
+
user_ratings = ratings_matrix.loc[user_id]
|
| 45 |
+
predicted_ratings = pd.Series(dtype='float64')
|
| 46 |
+
|
| 47 |
+
for item in ratings_matrix.columns:
|
| 48 |
+
if user_ratings[item] == 0:
|
| 49 |
+
weighted_sum = 0
|
| 50 |
+
similarity_sum = 0
|
| 51 |
+
for similar_user in similar_users.index:
|
| 52 |
+
if ratings_matrix.loc[similar_user, item] > 0:
|
| 53 |
+
weighted_sum += similar_users[similar_user] * ratings_matrix.loc[similar_user, item]
|
| 54 |
+
similarity_sum += similar_users[similar_user]
|
| 55 |
+
if similarity_sum > 0:
|
| 56 |
+
predicted_ratings[item] = weighted_sum / similarity_sum
|
| 57 |
+
|
| 58 |
+
return predicted_ratings.sort_values(ascending=False).head(n_recommendations)
|
| 59 |
+
|
| 60 |
+
# Usage
|
| 61 |
+
recommendations = user_based_cf('User1', ratings_matrix)
|
| 62 |
+
print("Recommendations for User1:", recommendations)
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### 2. Item-Based Collaborative Filtering
|
| 66 |
+
Recommend items similar to those the user liked.
|
| 67 |
+
|
| 68 |
+
```python
|
| 69 |
+
def item_based_cf(user_id, ratings_matrix, n_recommendations=3):
|
| 70 |
+
# Calculate item similarity
|
| 71 |
+
item_similarity = cosine_similarity(ratings_matrix.T)
|
| 72 |
+
item_similarity_df = pd.DataFrame(item_similarity, index=ratings_matrix.columns, columns=ratings_matrix.columns)
|
| 73 |
+
|
| 74 |
+
user_ratings = ratings_matrix.loc[user_id]
|
| 75 |
+
predicted_ratings = pd.Series(dtype='float64')
|
| 76 |
+
|
| 77 |
+
for item in ratings_matrix.columns:
|
| 78 |
+
if user_ratings[item] == 0:
|
| 79 |
+
weighted_sum = 0
|
| 80 |
+
similarity_sum = 0
|
| 81 |
+
for rated_item in user_ratings.index:
|
| 82 |
+
if user_ratings[rated_item] > 0:
|
| 83 |
+
weighted_sum += item_similarity_df[item][rated_item] * user_ratings[rated_item]
|
| 84 |
+
similarity_sum += item_similarity_df[item][rated_item]
|
| 85 |
+
if similarity_sum > 0:
|
| 86 |
+
predicted_ratings[item] = weighted_sum / similarity_sum
|
| 87 |
+
|
| 88 |
+
return predicted_ratings.sort_values(ascending=False).head(n_recommendations)
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 3. Matrix Factorization with SVD
|
| 92 |
+
Use Singular Value Decomposition for better recommendations.
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
from scipy.sparse.linalg import svds
|
| 96 |
+
|
| 97 |
+
def svd_recommender(ratings_matrix, user_id, n_recommendations=3, n_factors=2):
|
| 98 |
+
# Convert to numpy array and center
|
| 99 |
+
ratings = ratings_matrix.values
|
| 100 |
+
user_ratings_mean = np.mean(ratings, axis=1)
|
| 101 |
+
ratings_demeaned = ratings - user_ratings_mean.reshape(-1, 1)
|
| 102 |
+
|
| 103 |
+
# Perform SVD
|
| 104 |
+
U, sigma, Vt = svds(ratings_demeaned, k=n_factors)
|
| 105 |
+
sigma = np.diag(sigma)
|
| 106 |
+
|
| 107 |
+
# Reconstruct ratings
|
| 108 |
+
predicted_ratings = np.dot(np.dot(U, sigma), Vt) + user_ratings_mean.reshape(-1, 1)
|
| 109 |
+
predicted_ratings_df = pd.DataFrame(predicted_ratings, index=ratings_matrix.index, columns=ratings_matrix.columns)
|
| 110 |
+
|
| 111 |
+
# Get recommendations
|
| 112 |
+
user_ratings = ratings_matrix.loc[user_id]
|
| 113 |
+
recommendations = predicted_ratings_df.loc[user_id][user_ratings == 0].sort_values(ascending=False).head(n_recommendations)
|
| 114 |
+
|
| 115 |
+
return recommendations
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### 4. Using Surprise Library
|
| 119 |
+
Use the Surprise library for recommendation systems.
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
from surprise import Dataset, Reader, SVD, KNNBasic
|
| 123 |
+
from surprise.model_selection import train_test_split
|
| 124 |
+
from surprise.metrics import accuracy
|
| 125 |
+
|
| 126 |
+
# Load data
|
| 127 |
+
ratings_dict = {
|
| 128 |
+
'userID': [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
|
| 129 |
+
'itemID': [1, 2, 3, 1, 2, 4, 2, 3, 4, 1, 3, 4],
|
| 130 |
+
'rating': [5, 4, 3, 5, 4, 4, 4, 5, 5, 3, 5, 4]
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
reader = Reader(rating_scale=(1, 5))
|
| 134 |
+
data = Dataset.load_from_df(pd.DataFrame(ratings_dict)[['userID', 'itemID', 'rating']], reader)
|
| 135 |
+
|
| 136 |
+
# Split data
|
| 137 |
+
trainset, testset = train_test_split(data, test_size=0.25)
|
| 138 |
+
|
| 139 |
+
# Train SVD model
|
| 140 |
+
algo = SVD()
|
| 141 |
+
algo.fit(trainset)
|
| 142 |
+
|
| 143 |
+
# Test
|
| 144 |
+
predictions = algo.test(testset)
|
| 145 |
+
print("RMSE:", accuracy.rmse(predictions))
|
| 146 |
+
|
| 147 |
+
# Get recommendations for a user
|
| 148 |
+
def get_surprise_recommendations(algo, user_id, item_ids, n_recommendations=3):
|
| 149 |
+
predictions = []
|
| 150 |
+
for item_id in item_ids:
|
| 151 |
+
pred = algo.predict(user_id, item_id)
|
| 152 |
+
predictions.append((item_id, pred.est))
|
| 153 |
+
|
| 154 |
+
predictions.sort(key=lambda x: x[1], reverse=True)
|
| 155 |
+
return predictions[:n_recommendations]
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
### 5. Hybrid Recommender
|
| 159 |
+
Combine collaborative filtering with content-based filtering.
|
| 160 |
+
|
| 161 |
+
```python
|
| 162 |
+
def hybrid_recommender(user_id, ratings_matrix, item_features, n_recommendations=3):
|
| 163 |
+
# Get collaborative filtering recommendations
|
| 164 |
+
cf_recs = user_based_cf(user_id, ratings_matrix, n_recommendations=5)
|
| 165 |
+
|
| 166 |
+
# Get content-based recommendations (using item features)
|
| 167 |
+
item_similarity = cosine_similarity(item_features)
|
| 168 |
+
user_rated_items = ratings_matrix.loc[user_id][ratings_matrix.loc[user_id] > 0].index
|
| 169 |
+
|
| 170 |
+
content_recs = pd.Series(dtype='float64')
|
| 171 |
+
for item in ratings_matrix.columns:
|
| 172 |
+
if item not in user_rated_items:
|
| 173 |
+
sim_sum = 0
|
| 174 |
+
for rated_item in user_rated_items:
|
| 175 |
+
sim_sum += item_similarity[list(ratings_matrix.columns).index(item)][list(ratings_matrix.columns).index(rated_item)]
|
| 176 |
+
content_recs[item] = sim_sum / len(user_rated_items)
|
| 177 |
+
|
| 178 |
+
content_recs = content_recs.sort_values(ascending=False).head(5)
|
| 179 |
+
|
| 180 |
+
# Combine recommendations (simple average)
|
| 181 |
+
combined_recs = pd.Series(dtype='float64')
|
| 182 |
+
for item in set(cf_recs.index).union(set(content_recs.index)):
|
| 183 |
+
cf_score = cf_recs.get(item, 0)
|
| 184 |
+
content_score = content_recs.get(item, 0)
|
| 185 |
+
combined_recs[item] = (cf_score + content_score) / 2
|
| 186 |
+
|
| 187 |
+
return combined_recs.sort_values(ascending=False).head(n_recommendations)
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
## Best Practices
|
| 191 |
+
- **Data Preprocessing**: Clean and preprocess your data thoroughly
|
| 192 |
+
- **Cold Start**: Handle new users/items with hybrid approaches
|
| 193 |
+
- **Evaluation**: Use RMSE, MAE, or ranking metrics (NDCG, MAP)
|
| 194 |
+
- **Scalability**: Use matrix factorization or deep learning for large datasets
|
| 195 |
+
- **Diversity**: Ensure recommendations are diverse, not just similar
|
| 196 |
+
- **Freshness**: Update recommendations regularly with new data
|
| 197 |
+
- **A/B Testing**: Always test recommendations with real users
|
| 198 |
+
- **Privacy**: Be mindful of user privacy and data usage
|
TRAE-Skills/ai_engineering/Reinforcement_Learning_Basics.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Reinforcement Learning Basics
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To implement intelligent agents that learn optimal behaviors through interaction with an environment, using rewards and penalties to guide decision-making.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building game-playing AI
|
| 8 |
+
- When optimizing control systems (robotics, autonomous vehicles)
|
| 9 |
+
- When solving sequential decision problems
|
| 10 |
+
- When implementing recommendation systems with user feedback
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Q-Learning Implementation
|
| 15 |
+
Implement basic Q-learning algorithm.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
import numpy as np
|
| 19 |
+
from collections import defaultdict
|
| 20 |
+
|
| 21 |
+
class QLearningAgent:
|
| 22 |
+
def __init__(self, state_size, action_size, learning_rate=0.1, discount_factor=0.95, epsilon=1.0):
|
| 23 |
+
self.state_size = state_size
|
| 24 |
+
self.action_size = action_size
|
| 25 |
+
self.learning_rate = learning_rate
|
| 26 |
+
self.discount_factor = discount_factor
|
| 27 |
+
self.epsilon = epsilon
|
| 28 |
+
self.epsilon_decay = 0.995
|
| 29 |
+
self.epsilon_min = 0.01
|
| 30 |
+
self.q_table = defaultdict(lambda: np.zeros(action_size))
|
| 31 |
+
|
| 32 |
+
def choose_action(self, state):
|
| 33 |
+
"""Choose action using epsilon-greedy policy."""
|
| 34 |
+
if np.random.random() <= self.epsilon:
|
| 35 |
+
return np.random.choice(self.action_size)
|
| 36 |
+
else:
|
| 37 |
+
return np.argmax(self.q_table[state])
|
| 38 |
+
|
| 39 |
+
def learn(self, state, action, reward, next_state, done):
|
| 40 |
+
"""Update Q-value using Q-learning formula."""
|
| 41 |
+
current_q = self.q_table[state][action]
|
| 42 |
+
|
| 43 |
+
if done:
|
| 44 |
+
max_next_q = 0
|
| 45 |
+
else:
|
| 46 |
+
max_next_q = np.max(self.q_table[next_state])
|
| 47 |
+
|
| 48 |
+
new_q = current_q + self.learning_rate * (reward + self.discount_factor * max_next_q - current_q)
|
| 49 |
+
self.q_table[state][action] = new_q
|
| 50 |
+
|
| 51 |
+
# Decay epsilon
|
| 52 |
+
if self.epsilon > self.epsilon_min:
|
| 53 |
+
self.epsilon *= self.epsilon_decay
|
| 54 |
+
|
| 55 |
+
def save_q_table(self, filename):
|
| 56 |
+
"""Save Q-table to file."""
|
| 57 |
+
dict_q_table = dict(self.q_table)
|
| 58 |
+
np.save(filename, dict_q_table)
|
| 59 |
+
|
| 60 |
+
def load_q_table(self, filename):
|
| 61 |
+
"""Load Q-table from file."""
|
| 62 |
+
dict_q_table = np.load(filename, allow_pickle=True).item()
|
| 63 |
+
self.q_table = defaultdict(lambda: np.zeros(self.action_size), dict_q_table)
|
| 64 |
+
|
| 65 |
+
# Simple grid world environment
|
| 66 |
+
class GridWorld:
|
| 67 |
+
def __init__(self, size=5):
|
| 68 |
+
self.size = size
|
| 69 |
+
self.start = (0, 0)
|
| 70 |
+
self.goal = (size-1, size-1)
|
| 71 |
+
self.obstacles = [(2, 2), (3, 1), (1, 3)]
|
| 72 |
+
self.current_state = self.start
|
| 73 |
+
self.actions = ['up', 'down', 'left', 'right']
|
| 74 |
+
|
| 75 |
+
def reset(self):
|
| 76 |
+
self.current_state = self.start
|
| 77 |
+
return self.current_state
|
| 78 |
+
|
| 79 |
+
def step(self, action):
|
| 80 |
+
row, col = self.current_state
|
| 81 |
+
|
| 82 |
+
if action == 'up':
|
| 83 |
+
new_row, new_col = row - 1, col
|
| 84 |
+
elif action == 'down':
|
| 85 |
+
new_row, new_col = row + 1, col
|
| 86 |
+
elif action == 'left':
|
| 87 |
+
new_row, new_col = row, col - 1
|
| 88 |
+
elif action == 'right':
|
| 89 |
+
new_row, new_col = row, col + 1
|
| 90 |
+
|
| 91 |
+
# Check boundaries
|
| 92 |
+
if 0 <= new_row < self.size and 0 <= new_col < self.size:
|
| 93 |
+
if (new_row, new_col) not in self.obstacles:
|
| 94 |
+
self.current_state = (new_row, new_col)
|
| 95 |
+
|
| 96 |
+
# Calculate reward
|
| 97 |
+
if self.current_state == self.goal:
|
| 98 |
+
reward = 10
|
| 99 |
+
done = True
|
| 100 |
+
else:
|
| 101 |
+
reward = -1
|
| 102 |
+
done = False
|
| 103 |
+
|
| 104 |
+
return self.current_state, reward, done
|
| 105 |
+
|
| 106 |
+
# Training
|
| 107 |
+
env = GridWorld()
|
| 108 |
+
agent = QLearningAgent(state_size=env.size*env.size, action_size=4)
|
| 109 |
+
|
| 110 |
+
episodes = 1000
|
| 111 |
+
for episode in range(episodes):
|
| 112 |
+
state = env.reset()
|
| 113 |
+
done = False
|
| 114 |
+
total_reward = 0
|
| 115 |
+
|
| 116 |
+
while not done:
|
| 117 |
+
action_idx = agent.choose_action(state)
|
| 118 |
+
action = env.actions[action_idx]
|
| 119 |
+
next_state, reward, done = env.step(action)
|
| 120 |
+
|
| 121 |
+
agent.learn(state, action_idx, reward, next_state, done)
|
| 122 |
+
state = next_state
|
| 123 |
+
total_reward += reward
|
| 124 |
+
|
| 125 |
+
if episode % 100 == 0:
|
| 126 |
+
print(f"Episode {episode}, Total Reward: {total_reward}, Epsilon: {agent.epsilon:.3f}")
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### 2. Deep Q-Network (DQN)
|
| 130 |
+
Implement deep Q-learning with neural networks.
|
| 131 |
+
|
| 132 |
+
```python
|
| 133 |
+
import torch
|
| 134 |
+
import torch.nn as nn
|
| 135 |
+
import torch.optim as optim
|
| 136 |
+
from collections import deque
|
| 137 |
+
import random
|
| 138 |
+
|
| 139 |
+
class DQN(nn.Module):
|
| 140 |
+
def __init__(self, state_size, action_size):
|
| 141 |
+
super(DQN, self).__init__()
|
| 142 |
+
self.fc1 = nn.Linear(state_size, 64)
|
| 143 |
+
self.fc2 = nn.Linear(64, 64)
|
| 144 |
+
self.fc3 = nn.Linear(64, action_size)
|
| 145 |
+
|
| 146 |
+
def forward(self, x):
|
| 147 |
+
x = torch.relu(self.fc1(x))
|
| 148 |
+
x = torch.relu(self.fc2(x))
|
| 149 |
+
return self.fc3(x)
|
| 150 |
+
|
| 151 |
+
class DQNAgent:
|
| 152 |
+
def __init__(self, state_size, action_size):
|
| 153 |
+
self.state_size = state_size
|
| 154 |
+
self.action_size = action_size
|
| 155 |
+
self.memory = deque(maxlen=10000)
|
| 156 |
+
self.gamma = 0.95
|
| 157 |
+
self.epsilon = 1.0
|
| 158 |
+
self.epsilon_min = 0.01
|
| 159 |
+
self.epsilon_decay = 0.995
|
| 160 |
+
self.learning_rate = 0.001
|
| 161 |
+
self.batch_size = 32
|
| 162 |
+
|
| 163 |
+
self.model = DQN(state_size, action_size)
|
| 164 |
+
self.optimizer = optim.Adam(self.model.parameters(), lr=self.learning_rate)
|
| 165 |
+
|
| 166 |
+
def remember(self, state, action, reward, next_state, done):
|
| 167 |
+
"""Store experience in replay memory."""
|
| 168 |
+
self.memory.append((state, action, reward, next_state, done))
|
| 169 |
+
|
| 170 |
+
def act(self, state):
|
| 171 |
+
"""Choose action using epsilon-greedy policy."""
|
| 172 |
+
if np.random.random() <= self.epsilon:
|
| 173 |
+
return random.randrange(self.action_size)
|
| 174 |
+
|
| 175 |
+
state = torch.FloatTensor(state).unsqueeze(0)
|
| 176 |
+
q_values = self.model(state)
|
| 177 |
+
return np.argmax(q_values.detach().numpy()[0])
|
| 178 |
+
|
| 179 |
+
def replay(self):
|
| 180 |
+
"""Train on a batch of experiences."""
|
| 181 |
+
if len(self.memory) < self.batch_size:
|
| 182 |
+
return
|
| 183 |
+
|
| 184 |
+
minibatch = random.sample(self.memory, self.batch_size)
|
| 185 |
+
states = torch.FloatTensor([t[0] for t in minibatch])
|
| 186 |
+
actions = torch.LongTensor([t[1] for t in minibatch])
|
| 187 |
+
rewards = torch.FloatTensor([t[2] for t in minibatch])
|
| 188 |
+
next_states = torch.FloatTensor([t[3] for t in minibatch])
|
| 189 |
+
dones = torch.FloatTensor([t[4] for t in minibatch])
|
| 190 |
+
|
| 191 |
+
current_q_values = self.model(states).gather(1, actions.unsqueeze(1))
|
| 192 |
+
next_q_values = self.model(next_states).max(1)[0].detach()
|
| 193 |
+
target_q_values = rewards + (1 - dones) * self.gamma * next_q_values
|
| 194 |
+
|
| 195 |
+
loss = nn.MSELoss()(current_q_values.squeeze(), target_q_values)
|
| 196 |
+
|
| 197 |
+
self.optimizer.zero_grad()
|
| 198 |
+
loss.backward()
|
| 199 |
+
self.optimizer.step()
|
| 200 |
+
|
| 201 |
+
if self.epsilon > self.epsilon_min:
|
| 202 |
+
self.epsilon *= self.epsilon_decay
|
| 203 |
+
|
| 204 |
+
def save(self, filename):
|
| 205 |
+
"""Save model."""
|
| 206 |
+
torch.save(self.model.state_dict(), filename)
|
| 207 |
+
|
| 208 |
+
def load(self, filename):
|
| 209 |
+
"""Load model."""
|
| 210 |
+
self.model.load_state_dict(torch.load(filename))
|
| 211 |
+
|
| 212 |
+
# Usage
|
| 213 |
+
env = GridWorld()
|
| 214 |
+
state_size = env.size * env.size
|
| 215 |
+
action_size = 4
|
| 216 |
+
|
| 217 |
+
agent = DQNAgent(state_size, action_size)
|
| 218 |
+
|
| 219 |
+
episodes = 500
|
| 220 |
+
for episode in range(episodes):
|
| 221 |
+
state = env.reset()
|
| 222 |
+
state_flat = np.array(state).flatten()
|
| 223 |
+
done = False
|
| 224 |
+
total_reward = 0
|
| 225 |
+
|
| 226 |
+
while not done:
|
| 227 |
+
action = agent.act(state_flat)
|
| 228 |
+
next_state, reward, done = env.step(env.actions[action])
|
| 229 |
+
next_state_flat = np.array(next_state).flatten()
|
| 230 |
+
|
| 231 |
+
agent.remember(state_flat, action, reward, next_state_flat, done)
|
| 232 |
+
state = next_state
|
| 233 |
+
state_flat = next_state_flat
|
| 234 |
+
total_reward += reward
|
| 235 |
+
|
| 236 |
+
agent.replay()
|
| 237 |
+
|
| 238 |
+
if episode % 50 == 0:
|
| 239 |
+
print(f"Episode {episode}, Total Reward: {total_reward}, Epsilon: {agent.epsilon:.3f}")
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
### 3. Policy Gradient Implementation
|
| 243 |
+
Implement REINFORCE algorithm.
|
| 244 |
+
|
| 245 |
+
```python
|
| 246 |
+
class PolicyGradientAgent:
|
| 247 |
+
def __init__(self, state_size, action_size, learning_rate=0.01):
|
| 248 |
+
self.state_size = state_size
|
| 249 |
+
self.action_size = action_size
|
| 250 |
+
self.gamma = 0.99
|
| 251 |
+
|
| 252 |
+
# Policy network
|
| 253 |
+
self.policy = nn.Sequential(
|
| 254 |
+
nn.Linear(state_size, 128),
|
| 255 |
+
nn.ReLU(),
|
| 256 |
+
nn.Linear(128, action_size),
|
| 257 |
+
nn.Softmax(dim=-1)
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
self.optimizer = optim.Adam(self.policy.parameters(), lr=learning_rate)
|
| 261 |
+
|
| 262 |
+
def choose_action(self, state):
|
| 263 |
+
"""Choose action based on policy."""
|
| 264 |
+
state = torch.FloatTensor(state)
|
| 265 |
+
probs = self.policy(state)
|
| 266 |
+
|
| 267 |
+
# Sample from probability distribution
|
| 268 |
+
action_dist = torch.distributions.Categorical(probs)
|
| 269 |
+
action = action_dist.sample()
|
| 270 |
+
|
| 271 |
+
return action.item(), action_dist.log_prob(action)
|
| 272 |
+
|
| 273 |
+
def update_policy(self, rewards, log_probs):
|
| 274 |
+
"""Update policy using REINFORCE."""
|
| 275 |
+
returns = []
|
| 276 |
+
R = 0
|
| 277 |
+
|
| 278 |
+
# Calculate discounted returns
|
| 279 |
+
for r in reversed(rewards):
|
| 280 |
+
R = r + self.gamma * R
|
| 281 |
+
returns.insert(0, R)
|
| 282 |
+
|
| 283 |
+
returns = torch.FloatTensor(returns)
|
| 284 |
+
log_probs = torch.stack(log_probs)
|
| 285 |
+
|
| 286 |
+
# Calculate loss
|
| 287 |
+
policy_loss = []
|
| 288 |
+
for log_prob, R in zip(log_probs, returns):
|
| 289 |
+
policy_loss.append(-log_prob * R)
|
| 290 |
+
|
| 291 |
+
policy_loss = torch.stack(policy_loss).sum()
|
| 292 |
+
|
| 293 |
+
# Update policy
|
| 294 |
+
self.optimizer.zero_grad()
|
| 295 |
+
policy_loss.backward()
|
| 296 |
+
self.optimizer.step()
|
| 297 |
+
|
| 298 |
+
# Training
|
| 299 |
+
env = GridWorld()
|
| 300 |
+
agent = PolicyGradientAgent(state_size=env.size*env.size, action_size=4)
|
| 301 |
+
|
| 302 |
+
episodes = 1000
|
| 303 |
+
for episode in range(episodes):
|
| 304 |
+
state = env.reset()
|
| 305 |
+
state_flat = np.array(state).flatten()
|
| 306 |
+
done = False
|
| 307 |
+
rewards = []
|
| 308 |
+
log_probs = []
|
| 309 |
+
|
| 310 |
+
while not done:
|
| 311 |
+
action, log_prob = agent.choose_action(state_flat)
|
| 312 |
+
next_state, reward, done = env.step(env.actions[action])
|
| 313 |
+
|
| 314 |
+
rewards.append(reward)
|
| 315 |
+
log_probs.append(log_prob)
|
| 316 |
+
|
| 317 |
+
state = next_state
|
| 318 |
+
state_flat = np.array(state).flatten()
|
| 319 |
+
|
| 320 |
+
agent.update_policy(rewards, log_probs)
|
| 321 |
+
|
| 322 |
+
if episode % 100 == 0:
|
| 323 |
+
print(f"Episode {episode}, Total Reward: {sum(rewards)}")
|
| 324 |
+
```
|
| 325 |
+
|
| 326 |
+
## Constraints
|
| 327 |
+
- **Training Time**: RL requires many episodes to converge
|
| 328 |
+
- **Hyperparameter Sensitivity**: Performance highly depends on hyperparameter tuning
|
| 329 |
+
- **Sample Efficiency**: Traditional RL is sample inefficient
|
| 330 |
+
- **Exploration vs. Exploitation**: Balancing exploration and exploitation is crucial
|
| 331 |
+
- **Reward Design**: Poor reward design leads to unexpected behaviors
|
| 332 |
+
- **Computational Resources**: Deep RL requires significant computational power
|
| 333 |
+
|
| 334 |
+
## Expected Output
|
| 335 |
+
Intelligent agents that learn optimal policies through experience, capable of making sequential decisions in complex environments.
|
TRAE-Skills/ai_engineering/Speech_to_Text_Whisper.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Speech-to-Text Implementation (Whisper)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To implement robust audio transcription and translation using OpenAI's Whisper model, handling large files, various formats, and specialized vocabulary.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When converting user voice commands to text in a web/mobile app.
|
| 8 |
+
- When transcribing long-form content (podcasts, meetings).
|
| 9 |
+
- When you need high-accuracy transcription for non-English languages.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Handling Large Files (Splitting with FFmpeg)
|
| 14 |
+
Whisper API has a 25MB limit. Use FFmpeg to split or compress.
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
# Split audio into 10-minute segments
|
| 18 |
+
ffmpeg -i input.mp3 -f segment -segment_time 600 -c copy out%03d.mp3
|
| 19 |
+
|
| 20 |
+
# Compress to low-bitrate mono MP3 (saves space while keeping speech clear)
|
| 21 |
+
ffmpeg -i input.wav -ac 1 -ar 16000 -ab 32k output.mp3
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 2. Basic Transcription (Node.js)
|
| 25 |
+
Integrate the OpenAI SDK for transcription.
|
| 26 |
+
|
| 27 |
+
```typescript
|
| 28 |
+
import fs from 'fs';
|
| 29 |
+
import OpenAI from 'openai';
|
| 30 |
+
|
| 31 |
+
const openai = new OpenAI();
|
| 32 |
+
|
| 33 |
+
async function transcribe(filePath: string) {
|
| 34 |
+
const response = await openai.audio.transcriptions.create({
|
| 35 |
+
file: fs.createReadStream(filePath),
|
| 36 |
+
model: "whisper-1",
|
| 37 |
+
language: "en", // Optional but improves accuracy
|
| 38 |
+
response_format: "verbose_json", // Gives timestamps
|
| 39 |
+
prompt: "The transcript is about a software architecture meeting.", // Helps with context/acronyms
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
return response;
|
| 43 |
+
}
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 3. Handling Timestamps & Subtitles
|
| 47 |
+
Use the `srt` or `vtt` format for video captions.
|
| 48 |
+
|
| 49 |
+
```typescript
|
| 50 |
+
const srtTranscription = await openai.audio.transcriptions.create({
|
| 51 |
+
file: fs.createReadStream("video_audio.mp3"),
|
| 52 |
+
model: "whisper-1",
|
| 53 |
+
response_format: "srt",
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
fs.writeFileSync("subtitles.srt", srtTranscription);
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### 4. Specialized Vocabulary (Prompts)
|
| 60 |
+
Pass technical terms or proper nouns in the `prompt` parameter to ensure correct spelling.
|
| 61 |
+
|
| 62 |
+
```typescript
|
| 63 |
+
// Prompt example to ensure technical terms are spelled correctly
|
| 64 |
+
const prompt = "The speakers discuss Kubernetes, Istio, and gRPC.";
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 5. Local Execution (Python/Faster-Whisper)
|
| 68 |
+
For privacy or high volume, use `faster-whisper` locally.
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
from faster_whisper import WhisperModel
|
| 72 |
+
|
| 73 |
+
model_size = "large-v3"
|
| 74 |
+
model = WhisperModel(model_size, device="cuda", compute_type="float16")
|
| 75 |
+
|
| 76 |
+
segments, info = model.transcribe("audio.mp3", beam_size=5)
|
| 77 |
+
|
| 78 |
+
for segment in segments:
|
| 79 |
+
print("[%.2fs -> %.2fs] %s" % (segment.start, segment.end, segment.text))
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Constraints
|
| 83 |
+
- **File Size**: 25MB limit on API. Must split/compress manually.
|
| 84 |
+
- **Latency**: Not real-time (usually 5-15s for a 1-minute clip).
|
| 85 |
+
- **Privacy**: API usage sends data to OpenAI. Use local Whisper for sensitive data.
|
| 86 |
+
|
| 87 |
+
## Expected Output
|
| 88 |
+
Highly accurate, time-stamped text representing the spoken audio, optionally translated into English.
|
TRAE-Skills/ai_engineering/Stream_Responses.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Stream Responses
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To deliver LLM responses in real-time chunks, improving user experience by providing immediate feedback and reducing perceived latency.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building chat interfaces
|
| 8 |
+
- When processing long responses
|
| 9 |
+
- When users need immediate feedback
|
| 10 |
+
- When implementing real-time AI interactions
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Basic Streaming Setup
|
| 15 |
+
Implement basic streaming with OpenAI API.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
client = OpenAI()
|
| 22 |
+
|
| 23 |
+
def stream_response(prompt, model="gpt-4"):
|
| 24 |
+
"""Stream a basic response from the LLM."""
|
| 25 |
+
print("Assistant: ", end="", flush=True)
|
| 26 |
+
|
| 27 |
+
stream = client.chat.completions.create(
|
| 28 |
+
model=model,
|
| 29 |
+
messages=[{"role": "user", "content": prompt}],
|
| 30 |
+
stream=True
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
for chunk in stream:
|
| 34 |
+
if chunk.choices[0].delta.content is not None:
|
| 35 |
+
content = chunk.choices[0].delta.content
|
| 36 |
+
print(content, end="", flush=True)
|
| 37 |
+
yield content
|
| 38 |
+
|
| 39 |
+
print() # New line after completion
|
| 40 |
+
|
| 41 |
+
# Usage
|
| 42 |
+
for chunk in stream_response("Explain quantum computing in simple terms"):
|
| 43 |
+
pass # Process chunks if needed
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 2. Streaming with Buffering
|
| 47 |
+
Implement buffering for more controlled output.
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
def buffered_stream(prompt, buffer_size=10, delay=0.1):
|
| 51 |
+
"""Stream with buffering to prevent choppy output."""
|
| 52 |
+
import time
|
| 53 |
+
|
| 54 |
+
buffer = []
|
| 55 |
+
stream = client.chat.completions.create(
|
| 56 |
+
model="gpt-4",
|
| 57 |
+
messages=[{"role": "user", "content": prompt}],
|
| 58 |
+
stream=True
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
for chunk in stream:
|
| 62 |
+
if chunk.choices[0].delta.content is not None:
|
| 63 |
+
content = chunk.choices[0].delta.content
|
| 64 |
+
buffer.append(content)
|
| 65 |
+
|
| 66 |
+
if len(buffer) >= buffer_size:
|
| 67 |
+
print("".join(buffer), end="", flush=True)
|
| 68 |
+
buffer = []
|
| 69 |
+
time.sleep(delay)
|
| 70 |
+
|
| 71 |
+
# Print remaining buffer
|
| 72 |
+
if buffer:
|
| 73 |
+
print("".join(buffer), end="", flush=True)
|
| 74 |
+
print()
|
| 75 |
+
|
| 76 |
+
# Usage
|
| 77 |
+
buffered_stream("Write a short story about a robot learning to paint")
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### 3. Async Streaming
|
| 81 |
+
Implement asynchronous streaming for better performance.
|
| 82 |
+
|
| 83 |
+
```python
|
| 84 |
+
import asyncio
|
| 85 |
+
from openai import AsyncOpenAI
|
| 86 |
+
|
| 87 |
+
async_client = AsyncOpenAI()
|
| 88 |
+
|
| 89 |
+
async def async_stream(prompt):
|
| 90 |
+
"""Stream responses asynchronously."""
|
| 91 |
+
stream = await async_client.chat.completions.create(
|
| 92 |
+
model="gpt-4",
|
| 93 |
+
messages=[{"role": "user", "content": prompt}],
|
| 94 |
+
stream=True
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
full_response = ""
|
| 98 |
+
async for chunk in stream:
|
| 99 |
+
if chunk.choices[0].delta.content is not None:
|
| 100 |
+
content = chunk.choices[0].delta.content
|
| 101 |
+
print(content, end="", flush=True)
|
| 102 |
+
full_response += content
|
| 103 |
+
|
| 104 |
+
print()
|
| 105 |
+
return full_response
|
| 106 |
+
|
| 107 |
+
# Usage
|
| 108 |
+
async def main():
|
| 109 |
+
response = await async_stream("What are the benefits of async programming?")
|
| 110 |
+
|
| 111 |
+
asyncio.run(main())
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
### 4. Multi-User Streaming
|
| 115 |
+
Handle streaming for multiple concurrent users.
|
| 116 |
+
|
| 117 |
+
```python
|
| 118 |
+
class StreamingManager:
|
| 119 |
+
def __init__(self):
|
| 120 |
+
self.active_streams = {}
|
| 121 |
+
|
| 122 |
+
async def stream_to_user(self, user_id, prompt):
|
| 123 |
+
"""Stream response to a specific user."""
|
| 124 |
+
self.active_streams[user_id] = True
|
| 125 |
+
|
| 126 |
+
try:
|
| 127 |
+
stream = await async_client.chat.completions.create(
|
| 128 |
+
model="gpt-4",
|
| 129 |
+
messages=[{"role": "user", "content": prompt}],
|
| 130 |
+
stream=True
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
async for chunk in stream:
|
| 134 |
+
if not self.active_streams.get(user_id, False):
|
| 135 |
+
print(f"Stream cancelled for user {user_id}")
|
| 136 |
+
break
|
| 137 |
+
|
| 138 |
+
if chunk.choices[0].delta.content is not None:
|
| 139 |
+
# In real implementation, send to user via WebSocket
|
| 140 |
+
content = chunk.choices[0].delta.content
|
| 141 |
+
print(f"User {user_id}: {content}", end="", flush=True)
|
| 142 |
+
|
| 143 |
+
print()
|
| 144 |
+
finally:
|
| 145 |
+
self.active_streams.pop(user_id, None)
|
| 146 |
+
|
| 147 |
+
def cancel_stream(self, user_id):
|
| 148 |
+
"""Cancel stream for a specific user."""
|
| 149 |
+
self.active_streams[user_id] = False
|
| 150 |
+
|
| 151 |
+
# Usage
|
| 152 |
+
manager = StreamingManager()
|
| 153 |
+
|
| 154 |
+
async def handle_multiple_users():
|
| 155 |
+
tasks = [
|
| 156 |
+
manager.stream_to_user(1, "Tell me a joke"),
|
| 157 |
+
manager.stream_to_user(2, "Explain machine learning")
|
| 158 |
+
]
|
| 159 |
+
await asyncio.gather(*tasks)
|
| 160 |
+
|
| 161 |
+
asyncio.run(handle_multiple_users())
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
### 5. Process Streaming Output
|
| 165 |
+
Process chunks during streaming.
|
| 166 |
+
|
| 167 |
+
```python
|
| 168 |
+
class StreamProcessor:
|
| 169 |
+
def __init__(self):
|
| 170 |
+
self.collected_chunks = []
|
| 171 |
+
self.processed_chunks = []
|
| 172 |
+
|
| 173 |
+
def process_chunk(self, chunk):
|
| 174 |
+
"""Process individual chunks during streaming."""
|
| 175 |
+
# Example: filter or transform chunks
|
| 176 |
+
processed = chunk.replace("**", "").replace("__", "")
|
| 177 |
+
self.processed_chunks.append(processed)
|
| 178 |
+
return processed
|
| 179 |
+
|
| 180 |
+
def stream_with_processing(self, prompt):
|
| 181 |
+
"""Stream and process chunks in real-time."""
|
| 182 |
+
stream = client.chat.completions.create(
|
| 183 |
+
model="gpt-4",
|
| 184 |
+
messages=[{"role": "user", "content": prompt}],
|
| 185 |
+
stream=True
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
for chunk in stream:
|
| 189 |
+
if chunk.choices[0].delta.content is not None:
|
| 190 |
+
content = chunk.choices[0].delta.content
|
| 191 |
+
self.collected_chunks.append(content)
|
| 192 |
+
|
| 193 |
+
processed = self.process_chunk(content)
|
| 194 |
+
print(processed, end="", flush=True)
|
| 195 |
+
|
| 196 |
+
print()
|
| 197 |
+
return "".join(self.processed_chunks)
|
| 198 |
+
|
| 199 |
+
# Usage
|
| 200 |
+
processor = StreamProcessor()
|
| 201 |
+
result = processor.stream_with_processing("Write markdown formatted text with bold and italics")
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
### 6. Web Integration with Streaming
|
| 205 |
+
Integrate streaming with web frameworks.
|
| 206 |
+
|
| 207 |
+
```python
|
| 208 |
+
from fastapi import FastAPI
|
| 209 |
+
from fastapi.responses import StreamingResponse
|
| 210 |
+
import json
|
| 211 |
+
|
| 212 |
+
app = FastAPI()
|
| 213 |
+
|
| 214 |
+
def generate_stream(prompt):
|
| 215 |
+
"""Generator for FastAPI streaming response."""
|
| 216 |
+
stream = client.chat.completions.create(
|
| 217 |
+
model="gpt-4",
|
| 218 |
+
messages=[{"role": "user", "content": prompt}],
|
| 219 |
+
stream=True
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
for chunk in stream:
|
| 223 |
+
if chunk.choices[0].delta.content is not None:
|
| 224 |
+
content = chunk.choices[0].delta.content
|
| 225 |
+
# Format as SSE (Server-Sent Events)
|
| 226 |
+
data = json.dumps({"content": content})
|
| 227 |
+
yield f"data: {data}\n\n"
|
| 228 |
+
|
| 229 |
+
yield "data: [DONE]\n\n"
|
| 230 |
+
|
| 231 |
+
@app.post("/chat")
|
| 232 |
+
async def chat_endpoint(prompt: str):
|
| 233 |
+
"""Endpoint for streaming chat responses."""
|
| 234 |
+
return StreamingResponse(
|
| 235 |
+
generate_stream(prompt),
|
| 236 |
+
media_type="text/event-stream"
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
# For client-side JavaScript:
|
| 240 |
+
"""
|
| 241 |
+
const response = await fetch('/chat', {
|
| 242 |
+
method: 'POST',
|
| 243 |
+
headers: {'Content-Type': 'application/json'},
|
| 244 |
+
body: JSON.stringify({prompt: 'Hello'})
|
| 245 |
+
});
|
| 246 |
+
|
| 247 |
+
const reader = response.body.getReader();
|
| 248 |
+
const decoder = new TextDecoder();
|
| 249 |
+
|
| 250 |
+
while (true) {
|
| 251 |
+
const {done, value} = await reader.read();
|
| 252 |
+
if (done) break;
|
| 253 |
+
|
| 254 |
+
const chunk = decoder.decode(value);
|
| 255 |
+
// Process chunk and update UI
|
| 256 |
+
}
|
| 257 |
+
"""
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
### 7. Streaming with Metadata
|
| 261 |
+
Include metadata with streaming responses.
|
| 262 |
+
|
| 263 |
+
```python
|
| 264 |
+
def stream_with_metadata(prompt):
|
| 265 |
+
"""Stream response with additional metadata."""
|
| 266 |
+
stream = client.chat.completions.create(
|
| 267 |
+
model="gpt-4",
|
| 268 |
+
messages=[{"role": "user", "content": prompt}],
|
| 269 |
+
stream=True
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
total_tokens = 0
|
| 273 |
+
start_time = time.time()
|
| 274 |
+
|
| 275 |
+
for i, chunk in enumerate(stream):
|
| 276 |
+
if chunk.choices[0].delta.content is not None:
|
| 277 |
+
content = chunk.choices[0].delta.content
|
| 278 |
+
total_tokens += len(content.split())
|
| 279 |
+
|
| 280 |
+
metadata = {
|
| 281 |
+
"chunk": i,
|
| 282 |
+
"tokens": total_tokens,
|
| 283 |
+
"elapsed": time.time() - start_time
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
yield {
|
| 287 |
+
"content": content,
|
| 288 |
+
"metadata": metadata
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
# Final metadata
|
| 292 |
+
final_metadata = {
|
| 293 |
+
"total_chunks": i + 1,
|
| 294 |
+
"estimated_tokens": total_tokens,
|
| 295 |
+
"total_time": time.time() - start_time
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
yield {
|
| 299 |
+
"content": "",
|
| 300 |
+
"metadata": final_metadata,
|
| 301 |
+
"complete": True
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
# Usage
|
| 305 |
+
for response in stream_with_metadata("Write a 500-word essay on AI"):
|
| 306 |
+
if response.get("complete"):
|
| 307 |
+
print(f"\nCompleted in {response['metadata']['total_time']:.2f}s")
|
| 308 |
+
else:
|
| 309 |
+
print(response["content"], end="", flush=True)
|
| 310 |
+
```
|
| 311 |
+
|
| 312 |
+
## Constraints
|
| 313 |
+
- **Token Counting**: Streaming makes exact token counting difficult
|
| 314 |
+
- **Error Handling**: Handle connection failures mid-stream gracefully
|
| 315 |
+
- **Buffer Size**: Balance between real-time feedback and choppy output
|
| 316 |
+
- **Memory Usage**: Be careful with memory for very long responses
|
| 317 |
+
- **Cancellation**: Implement proper cancellation for user interruptions
|
| 318 |
+
- **Format Maintenance**: Streaming may break markdown formatting temporarily
|
| 319 |
+
|
| 320 |
+
## Expected Output
|
| 321 |
+
Real-time streaming responses that provide immediate user feedback, with proper error handling and integration capabilities for various applications.
|
TRAE-Skills/ai_engineering/Structured_Output_Parsing.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Structured Output Parsing
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To force LLMs to output data in specific, parseable formats like JSON, enabling reliable integration with applications and automated workflows.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building APIs that need structured responses
|
| 8 |
+
- When extracting data from unstructured text
|
| 9 |
+
- When generating code, configuration files, or data formats
|
| 10 |
+
- When requiring consistent output formatting for downstream processing
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Basic JSON Output
|
| 15 |
+
Request JSON format in your prompt.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from openai import OpenAI
|
| 19 |
+
import json
|
| 20 |
+
|
| 21 |
+
client = OpenAI()
|
| 22 |
+
|
| 23 |
+
def get_json_response(prompt, schema_description):
|
| 24 |
+
"""Get structured JSON response from LLM."""
|
| 25 |
+
full_prompt = f"""
|
| 26 |
+
{prompt}
|
| 27 |
+
|
| 28 |
+
Provide your response as a JSON object following this structure:
|
| 29 |
+
{schema_description}
|
| 30 |
+
|
| 31 |
+
Return only the JSON, no additional text.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
response = client.chat.completions.create(
|
| 35 |
+
model="gpt-4",
|
| 36 |
+
messages=[{"role": "user", "content": full_prompt}],
|
| 37 |
+
temperature=0 # Lower temperature for more consistent structure
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# Parse JSON response
|
| 41 |
+
try:
|
| 42 |
+
return json.loads(response.choices[0].message.content)
|
| 43 |
+
except json.JSONDecodeError:
|
| 44 |
+
# Fallback: ask model to fix the JSON
|
| 45 |
+
return fix_json_parse(response.choices[0].message.content)
|
| 46 |
+
|
| 47 |
+
# Example usage
|
| 48 |
+
person_info = get_json_response(
|
| 49 |
+
"Extract information from: 'John Doe is a 35-year-old software engineer from New York.'",
|
| 50 |
+
"""
|
| 51 |
+
{
|
| 52 |
+
"name": "string",
|
| 53 |
+
"age": "number",
|
| 54 |
+
"occupation": "string",
|
| 55 |
+
"location": "string"
|
| 56 |
+
}
|
| 57 |
+
"""
|
| 58 |
+
)
|
| 59 |
+
print(person_info)
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### 2. Pydantic Models for Validation
|
| 63 |
+
Use Pydantic for robust schema validation.
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
from pydantic import BaseModel, Field
|
| 67 |
+
from typing import List, Optional
|
| 68 |
+
|
| 69 |
+
class Person(BaseModel):
|
| 70 |
+
name: str = Field(description="Full name of the person")
|
| 71 |
+
age: int = Field(description="Age in years", ge=0, le=150)
|
| 72 |
+
occupation: str = Field(description="Job title or occupation")
|
| 73 |
+
location: Optional[str] = Field(default=None, description="City or location")
|
| 74 |
+
|
| 75 |
+
class ProductReview(BaseModel):
|
| 76 |
+
product_name: str
|
| 77 |
+
rating: int = Field(ge=1, le=5)
|
| 78 |
+
review_text: str
|
| 79 |
+
pros: List[str]
|
| 80 |
+
cons: List[str]
|
| 81 |
+
would_recommend: bool
|
| 82 |
+
|
| 83 |
+
def extract_structured_data(text, model_class):
|
| 84 |
+
"""Extract structured data using Pydantic model."""
|
| 85 |
+
schema = model_class.model_json_schema()
|
| 86 |
+
|
| 87 |
+
prompt = f"""
|
| 88 |
+
Extract information from the following text and return it as JSON:
|
| 89 |
+
|
| 90 |
+
Text: {text}
|
| 91 |
+
|
| 92 |
+
Return JSON matching this schema:
|
| 93 |
+
{json.dumps(schema, indent=2)}
|
| 94 |
+
|
| 95 |
+
Return only the JSON, no additional text.
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
response = client.chat.completions.create(
|
| 99 |
+
model="gpt-4",
|
| 100 |
+
messages=[{"role": "user", "content": prompt}],
|
| 101 |
+
temperature=0
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
try:
|
| 105 |
+
data = json.loads(response.choices[0].message.content)
|
| 106 |
+
return model_class(**data)
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"Error parsing response: {e}")
|
| 109 |
+
return None
|
| 110 |
+
|
| 111 |
+
# Example usage
|
| 112 |
+
review_text = """
|
| 113 |
+
I bought the Acme Widget Pro last month. Overall I'd give it 4 stars.
|
| 114 |
+
The build quality is excellent and battery life is amazing.
|
| 115 |
+
However, the price is quite high and the app interface is confusing.
|
| 116 |
+
Despite these issues, I would recommend it to others.
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
review = extract_structured_data(review_text, ProductReview)
|
| 120 |
+
if review:
|
| 121 |
+
print(f"Product: {review.product_name}")
|
| 122 |
+
print(f"Rating: {review.rating}/5")
|
| 123 |
+
print(f"Pros: {', '.join(review.pros)}")
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### 3. Few-Shot JSON Examples
|
| 127 |
+
Provide examples to improve JSON formatting.
|
| 128 |
+
|
| 129 |
+
```python
|
| 130 |
+
def extract_with_few_shots(text):
|
| 131 |
+
"""Extract structured data with few-shot examples."""
|
| 132 |
+
prompt = """
|
| 133 |
+
Example 1:
|
| 134 |
+
Input: "Sarah Connor, 28, works as a data analyst in Boston."
|
| 135 |
+
Output: {"name": "Sarah Connor", "age": 28, "occupation": "data analyst", "location": "Boston"}
|
| 136 |
+
|
| 137 |
+
Example 2:
|
| 138 |
+
Input: "Mike Ross is a 32-year-old lawyer from New York."
|
| 139 |
+
Output: {"name": "Mike Ross", "age": 32, "occupation": "lawyer", "location": "New York"}
|
| 140 |
+
|
| 141 |
+
Example 3:
|
| 142 |
+
Input: "Emily Chen, 25, graphic designer, San Francisco"
|
| 143 |
+
Output: {"name": "Emily Chen", "age": 25, "occupation": "graphic designer", "location": "San Francisco"}
|
| 144 |
+
|
| 145 |
+
Now extract from this input:
|
| 146 |
+
Input: "{text}"
|
| 147 |
+
Output:"""
|
| 148 |
+
|
| 149 |
+
response = client.chat.completions.create(
|
| 150 |
+
model="gpt-4",
|
| 151 |
+
messages=[{"role": "user", "content": prompt}],
|
| 152 |
+
temperature=0
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
return json.loads(response.choices[0].message.content)
|
| 156 |
+
|
| 157 |
+
# Usage
|
| 158 |
+
result = extract_with_few_shots("James Wilson, 42, architect, Chicago")
|
| 159 |
+
print(result)
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### 4. Structured Output with Function Calling
|
| 163 |
+
Use function calling for guaranteed structured output.
|
| 164 |
+
|
| 165 |
+
```python
|
| 166 |
+
def structured_output_with_functions(text):
|
| 167 |
+
"""Use function calling for structured output."""
|
| 168 |
+
function_def = {
|
| 169 |
+
"name": "extract_person_info",
|
| 170 |
+
"description": "Extract person information from text",
|
| 171 |
+
"parameters": {
|
| 172 |
+
"type": "object",
|
| 173 |
+
"properties": {
|
| 174 |
+
"name": {"type": "string"},
|
| 175 |
+
"age": {"type": "integer"},
|
| 176 |
+
"occupation": {"type": "string"},
|
| 177 |
+
"location": {"type": "string"}
|
| 178 |
+
},
|
| 179 |
+
"required": ["name", "age", "occupation"]
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
response = client.chat.completions.create(
|
| 184 |
+
model="gpt-4",
|
| 185 |
+
messages=[{
|
| 186 |
+
"role": "user",
|
| 187 |
+
"content": f"Extract person information from: {text}"
|
| 188 |
+
}],
|
| 189 |
+
functions=[function_def],
|
| 190 |
+
function_call={"name": "extract_person_info"}
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
function_call = response.choices[0].message.function_call
|
| 194 |
+
return json.loads(function_call.arguments)
|
| 195 |
+
|
| 196 |
+
# Example
|
| 197 |
+
person = structured_output_with_functions(
|
| 198 |
+
"Dr. Lisa Anderson, 38, cardiologist at Mayo Clinic, Rochester"
|
| 199 |
+
)
|
| 200 |
+
print(person)
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
### 5. Complex Nested Structures
|
| 204 |
+
Handle complex nested JSON structures.
|
| 205 |
+
|
| 206 |
+
```python
|
| 207 |
+
class Company(BaseModel):
|
| 208 |
+
name: str
|
| 209 |
+
founded_year: int
|
| 210 |
+
headquarters: str
|
| 211 |
+
employees: List[str]
|
| 212 |
+
departments: dict
|
| 213 |
+
|
| 214 |
+
def extract_company_info(text):
|
| 215 |
+
"""Extract complex nested company information."""
|
| 216 |
+
prompt = f"""
|
| 217 |
+
Extract company information from the following text and return it as JSON.
|
| 218 |
+
Include all employees mentioned and their departments.
|
| 219 |
+
|
| 220 |
+
Text: {text}
|
| 221 |
+
|
| 222 |
+
Return JSON matching this structure:
|
| 223 |
+
{json.dumps(Company.model_json_schema(), indent=2)}
|
| 224 |
+
|
| 225 |
+
Return only valid JSON.
|
| 226 |
+
"""
|
| 227 |
+
|
| 228 |
+
response = client.chat.completions.create(
|
| 229 |
+
model="gpt-4",
|
| 230 |
+
messages=[{"role": "user", "content": prompt}],
|
| 231 |
+
temperature=0
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
try:
|
| 235 |
+
data = json.loads(response.choices[0].message.content)
|
| 236 |
+
return Company(**data)
|
| 237 |
+
except Exception as e:
|
| 238 |
+
print(f"Error: {e}")
|
| 239 |
+
return None
|
| 240 |
+
|
| 241 |
+
# Example
|
| 242 |
+
company_text = """
|
| 243 |
+
TechCorp Inc. was founded in 2010 and is headquartered in Austin, Texas.
|
| 244 |
+
The company has three main departments: Engineering, Sales, and Marketing.
|
| 245 |
+
Key employees include:
|
| 246 |
+
- John Smith (CEO, Engineering department)
|
| 247 |
+
- Sarah Johnson (VP of Sales)
|
| 248 |
+
- Michael Chen (CTO, Engineering)
|
| 249 |
+
- Emily Davis (Marketing Director)
|
| 250 |
+
"""
|
| 251 |
+
|
| 252 |
+
company = extract_company_info(company_text)
|
| 253 |
+
if company:
|
| 254 |
+
print(f"Company: {company.name}")
|
| 255 |
+
print(f"Departments: {company.departments.keys()}")
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
### 6. Error Recovery and Retry Logic
|
| 259 |
+
Implement robust error handling for JSON parsing.
|
| 260 |
+
|
| 261 |
+
```python
|
| 262 |
+
def extract_with_retry(text, model_class, max_retries=3):
|
| 263 |
+
"""Extract structured data with retry logic."""
|
| 264 |
+
schema = model_class.model_json_schema()
|
| 265 |
+
|
| 266 |
+
for attempt in range(max_retries):
|
| 267 |
+
prompt = f"""
|
| 268 |
+
Extract information as JSON following this schema:
|
| 269 |
+
{json.dumps(schema, indent=2)}
|
| 270 |
+
|
| 271 |
+
Text: {text}
|
| 272 |
+
|
| 273 |
+
{'Only return the JSON object, no other text.' if attempt == 0 else 'Your previous response was not valid JSON. Please ensure you return ONLY a valid JSON object, nothing else.'}
|
| 274 |
+
"""
|
| 275 |
+
|
| 276 |
+
response = client.chat.completions.create(
|
| 277 |
+
model="gpt-4",
|
| 278 |
+
messages=[{"role": "user", "content": prompt}],
|
| 279 |
+
temperature=0
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
try:
|
| 283 |
+
data = json.loads(response.choices[0].message.content)
|
| 284 |
+
return model_class(**data)
|
| 285 |
+
except Exception as e:
|
| 286 |
+
if attempt == max_retries - 1:
|
| 287 |
+
print(f"Failed after {max_retries} attempts: {e}")
|
| 288 |
+
return None
|
| 289 |
+
print(f"Attempt {attempt + 1} failed, retrying...")
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
## Constraints
|
| 293 |
+
- **Model Consistency**: Not all models follow instructions perfectly - use function calling when possible
|
| 294 |
+
- **Temperature**: Use low temperature (0-0.3) for more consistent formatting
|
| 295 |
+
- **Complexity**: Very complex structures may require multiple extraction steps
|
| 296 |
+
- **Validation**: Always validate parsed data before using it
|
| 297 |
+
- **Error Handling**: Implement robust error handling for malformed responses
|
| 298 |
+
- **Token Limits**: Large schemas consume tokens - keep them as minimal as possible
|
| 299 |
+
|
| 300 |
+
## Expected Output
|
| 301 |
+
Reliable structured data extraction from unstructured text, with properly validated and typed results that can be directly used in applications.
|
TRAE-Skills/ai_engineering/Time_Series_Forecasting.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Time Series Forecasting
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To predict future values based on previously observed time-ordered data, considering trends, seasonality, and exogenous variables.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When forecasting sales, inventory, stock prices, or resource demand
|
| 8 |
+
- When identifying anomalies in system metrics over time
|
| 9 |
+
- When working with IoT sensor data
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Identify the Time Series Characteristics
|
| 14 |
+
Analyze the data for:
|
| 15 |
+
- **Trend**: Long-term upward or downward movement.
|
| 16 |
+
- **Seasonality**: Repeating patterns at fixed intervals (daily, weekly, yearly).
|
| 17 |
+
- **Stationarity**: Whether statistical properties (mean, variance) change over time.
|
| 18 |
+
|
| 19 |
+
### 2. Choose the Forecasting Model
|
| 20 |
+
Select an appropriate algorithm based on data complexity:
|
| 21 |
+
- **ARIMA / SARIMA**: Traditional statistical models. Best for univariate data with clear seasonality.
|
| 22 |
+
- **Prophet (by Meta)**: Excellent for business time series with daily observations and strong seasonal effects.
|
| 23 |
+
- **XGBoost / LightGBM**: Effective for tabular time series with many exogenous features.
|
| 24 |
+
- **LSTMs / Transformers (e.g., Temporal Fusion Transformer)**: Best for complex, non-linear relationships and long sequences.
|
| 25 |
+
|
| 26 |
+
### 3. Prophet Implementation Example
|
| 27 |
+
|
| 28 |
+
**Setup and Fitting**:
|
| 29 |
+
```python
|
| 30 |
+
from prophet import Prophet
|
| 31 |
+
import pandas as pd
|
| 32 |
+
|
| 33 |
+
# Data must have 'ds' (datestamp) and 'y' (target) columns
|
| 34 |
+
df = pd.read_csv('sales_data.csv')
|
| 35 |
+
df.rename(columns={'date': 'ds', 'sales': 'y'}, inplace=True)
|
| 36 |
+
|
| 37 |
+
# Initialize Prophet model
|
| 38 |
+
m = Prophet(
|
| 39 |
+
yearly_seasonality=True,
|
| 40 |
+
weekly_seasonality=True,
|
| 41 |
+
daily_seasonality=False
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# Add custom holidays or exogenous variables if necessary
|
| 45 |
+
m.add_country_holidays(country_name='US')
|
| 46 |
+
|
| 47 |
+
# Fit the model
|
| 48 |
+
m.fit(df)
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**Forecasting**:
|
| 52 |
+
```python
|
| 53 |
+
# Create future dates
|
| 54 |
+
future = m.make_future_dataframe(periods=365)
|
| 55 |
+
|
| 56 |
+
# Predict future values
|
| 57 |
+
forecast = m.predict(future)
|
| 58 |
+
|
| 59 |
+
# Plotting results
|
| 60 |
+
fig1 = m.plot(forecast)
|
| 61 |
+
fig2 = m.plot_components(forecast)
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### 4. Evaluation Metrics
|
| 65 |
+
Use appropriate error metrics for time series:
|
| 66 |
+
- **MAE (Mean Absolute Error)**: Easy to interpret, robust to outliers.
|
| 67 |
+
- **RMSE (Root Mean Squared Error)**: Penalizes large errors heavily.
|
| 68 |
+
- **MAPE (Mean Absolute Percentage Error)**: Useful for comparing relative performance across different scales.
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
from sklearn.metrics import mean_absolute_error, mean_squared_error
|
| 72 |
+
import numpy as np
|
| 73 |
+
|
| 74 |
+
mae = mean_absolute_error(y_true, y_pred)
|
| 75 |
+
rmse = np.sqrt(mean_squared_error(y_true, y_pred))
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### 5. Cross-Validation
|
| 79 |
+
Do not use standard K-Fold cross-validation. Use Time Series Split to respect temporal order.
|
| 80 |
+
|
| 81 |
+
```python
|
| 82 |
+
from sklearn.model_selection import TimeSeriesSplit
|
| 83 |
+
|
| 84 |
+
tscv = TimeSeriesSplit(n_splits=5)
|
| 85 |
+
for train_index, test_index in tscv.split(X):
|
| 86 |
+
X_train, X_test = X[train_index], X[test_index]
|
| 87 |
+
y_train, y_test = y[train_index], y[test_index]
|
| 88 |
+
# Train and evaluate model
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
## Best Practices
|
| 92 |
+
- Always plot your data before modeling. Visual inspection reveals obvious anomalies or structural breaks.
|
| 93 |
+
- Use baseline models (like naive forecasting or moving average) to establish a performance floor before moving to complex models.
|
| 94 |
+
- Handle missing values carefully; avoid interpolating over large gaps without justification.
|
TRAE-Skills/ai_engineering/Time_Series_Forecasting_LSTM.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Time Series Forecasting with LSTM Networks
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To build accurate time series prediction models using Long Short-Term Memory (LSTM) neural networks.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- For stock price prediction
|
| 8 |
+
- When forecasting sales or demand
|
| 9 |
+
- For predicting energy consumption
|
| 10 |
+
- When analyzing financial markets
|
| 11 |
+
- For weather forecasting with sequential data
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Data Preparation
|
| 16 |
+
Prepare time series data for LSTM.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
import numpy as np
|
| 20 |
+
import pandas as pd
|
| 21 |
+
import matplotlib.pyplot as plt
|
| 22 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 23 |
+
from sklearn.model_selection import train_test_split
|
| 24 |
+
|
| 25 |
+
# Load sample data
|
| 26 |
+
df = pd.read_csv('time_series_data.csv', parse_dates=['date'], index_col='date')
|
| 27 |
+
data = df[['value']].values
|
| 28 |
+
|
| 29 |
+
# Normalize data
|
| 30 |
+
scaler = MinMaxScaler(feature_range=(0, 1))
|
| 31 |
+
scaled_data = scaler.fit_transform(data)
|
| 32 |
+
|
| 33 |
+
# Create sequences
|
| 34 |
+
def create_sequences(data, time_steps=60):
|
| 35 |
+
X, y = [], []
|
| 36 |
+
for i in range(time_steps, len(data)):
|
| 37 |
+
X.append(data[i-time_steps:i, 0])
|
| 38 |
+
y.append(data[i, 0])
|
| 39 |
+
return np.array(X), np.array(y)
|
| 40 |
+
|
| 41 |
+
time_steps = 60
|
| 42 |
+
X, y = create_sequences(scaled_data, time_steps)
|
| 43 |
+
|
| 44 |
+
# Reshape for LSTM (samples, time steps, features)
|
| 45 |
+
X = np.reshape(X, (X.shape[0], X.shape[1], 1))
|
| 46 |
+
|
| 47 |
+
# Split into train and test
|
| 48 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### 2. Build LSTM Model
|
| 52 |
+
Create an LSTM model with Keras.
|
| 53 |
+
|
| 54 |
+
```python
|
| 55 |
+
from tensorflow.keras.models import Sequential
|
| 56 |
+
from tensorflow.keras.layers import LSTM, Dense, Dropout
|
| 57 |
+
from tensorflow.keras.optimizers import Adam
|
| 58 |
+
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
|
| 59 |
+
|
| 60 |
+
model = Sequential([
|
| 61 |
+
LSTM(50, return_sequences=True, input_shape=(X_train.shape[1], 1)),
|
| 62 |
+
Dropout(0.2),
|
| 63 |
+
LSTM(50, return_sequences=False),
|
| 64 |
+
Dropout(0.2),
|
| 65 |
+
Dense(25),
|
| 66 |
+
Dense(1)
|
| 67 |
+
])
|
| 68 |
+
|
| 69 |
+
model.compile(optimizer=Adam(learning_rate=0.001), loss='mean_squared_error')
|
| 70 |
+
|
| 71 |
+
model.summary()
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### 3. Train the Model
|
| 75 |
+
Train the LSTM model.
|
| 76 |
+
|
| 77 |
+
```python
|
| 78 |
+
callbacks = [
|
| 79 |
+
EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True),
|
| 80 |
+
ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
history = model.fit(
|
| 84 |
+
X_train, y_train,
|
| 85 |
+
batch_size=32,
|
| 86 |
+
epochs=100,
|
| 87 |
+
validation_data=(X_test, y_test),
|
| 88 |
+
callbacks=callbacks
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
# Plot training history
|
| 92 |
+
plt.figure(figsize=(12, 6))
|
| 93 |
+
plt.plot(history.history['loss'], label='Training Loss')
|
| 94 |
+
plt.plot(history.history['val_loss'], label='Validation Loss')
|
| 95 |
+
plt.title('Model Loss')
|
| 96 |
+
plt.xlabel('Epoch')
|
| 97 |
+
plt.ylabel('Loss')
|
| 98 |
+
plt.legend()
|
| 99 |
+
plt.show()
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### 4. Make Predictions
|
| 103 |
+
Generate predictions with the trained model.
|
| 104 |
+
|
| 105 |
+
```python
|
| 106 |
+
# Predict on test data
|
| 107 |
+
predictions = model.predict(X_test)
|
| 108 |
+
predictions = scaler.inverse_transform(predictions)
|
| 109 |
+
y_test_actual = scaler.inverse_transform(y_test.reshape(-1, 1))
|
| 110 |
+
|
| 111 |
+
# Plot results
|
| 112 |
+
train = df[:-len(y_test)]
|
| 113 |
+
valid = df[-len(y_test):]
|
| 114 |
+
valid['Predictions'] = predictions
|
| 115 |
+
|
| 116 |
+
plt.figure(figsize=(16, 8))
|
| 117 |
+
plt.title('Time Series Prediction')
|
| 118 |
+
plt.xlabel('Date', fontsize=18)
|
| 119 |
+
plt.ylabel('Value', fontsize=18)
|
| 120 |
+
plt.plot(train['value'])
|
| 121 |
+
plt.plot(valid[['value', 'Predictions']])
|
| 122 |
+
plt.legend(['Training Data', 'Actual Value', 'Predicted Value'], loc='lower right')
|
| 123 |
+
plt.show()
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### 5. Evaluate the Model
|
| 127 |
+
Calculate evaluation metrics.
|
| 128 |
+
|
| 129 |
+
```python
|
| 130 |
+
from sklearn.metrics import mean_squared_error, mean_absolute_error, mean_absolute_percentage_error
|
| 131 |
+
|
| 132 |
+
rmse = np.sqrt(mean_squared_error(y_test_actual, predictions))
|
| 133 |
+
mae = mean_absolute_error(y_test_actual, predictions)
|
| 134 |
+
mape = mean_absolute_percentage_error(y_test_actual, predictions)
|
| 135 |
+
|
| 136 |
+
print(f'RMSE: {rmse:.2f}')
|
| 137 |
+
print(f'MAE: {mae:.2f}')
|
| 138 |
+
print(f'MAPE: {mape:.2%}')
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
### 6. Multi-Step Forecasting
|
| 142 |
+
Predict multiple future time steps.
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
def multi_step_forecast(model, last_sequence, steps, scaler, time_steps):
|
| 146 |
+
forecast = []
|
| 147 |
+
current_sequence = last_sequence.copy()
|
| 148 |
+
|
| 149 |
+
for _ in range(steps):
|
| 150 |
+
# Predict next step
|
| 151 |
+
prediction = model.predict(current_sequence.reshape(1, time_steps, 1), verbose=0)
|
| 152 |
+
|
| 153 |
+
# Store prediction
|
| 154 |
+
forecast.append(prediction[0, 0])
|
| 155 |
+
|
| 156 |
+
# Update sequence
|
| 157 |
+
current_sequence = np.roll(current_sequence, -1)
|
| 158 |
+
current_sequence[-1] = prediction[0, 0]
|
| 159 |
+
|
| 160 |
+
# Inverse transform
|
| 161 |
+
forecast = scaler.inverse_transform(np.array(forecast).reshape(-1, 1))
|
| 162 |
+
return forecast.flatten()
|
| 163 |
+
|
| 164 |
+
# Get the last sequence from training data
|
| 165 |
+
last_sequence = X_test[-1]
|
| 166 |
+
|
| 167 |
+
# Forecast next 30 days
|
| 168 |
+
forecast_steps = 30
|
| 169 |
+
forecast = multi_step_forecast(model, last_sequence, forecast_steps, scaler, time_steps)
|
| 170 |
+
|
| 171 |
+
# Create future dates
|
| 172 |
+
last_date = df.index[-1]
|
| 173 |
+
future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=forecast_steps)
|
| 174 |
+
|
| 175 |
+
# Plot forecast
|
| 176 |
+
plt.figure(figsize=(16, 8))
|
| 177 |
+
plt.plot(df['value'], label='Historical Data')
|
| 178 |
+
plt.plot(future_dates, forecast, label='Forecast', linestyle='--')
|
| 179 |
+
plt.title('Multi-Step Time Series Forecast')
|
| 180 |
+
plt.xlabel('Date')
|
| 181 |
+
plt.ylabel('Value')
|
| 182 |
+
plt.legend()
|
| 183 |
+
plt.show()
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
### 7. Multivariate LSTM
|
| 187 |
+
Use multiple features for prediction.
|
| 188 |
+
|
| 189 |
+
```python
|
| 190 |
+
# Load data with multiple features
|
| 191 |
+
df = pd.read_csv('multivariate_data.csv', parse_dates=['date'], index_col='date')
|
| 192 |
+
features = ['value', 'feature1', 'feature2', 'feature3']
|
| 193 |
+
data = df[features].values
|
| 194 |
+
|
| 195 |
+
# Normalize all features
|
| 196 |
+
scaler = MinMaxScaler(feature_range=(0, 1))
|
| 197 |
+
scaled_data = scaler.fit_transform(data)
|
| 198 |
+
|
| 199 |
+
# Create sequences with multiple features
|
| 200 |
+
def create_multivariate_sequences(data, time_steps=60):
|
| 201 |
+
X, y = [], []
|
| 202 |
+
for i in range(time_steps, len(data)):
|
| 203 |
+
X.append(data[i-time_steps:i, :]) # All features
|
| 204 |
+
y.append(data[i, 0]) # Target is first feature
|
| 205 |
+
return np.array(X), np.array(y)
|
| 206 |
+
|
| 207 |
+
time_steps = 60
|
| 208 |
+
X, y = create_multivariate_sequences(scaled_data, time_steps)
|
| 209 |
+
|
| 210 |
+
# Build multivariate LSTM model
|
| 211 |
+
model = Sequential([
|
| 212 |
+
LSTM(100, return_sequences=True, input_shape=(X.shape[1], X.shape[2])),
|
| 213 |
+
Dropout(0.3),
|
| 214 |
+
LSTM(100, return_sequences=False),
|
| 215 |
+
Dropout(0.3),
|
| 216 |
+
Dense(50),
|
| 217 |
+
Dense(1)
|
| 218 |
+
])
|
| 219 |
+
|
| 220 |
+
model.compile(optimizer=Adam(learning_rate=0.001), loss='mean_squared_error')
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
## Best Practices
|
| 224 |
+
- **Normalize/Standardize**: Always normalize time series data
|
| 225 |
+
- **Sequence Length**: Choose appropriate time step window
|
| 226 |
+
- **Validation**: Use walk-forward validation for time series
|
| 227 |
+
- **Regularization**: Use dropout to prevent overfitting
|
| 228 |
+
- **Early Stopping**: Stop training when validation loss stops improving
|
| 229 |
+
- **Feature Engineering**: Create meaningful features (lags, rolling stats)
|
| 230 |
+
- **Ensemble**: Combine with ARIMA, Prophet for better results
|
| 231 |
+
- **Hyperparameter Tuning**: Optimize layers, units, learning rate
|
| 232 |
+
- **Monitor**: Track performance on both train and validation sets
|
| 233 |
+
- **Update**: Retrain model periodically with new data
|
TRAE-Skills/ai_engineering/Token_Optimization.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Token Optimization
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To minimize token usage while maintaining output quality, reducing API costs and improving response times.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When working with large documents or contexts
|
| 8 |
+
- When building cost-sensitive applications
|
| 9 |
+
- When processing multiple documents
|
| 10 |
+
- When optimizing for faster responses
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Prune Redundant Content
|
| 15 |
+
Remove unnecessary information from prompts.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
def prune_redundant_content(text):
|
| 19 |
+
"""Remove redundant phrases and content."""
|
| 20 |
+
# Common redundant phrases to remove
|
| 21 |
+
redundant_phrases = [
|
| 22 |
+
"please note that",
|
| 23 |
+
"it is important to mention",
|
| 24 |
+
"as previously stated",
|
| 25 |
+
"in conclusion",
|
| 26 |
+
"additionally",
|
| 27 |
+
"furthermore",
|
| 28 |
+
"moreover"
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
pruned = text
|
| 32 |
+
for phrase in redundant_phrases:
|
| 33 |
+
pruned = pruned.replace(phrase, "")
|
| 34 |
+
|
| 35 |
+
# Remove multiple spaces
|
| 36 |
+
pruned = " ".join(pruned.split())
|
| 37 |
+
|
| 38 |
+
return pruned
|
| 39 |
+
|
| 40 |
+
# Example
|
| 41 |
+
original = """Please note that it is important to mention that the document
|
| 42 |
+
contains multiple sections. Furthermore, additionally, it has various topics."""
|
| 43 |
+
|
| 44 |
+
optimized = prune_redundant_content(original)
|
| 45 |
+
print(f"Original: {len(original)} tokens")
|
| 46 |
+
print(f"Optimized: {len(optimized)} tokens")
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### 2. Use Efficient Prompt Structures
|
| 50 |
+
Structure prompts to maximize information density.
|
| 51 |
+
|
| 52 |
+
```python
|
| 53 |
+
# INEFFICIENT - verbose prompt
|
| 54 |
+
inefficient_prompt = """
|
| 55 |
+
I would like you to please help me by analyzing the following text.
|
| 56 |
+
Please provide me with a summary of the main points.
|
| 57 |
+
The text is as follows: {text}
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
# EFFICIENT - concise prompt
|
| 61 |
+
efficient_prompt = """Summarize the main points: {text}"""
|
| 62 |
+
|
| 63 |
+
# Even more efficient with examples
|
| 64 |
+
few_shot_efficient = """Text: {text1}
|
| 65 |
+
Summary: {summary1}
|
| 66 |
+
|
| 67 |
+
Text: {text2}
|
| 68 |
+
Summary: {summary2}
|
| 69 |
+
|
| 70 |
+
Text: {text}
|
| 71 |
+
Summary:"""
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### 3. Chunk and Summarize Large Documents
|
| 75 |
+
Process large documents in chunks with progressive summarization.
|
| 76 |
+
|
| 77 |
+
```python
|
| 78 |
+
def chunk_text(text, max_chunk_size=2000):
|
| 79 |
+
"""Split text into chunks of roughly equal token count."""
|
| 80 |
+
words = text.split()
|
| 81 |
+
chunks = []
|
| 82 |
+
current_chunk = []
|
| 83 |
+
current_length = 0
|
| 84 |
+
|
| 85 |
+
for word in words:
|
| 86 |
+
current_chunk.append(word)
|
| 87 |
+
current_length += 1
|
| 88 |
+
|
| 89 |
+
if current_length >= max_chunk_size:
|
| 90 |
+
chunks.append(" ".join(current_chunk))
|
| 91 |
+
current_chunk = []
|
| 92 |
+
current_length = 0
|
| 93 |
+
|
| 94 |
+
if current_chunk:
|
| 95 |
+
chunks.append(" ".join(current_chunk))
|
| 96 |
+
|
| 97 |
+
return chunks
|
| 98 |
+
|
| 99 |
+
def progressive_summarize(text):
|
| 100 |
+
"""Summarize large document progressively."""
|
| 101 |
+
# Split into chunks
|
| 102 |
+
chunks = chunk_text(text, max_chunk_size=2000)
|
| 103 |
+
|
| 104 |
+
# Summarize each chunk
|
| 105 |
+
chunk_summaries = []
|
| 106 |
+
for chunk in chunks:
|
| 107 |
+
summary = client.chat.completions.create(
|
| 108 |
+
model="gpt-4",
|
| 109 |
+
messages=[{
|
| 110 |
+
"role": "user",
|
| 111 |
+
"content": f"Summarize in 1-2 sentences: {chunk}"
|
| 112 |
+
}]
|
| 113 |
+
)
|
| 114 |
+
chunk_summaries.append(summary.choices[0].message.content)
|
| 115 |
+
|
| 116 |
+
# Combine chunk summaries
|
| 117 |
+
combined = " ".join(chunk_summaries)
|
| 118 |
+
|
| 119 |
+
# Final summary
|
| 120 |
+
final_summary = client.chat.completions.create(
|
| 121 |
+
model="gpt-4",
|
| 122 |
+
messages=[{
|
| 123 |
+
"role": "user",
|
| 124 |
+
"content": f"Create a cohesive summary: {combined}"
|
| 125 |
+
}]
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
return final_summary.choices[0].message.content
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
### 4. Use System Messages Effectively
|
| 132 |
+
Move static context to system messages.
|
| 133 |
+
|
| 134 |
+
```python
|
| 135 |
+
# INEFFICIENT - repeating instructions in every message
|
| 136 |
+
messages_inefficient = [
|
| 137 |
+
{
|
| 138 |
+
"role": "user",
|
| 139 |
+
"content": "You are a helpful assistant that provides concise answers.
|
| 140 |
+
Please answer this question: What is machine learning?"
|
| 141 |
+
}
|
| 142 |
+
]
|
| 143 |
+
|
| 144 |
+
# EFFICIENT - use system message
|
| 145 |
+
messages_efficient = [
|
| 146 |
+
{
|
| 147 |
+
"role": "system",
|
| 148 |
+
"content": "You are a helpful assistant. Provide concise answers in 2-3 sentences."
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"role": "user",
|
| 152 |
+
"content": "What is machine learning?"
|
| 153 |
+
}
|
| 154 |
+
]
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
### 5. Use Smaller Models When Appropriate
|
| 158 |
+
Choose the right model for the task.
|
| 159 |
+
|
| 160 |
+
```python
|
| 161 |
+
def choose_model(task_complexity, token_count):
|
| 162 |
+
"""Choose the most cost-effective model."""
|
| 163 |
+
if token_count < 500 and task_complexity == "simple":
|
| 164 |
+
return "gpt-3.5-turbo"
|
| 165 |
+
elif token_count < 2000 and task_complexity in ["simple", "medium"]:
|
| 166 |
+
return "gpt-3.5-turbo"
|
| 167 |
+
else:
|
| 168 |
+
return "gpt-4"
|
| 169 |
+
|
| 170 |
+
# Example usage
|
| 171 |
+
text = "Your text here..."
|
| 172 |
+
token_count = len(text.split()) * 1.3 # Rough estimate
|
| 173 |
+
model = choose_model("medium", token_count)
|
| 174 |
+
|
| 175 |
+
response = client.chat.completions.create(
|
| 176 |
+
model=model,
|
| 177 |
+
messages=[{"role": "user", "content": text}]
|
| 178 |
+
)
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
### 6. Implement Token Counting and Budgeting
|
| 182 |
+
Track and limit token usage.
|
| 183 |
+
|
| 184 |
+
```python
|
| 185 |
+
import tiktoken
|
| 186 |
+
|
| 187 |
+
def count_tokens(text, model="gpt-4"):
|
| 188 |
+
"""Count tokens in text."""
|
| 189 |
+
encoding = tiktoken.encoding_for_model(model)
|
| 190 |
+
return len(encoding.encode(text))
|
| 191 |
+
|
| 192 |
+
class TokenBudget:
|
| 193 |
+
def __init__(self, max_tokens=100000):
|
| 194 |
+
self.max_tokens = max_tokens
|
| 195 |
+
self.used_tokens = 0
|
| 196 |
+
self.encoding = tiktoken.encoding_for_model("gpt-4")
|
| 197 |
+
|
| 198 |
+
def can_process(self, text):
|
| 199 |
+
"""Check if we can process this text within budget."""
|
| 200 |
+
tokens = count_tokens(text)
|
| 201 |
+
return (self.used_tokens + tokens) <= self.max_tokens
|
| 202 |
+
|
| 203 |
+
def process(self, text, function):
|
| 204 |
+
"""Process text if within budget."""
|
| 205 |
+
if not self.can_process(text):
|
| 206 |
+
raise Exception("Token budget exceeded!")
|
| 207 |
+
|
| 208 |
+
tokens = count_tokens(text)
|
| 209 |
+
self.used_tokens += tokens
|
| 210 |
+
|
| 211 |
+
return function(text)
|
| 212 |
+
|
| 213 |
+
def get_usage(self):
|
| 214 |
+
"""Get current token usage."""
|
| 215 |
+
return {
|
| 216 |
+
"used": self.used_tokens,
|
| 217 |
+
"remaining": self.max_tokens - self.used_tokens,
|
| 218 |
+
"percentage": (self.used_tokens / self.max_tokens) * 100
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
# Usage
|
| 222 |
+
budget = TokenBudget(max_tokens=50000)
|
| 223 |
+
|
| 224 |
+
def make_llm_call(text):
|
| 225 |
+
return client.chat.completions.create(
|
| 226 |
+
model="gpt-4",
|
| 227 |
+
messages=[{"role": "user", "content": text}]
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
try:
|
| 231 |
+
result = budget.process("Your text here", make_llm_call)
|
| 232 |
+
print(budget.get_usage())
|
| 233 |
+
except Exception as e:
|
| 234 |
+
print(f"Error: {e}")
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
## Constraints
|
| 238 |
+
- **Quality vs. Cost**: Aggressive optimization may impact output quality
|
| 239 |
+
- **Context Loss**: Removing too much content may lose important information
|
| 240 |
+
- **Model Limitations**: Different models have different capabilities
|
| 241 |
+
- **Token Estimation**: Token counts are estimates, not exact
|
| 242 |
+
- **Budget Planning**: Always include buffer for unexpected token usage
|
| 243 |
+
|
| 244 |
+
## Expected Output
|
| 245 |
+
Reduced API costs (30-70% savings) while maintaining acceptable output quality through intelligent token optimization strategies.
|
TRAE-Skills/ai_engineering/Vector_Database_Setup.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Vector Database Setup
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To provision and configure a vector database (Vector DB) for storing high-dimensional embeddings, enabling semantic search and RAG applications.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- Implementing RAG (Retrieval-Augmented Generation).
|
| 8 |
+
- Building recommendation systems based on similarity.
|
| 9 |
+
- Implementing semantic search (search by meaning, not just keywords).
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Choice of Database (Selection)
|
| 14 |
+
- **Pinecone**: Best for managed, serverless, and fast scaling.
|
| 15 |
+
- **pgvector**: Best for existing PostgreSQL users who want to keep data in one place.
|
| 16 |
+
- **Chroma**: Best for local development and simple prototyping.
|
| 17 |
+
|
| 18 |
+
### 2. Implementation: Pinecone (Managed)
|
| 19 |
+
Install the client and initialize the index.
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
npm install @pinecone-database/pinecone
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
```typescript
|
| 26 |
+
import { Pinecone } from '@pinecone-database/pinecone';
|
| 27 |
+
|
| 28 |
+
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY! });
|
| 29 |
+
|
| 30 |
+
async function setupIndex() {
|
| 31 |
+
await pc.createIndex({
|
| 32 |
+
name: 'my-index',
|
| 33 |
+
dimension: 1536, // Must match embedding model (e.g., text-embedding-3-small)
|
| 34 |
+
metric: 'cosine',
|
| 35 |
+
spec: {
|
| 36 |
+
serverless: {
|
| 37 |
+
cloud: 'aws',
|
| 38 |
+
region: 'us-east-1'
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
});
|
| 42 |
+
}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
### 3. Implementation: pgvector (PostgreSQL)
|
| 46 |
+
Enable the extension and create a table with a vector column.
|
| 47 |
+
|
| 48 |
+
```sql
|
| 49 |
+
-- 1. Enable extension
|
| 50 |
+
CREATE EXTENSION IF NOT EXISTS vector;
|
| 51 |
+
|
| 52 |
+
-- 2. Create table
|
| 53 |
+
CREATE TABLE documents (
|
| 54 |
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 55 |
+
content text,
|
| 56 |
+
metadata jsonb,
|
| 57 |
+
embedding vector(1536) -- Match your model's dimensions
|
| 58 |
+
);
|
| 59 |
+
|
| 60 |
+
-- 3. Create index for fast search (HNSW is recommended)
|
| 61 |
+
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### 4. Data Insertion (Upsert)
|
| 65 |
+
Always batch your insertions for efficiency.
|
| 66 |
+
|
| 67 |
+
```typescript
|
| 68 |
+
// Pinecone example
|
| 69 |
+
const index = pc.index('my-index');
|
| 70 |
+
|
| 71 |
+
await index.upsert([
|
| 72 |
+
{
|
| 73 |
+
id: 'doc1',
|
| 74 |
+
values: [0.1, 0.2, ...], // The embedding vector
|
| 75 |
+
metadata: { text: 'The actual content...', category: 'legal' }
|
| 76 |
+
}
|
| 77 |
+
]);
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### 5. Querying (Semantic Search)
|
| 81 |
+
Perform a similarity search using a query vector.
|
| 82 |
+
|
| 83 |
+
```typescript
|
| 84 |
+
const queryResponse = await index.query({
|
| 85 |
+
vector: [0.1, 0.2, ...], // Vector of the user query
|
| 86 |
+
topK: 5,
|
| 87 |
+
includeMetadata: true,
|
| 88 |
+
});
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
## Constraints
|
| 92 |
+
- **Dimension Matching**: The dimension of the index MUST exactly match the output dimension of your embedding model.
|
| 93 |
+
- **Metric Selection**: Use `cosine` for text; `euclidean` or `dotproduct` for other specific use cases.
|
| 94 |
+
- **Batch Limits**: Most vector DBs have limits on payload size per upsert (e.g., 2MB or 100 vectors).
|
| 95 |
+
|
| 96 |
+
## Expected Output
|
| 97 |
+
A fully configured vector index ready for high-speed similarity searches and data retrieval.
|
TRAE-Skills/ai_engineering/Vector_Databases_Pinecone_Weaviate.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Advanced Vector Databases (Pinecone & Weaviate)
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To build production-ready RAG, semantic search, and similarity systems with modern vector databases.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- For building semantic search over large document collections
|
| 8 |
+
- When implementing RAG (Retrieval-Augmented Generation)
|
| 9 |
+
- For recommendation systems using embeddings
|
| 10 |
+
- When building question-answering systems
|
| 11 |
+
- For similarity search (images, text, audio)
|
| 12 |
+
|
| 13 |
+
## Procedure
|
| 14 |
+
|
| 15 |
+
### 1. Pinecone Setup & Indexing
|
| 16 |
+
Get started with Pinecone.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
import os
|
| 20 |
+
from pinecone import Pinecone, ServerlessSpec
|
| 21 |
+
from langchain_openai import OpenAIEmbeddings
|
| 22 |
+
from langchain_core.documents import Document
|
| 23 |
+
from langchain_pinecone import PineconeVectorStore
|
| 24 |
+
|
| 25 |
+
# Initialize Pinecone
|
| 26 |
+
pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))
|
| 27 |
+
|
| 28 |
+
# Create index
|
| 29 |
+
index_name = "rag-index"
|
| 30 |
+
if index_name not in pc.list_indexes().names():
|
| 31 |
+
pc.create_index(
|
| 32 |
+
name=index_name,
|
| 33 |
+
dimension=1536, # text-embedding-3-small
|
| 34 |
+
metric="cosine",
|
| 35 |
+
spec=ServerlessSpec(cloud="aws", region="us-east-1")
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
# Connect to index
|
| 39 |
+
index = pc.Index(index_name)
|
| 40 |
+
|
| 41 |
+
# Create embeddings
|
| 42 |
+
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
|
| 43 |
+
|
| 44 |
+
# Sample documents
|
| 45 |
+
documents = [
|
| 46 |
+
Document(page_content="LangGraph is a library for building stateful agents.", metadata={"source": "doc1", "category": "ai"}),
|
| 47 |
+
Document(page_content="Vector databases store embeddings for similarity search.", metadata={"source": "doc2", "category": "database"}),
|
| 48 |
+
Document(page_content="RAG combines retrieval with LLM generation.", metadata={"source": "doc3", "category": "ai"}),
|
| 49 |
+
Document(page_content="Pinecone is a serverless vector database.", metadata={"source": "doc4", "category": "database"})
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
# Add to vector store
|
| 53 |
+
vector_store = PineconeVectorStore.from_documents(
|
| 54 |
+
documents=documents,
|
| 55 |
+
embedding=embeddings,
|
| 56 |
+
index_name=index_name,
|
| 57 |
+
namespace="production"
|
| 58 |
+
)
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### 2. Advanced Retrieval with Pinecone
|
| 62 |
+
Implement hybrid search and filtering.
|
| 63 |
+
|
| 64 |
+
```python
|
| 65 |
+
# Semantic search
|
| 66 |
+
results = vector_store.similarity_search(
|
| 67 |
+
"What is LangGraph?",
|
| 68 |
+
k=3,
|
| 69 |
+
filter={"category": "ai"} # Metadata filtering
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# Similarity search with score
|
| 73 |
+
results_with_scores = vector_store.similarity_search_with_score(
|
| 74 |
+
"Tell me about vector databases",
|
| 75 |
+
k=3
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
# Hybrid search (dense + sparse)
|
| 79 |
+
# Use Pinecone's hybrid search with BM25
|
| 80 |
+
from langchain_community.retrievers import PineconeHybridSearchRetriever
|
| 81 |
+
from pinecone_text.sparse import BM25Encoder
|
| 82 |
+
|
| 83 |
+
bm25_encoder = BM25Encoder()
|
| 84 |
+
bm25_encoder.fit([d.page_content for d in documents])
|
| 85 |
+
|
| 86 |
+
hybrid_retriever = PineconeHybridSearchRetriever(
|
| 87 |
+
embeddings=embeddings,
|
| 88 |
+
sparse_encoder=bm25_encoder,
|
| 89 |
+
index=index,
|
| 90 |
+
namespace="production",
|
| 91 |
+
top_k=3
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
hybrid_results = hybrid_retriever.invoke("What is RAG?")
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### 3. Weaviate Setup
|
| 98 |
+
Use Weaviate for self-hosted or managed vector search.
|
| 99 |
+
|
| 100 |
+
```python
|
| 101 |
+
import weaviate
|
| 102 |
+
import weaviate.classes as wvc
|
| 103 |
+
from weaviate.classes.config import Property, DataType, Configure
|
| 104 |
+
|
| 105 |
+
# Connect to Weaviate
|
| 106 |
+
client = weaviate.connect_to_wcs(
|
| 107 |
+
cluster_url=os.getenv("WEAVIATE_CLUSTER_URL"),
|
| 108 |
+
auth_credentials=weaviate.auth.AuthApiKey(os.getenv("WEAVIATE_API_KEY")),
|
| 109 |
+
headers={
|
| 110 |
+
"X-OpenAI-Api-Key": os.getenv("OPENAI_API_KEY")
|
| 111 |
+
}
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
# Create collection
|
| 115 |
+
if not client.collections.exists("Document"):
|
| 116 |
+
collection = client.collections.create(
|
| 117 |
+
name="Document",
|
| 118 |
+
vectorizer_config=Configure.Vectorizer.text2vec_openai(model="text-embedding-3-small"),
|
| 119 |
+
generative_config=Configure.Generative.openai(model="gpt-4o"),
|
| 120 |
+
properties=[
|
| 121 |
+
Property(name="content", data_type=DataType.TEXT),
|
| 122 |
+
Property(name="source", data_type=DataType.TEXT),
|
| 123 |
+
Property(name="category", data_type=DataType.TEXT),
|
| 124 |
+
Property(name="created_at", data_type=DataType.DATE)
|
| 125 |
+
]
|
| 126 |
+
)
|
| 127 |
+
else:
|
| 128 |
+
collection = client.collections.get("Document")
|
| 129 |
+
|
| 130 |
+
# Add objects
|
| 131 |
+
with collection.batch.dynamic() as batch:
|
| 132 |
+
for doc in documents:
|
| 133 |
+
batch.add_object(
|
| 134 |
+
properties={
|
| 135 |
+
"content": doc.page_content,
|
| 136 |
+
"source": doc.metadata["source"],
|
| 137 |
+
"category": doc.metadata["category"],
|
| 138 |
+
"created_at": "2024-01-01T00:00:00Z"
|
| 139 |
+
}
|
| 140 |
+
)
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
### 4. Weaviate Generative Search (RAG)
|
| 144 |
+
Built-in generative search with Weaviate.
|
| 145 |
+
|
| 146 |
+
```python
|
| 147 |
+
# Basic search
|
| 148 |
+
response = collection.query.near_text(
|
| 149 |
+
query="What is LangGraph?",
|
| 150 |
+
limit=3,
|
| 151 |
+
filters=wvc.query.Filter.by_property("category").equal("ai"),
|
| 152 |
+
return_properties=["content", "source"]
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# Generative search (RAG)
|
| 156 |
+
generative_response = collection.generate.near_text(
|
| 157 |
+
query="Explain LangGraph in simple terms",
|
| 158 |
+
limit=3,
|
| 159 |
+
single_prompt="Summarize this content: {content}"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
for obj in generative_response.objects:
|
| 163 |
+
print(f"Source: {obj.properties['source']}")
|
| 164 |
+
print(f"Generated: {obj.generated}")
|
| 165 |
+
print("---")
|
| 166 |
+
|
| 167 |
+
# Grouped task
|
| 168 |
+
grouped_response = collection.generate.near_text(
|
| 169 |
+
query="Tell me about vector databases",
|
| 170 |
+
limit=5,
|
| 171 |
+
grouped_task="Write a comprehensive summary of all these documents"
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
print("Grouped Summary:", grouped_response.generated)
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
### 5. Production Best Practices
|
| 178 |
+
Optimize vector DB for production.
|
| 179 |
+
|
| 180 |
+
```python
|
| 181 |
+
# Pinecone optimization
|
| 182 |
+
# 1. Use namespaces for isolation
|
| 183 |
+
vector_store = PineconeVectorStore(
|
| 184 |
+
index=index,
|
| 185 |
+
embedding=embeddings,
|
| 186 |
+
namespace="tenant-123"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
# 2. Batch operations
|
| 190 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 191 |
+
|
| 192 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
| 193 |
+
chunk_size=1000,
|
| 194 |
+
chunk_overlap=200
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
split_docs = text_splitter.split_documents(documents)
|
| 198 |
+
|
| 199 |
+
# Add in batches
|
| 200 |
+
for i in range(0, len(split_docs), 100):
|
| 201 |
+
batch = split_docs[i:i+100]
|
| 202 |
+
vector_store.add_documents(batch)
|
| 203 |
+
|
| 204 |
+
# 3. Weaviate: Use tenant isolation
|
| 205 |
+
multi_tenancy_config = Configure.multi_tenancy(enabled=True)
|
| 206 |
+
|
| 207 |
+
# 4. Monitor usage
|
| 208 |
+
stats = index.describe_index_stats()
|
| 209 |
+
print(f"Total vectors: {stats['total_vector_count']}")
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
## Best Practices
|
| 213 |
+
- **Embedding Model**: Choose appropriate embedding model (dimensions, cost)
|
| 214 |
+
- **Chunking**: Use good text splitting strategy (chunk size, overlap)
|
| 215 |
+
- **Metadata**: Add rich metadata for filtering
|
| 216 |
+
- **Indexing**: Use batch operations for large datasets
|
| 217 |
+
- **Hybrid Search**: Combine vector + keyword search for better results
|
| 218 |
+
- **Namespaces/Tenants**: Isolate data for multi-tenant apps
|
| 219 |
+
- **Monitoring**: Monitor query latency and cost
|
| 220 |
+
- **Caching**: Cache frequent queries to reduce cost
|
TRAE-Skills/architecture/API_Gateway_Pattern.md
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: API Gateway Pattern
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To provide a single entry point for microservices by implementing a centralized API gateway that handles routing, authentication, rate limiting, and cross-cutting concerns.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When building microservices architectures
|
| 8 |
+
- When you need a single entry point for multiple services
|
| 9 |
+
- When implementing cross-cutting concerns like auth, logging, rate limiting
|
| 10 |
+
- When aggregating responses from multiple services
|
| 11 |
+
|
| 12 |
+
## Procedure
|
| 13 |
+
|
| 14 |
+
### 1. Basic API Gateway Setup
|
| 15 |
+
Implement a basic API gateway with routing.
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 19 |
+
from fastapi.responses import JSONResponse
|
| 20 |
+
from httpx import AsyncClient
|
| 21 |
+
import logging
|
| 22 |
+
from typing import Dict, Any
|
| 23 |
+
import asyncio
|
| 24 |
+
|
| 25 |
+
class APIGateway:
|
| 26 |
+
"""API Gateway for microservices."""
|
| 27 |
+
|
| 28 |
+
def __init__(self):
|
| 29 |
+
self.app = FastAPI(title="API Gateway")
|
| 30 |
+
self.services: Dict[str, str] = {}
|
| 31 |
+
self.client = AsyncClient()
|
| 32 |
+
self.logger = logging.getLogger("APIGateway")
|
| 33 |
+
|
| 34 |
+
# Setup middleware
|
| 35 |
+
self._setup_middleware()
|
| 36 |
+
|
| 37 |
+
# Setup routes
|
| 38 |
+
self._setup_routes()
|
| 39 |
+
|
| 40 |
+
def _setup_middleware(self):
|
| 41 |
+
"""Setup gateway middleware."""
|
| 42 |
+
@self.app.middleware("http")
|
| 43 |
+
async def log_requests(request: Request, call_next):
|
| 44 |
+
start_time = asyncio.get_event_loop().time()
|
| 45 |
+
|
| 46 |
+
# Log request
|
| 47 |
+
self.logger.info(f"Incoming request: {request.method} {request.url.path}")
|
| 48 |
+
|
| 49 |
+
# Process request
|
| 50 |
+
response = await call_next(request)
|
| 51 |
+
|
| 52 |
+
# Log response
|
| 53 |
+
process_time = asyncio.get_event_loop().time() - start_time
|
| 54 |
+
self.logger.info(f"Request completed in {process_time:.3f}s")
|
| 55 |
+
|
| 56 |
+
response.headers["X-Process-Time"] = str(process_time)
|
| 57 |
+
return response
|
| 58 |
+
|
| 59 |
+
def _setup_routes(self):
|
| 60 |
+
"""Setup gateway routes."""
|
| 61 |
+
|
| 62 |
+
@self.app.get("/health")
|
| 63 |
+
async def health_check():
|
| 64 |
+
return {"status": "healthy", "gateway": "API Gateway v1.0"}
|
| 65 |
+
|
| 66 |
+
@self.app.api_route("/{service}/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
|
| 67 |
+
async def proxy_request(service: str, path: str, request: Request):
|
| 68 |
+
"""Proxy request to appropriate service."""
|
| 69 |
+
if service not in self.services:
|
| 70 |
+
raise HTTPException(status_code=404, detail=f"Service '{service}' not found")
|
| 71 |
+
|
| 72 |
+
service_url = self.services[service]
|
| 73 |
+
target_url = f"{service_url}/{path}"
|
| 74 |
+
|
| 75 |
+
# Forward request
|
| 76 |
+
body = await request.body()
|
| 77 |
+
headers = dict(request.headers)
|
| 78 |
+
|
| 79 |
+
try:
|
| 80 |
+
response = await self.client.request(
|
| 81 |
+
method=request.method,
|
| 82 |
+
url=target_url,
|
| 83 |
+
headers=headers,
|
| 84 |
+
content=body,
|
| 85 |
+
timeout=30.0
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
return JSONResponse(
|
| 89 |
+
content=response.json(),
|
| 90 |
+
status_code=response.status_code,
|
| 91 |
+
headers=dict(response.headers)
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
except Exception as e:
|
| 95 |
+
self.logger.error(f"Error proxying request: {str(e)}")
|
| 96 |
+
raise HTTPException(status_code=503, detail="Service unavailable")
|
| 97 |
+
|
| 98 |
+
def register_service(self, name: str, url: str):
|
| 99 |
+
"""Register a microservice."""
|
| 100 |
+
self.services[name] = url
|
| 101 |
+
self.logger.info(f"Registered service: {name} -> {url}")
|
| 102 |
+
|
| 103 |
+
def get_app(self):
|
| 104 |
+
"""Get FastAPI application."""
|
| 105 |
+
return self.app
|
| 106 |
+
|
| 107 |
+
# Usage
|
| 108 |
+
gateway = APIGateway()
|
| 109 |
+
|
| 110 |
+
# Register services
|
| 111 |
+
gateway.register_service("users", "http://localhost:8001")
|
| 112 |
+
gateway.register_service("products", "http://localhost:8002")
|
| 113 |
+
gateway.register_service("orders", "http://localhost:8003")
|
| 114 |
+
|
| 115 |
+
app = gateway.get_app()
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### 2. Authentication and Authorization
|
| 119 |
+
Implement authentication middleware.
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
from fastapi import Security, HTTPException, status
|
| 123 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 124 |
+
import jwt
|
| 125 |
+
from datetime import datetime, timedelta
|
| 126 |
+
|
| 127 |
+
security = HTTPBearer()
|
| 128 |
+
|
| 129 |
+
class AuthMiddleware:
|
| 130 |
+
"""Authentication middleware for API Gateway."""
|
| 131 |
+
|
| 132 |
+
def __init__(self, secret_key: str, algorithm: str = "HS256"):
|
| 133 |
+
self.secret_key = secret_key
|
| 134 |
+
self.algorithm = algorithm
|
| 135 |
+
|
| 136 |
+
def create_token(self, user_id: str, permissions: list, expires_delta: timedelta = None):
|
| 137 |
+
"""Create JWT token."""
|
| 138 |
+
if expires_delta:
|
| 139 |
+
expire = datetime.utcnow() + expires_delta
|
| 140 |
+
else:
|
| 141 |
+
expire = datetime.utcnow() + timedelta(hours=1)
|
| 142 |
+
|
| 143 |
+
payload = {
|
| 144 |
+
"user_id": user_id,
|
| 145 |
+
"permissions": permissions,
|
| 146 |
+
"exp": expire
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
| 150 |
+
|
| 151 |
+
def verify_token(self, token: str) -> Dict[str, Any]:
|
| 152 |
+
"""Verify JWT token."""
|
| 153 |
+
try:
|
| 154 |
+
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
| 155 |
+
return payload
|
| 156 |
+
except jwt.ExpiredSignatureError:
|
| 157 |
+
raise HTTPException(
|
| 158 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 159 |
+
detail="Token expired"
|
| 160 |
+
)
|
| 161 |
+
except jwt.JWTError:
|
| 162 |
+
raise HTTPException(
|
| 163 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 164 |
+
detail="Invalid token"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
def check_permission(self, required_permission: str):
|
| 168 |
+
"""Check if user has required permission."""
|
| 169 |
+
async def permission_checker(credentials: HTTPAuthorizationCredentials = Security(security)):
|
| 170 |
+
payload = self.verify_token(credentials.credentials)
|
| 171 |
+
permissions = payload.get("permissions", [])
|
| 172 |
+
|
| 173 |
+
if required_permission not in permissions:
|
| 174 |
+
raise HTTPException(
|
| 175 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
| 176 |
+
detail=f"Permission '{required_permission}' required"
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
return payload
|
| 180 |
+
|
| 181 |
+
return permission_checker
|
| 182 |
+
|
| 183 |
+
# Integrate with gateway
|
| 184 |
+
auth_middleware = AuthMiddleware(secret_key="your-secret-key")
|
| 185 |
+
|
| 186 |
+
@gateway.app.get("/protected")
|
| 187 |
+
async def protected_endpoint(user_data: Dict = Depends(auth_middleware.check_permission("read:protected"))):
|
| 188 |
+
return {"message": "Access granted", "user": user_data["user_id"]}
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
### 3. Rate Limiting
|
| 192 |
+
Implement rate limiting for API protection.
|
| 193 |
+
|
| 194 |
+
```python
|
| 195 |
+
from fastapi import Request, HTTPException
|
| 196 |
+
from collections import defaultdict
|
| 197 |
+
import time
|
| 198 |
+
from typing import Dict
|
| 199 |
+
|
| 200 |
+
class RateLimiter:
|
| 201 |
+
"""Rate limiter using sliding window algorithm."""
|
| 202 |
+
|
| 203 |
+
def __init__(self, requests_per_minute: int = 60):
|
| 204 |
+
self.requests_per_minute = requests_per_minute
|
| 205 |
+
self.requests: Dict[str, list] = defaultdict(list)
|
| 206 |
+
|
| 207 |
+
def is_allowed(self, key: str) -> bool:
|
| 208 |
+
"""Check if request is allowed."""
|
| 209 |
+
now = time.time()
|
| 210 |
+
minute_ago = now - 60
|
| 211 |
+
|
| 212 |
+
# Remove old requests
|
| 213 |
+
self.requests[key] = [
|
| 214 |
+
req_time for req_time in self.requests[key]
|
| 215 |
+
if req_time > minute_ago
|
| 216 |
+
]
|
| 217 |
+
|
| 218 |
+
# Check if under limit
|
| 219 |
+
if len(self.requests[key]) >= self.requests_per_minute:
|
| 220 |
+
return False
|
| 221 |
+
|
| 222 |
+
# Add current request
|
| 223 |
+
self.requests[key].append(now)
|
| 224 |
+
return True
|
| 225 |
+
|
| 226 |
+
class RateLimitMiddleware:
|
| 227 |
+
"""Rate limiting middleware."""
|
| 228 |
+
|
| 229 |
+
def __init__(self, limiter: RateLimiter):
|
| 230 |
+
self.limiter = limiter
|
| 231 |
+
|
| 232 |
+
async def __call__(self, request: Request, call_next):
|
| 233 |
+
"""Process request with rate limiting."""
|
| 234 |
+
# Use API key or IP address as key
|
| 235 |
+
api_key = request.headers.get("X-API-Key", request.client.host)
|
| 236 |
+
|
| 237 |
+
if not self.limiter.is_allowed(api_key):
|
| 238 |
+
raise HTTPException(
|
| 239 |
+
status_code=429,
|
| 240 |
+
detail="Too many requests"
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
return await call_next(request)
|
| 244 |
+
|
| 245 |
+
# Integrate with gateway
|
| 246 |
+
rate_limiter = RateLimiter(requests_per_minute=60)
|
| 247 |
+
gateway.app.middleware("http")(RateLimitMiddleware(rate_limiter))
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
### 4. Service Aggregation
|
| 251 |
+
Implement response aggregation from multiple services.
|
| 252 |
+
|
| 253 |
+
```python
|
| 254 |
+
class ServiceAggregator:
|
| 255 |
+
"""Aggregate responses from multiple services."""
|
| 256 |
+
|
| 257 |
+
def __init__(self, gateway: APIGateway):
|
| 258 |
+
self.gateway = gateway
|
| 259 |
+
|
| 260 |
+
async def aggregate_user_data(self, user_id: str) -> Dict[str, Any]:
|
| 261 |
+
"""Aggregate user data from multiple services."""
|
| 262 |
+
tasks = [
|
| 263 |
+
self.fetch_user_profile(user_id),
|
| 264 |
+
self.fetch_user_orders(user_id),
|
| 265 |
+
self.fetch_user_recommendations(user_id)
|
| 266 |
+
]
|
| 267 |
+
|
| 268 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 269 |
+
|
| 270 |
+
return {
|
| 271 |
+
"user_id": user_id,
|
| 272 |
+
"profile": results[0] if not isinstance(results[0], Exception) else None,
|
| 273 |
+
"orders": results[1] if not isinstance(results[1], Exception) else None,
|
| 274 |
+
"recommendations": results[2] if not isinstance(results[2], Exception) else None
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
async def fetch_user_profile(self, user_id: str) -> Dict[str, Any]:
|
| 278 |
+
"""Fetch user profile."""
|
| 279 |
+
service_url = self.gateway.services.get("users")
|
| 280 |
+
if not service_url:
|
| 281 |
+
raise ValueError("Users service not found")
|
| 282 |
+
|
| 283 |
+
response = await self.gateway.client.get(f"{service_url}/users/{user_id}")
|
| 284 |
+
return response.json()
|
| 285 |
+
|
| 286 |
+
async def fetch_user_orders(self, user_id: str) -> Dict[str, Any]:
|
| 287 |
+
"""Fetch user orders."""
|
| 288 |
+
service_url = self.gateway.services.get("orders")
|
| 289 |
+
if not service_url:
|
| 290 |
+
raise ValueError("Orders service not found")
|
| 291 |
+
|
| 292 |
+
response = await self.gateway.client.get(f"{service_url}/orders/{user_id}")
|
| 293 |
+
return response.json()
|
| 294 |
+
|
| 295 |
+
async def fetch_user_recommendations(self, user_id: str) -> Dict[str, Any]:
|
| 296 |
+
"""Fetch user recommendations."""
|
| 297 |
+
service_url = self.gateway.services.get("products")
|
| 298 |
+
if not service_url:
|
| 299 |
+
raise ValueError("Products service not found")
|
| 300 |
+
|
| 301 |
+
response = await self.gateway.client.get(f"{service_url}/recommendations/{user_id}")
|
| 302 |
+
return response.json()
|
| 303 |
+
|
| 304 |
+
# Add aggregation endpoint
|
| 305 |
+
aggregator = ServiceAggregator(gateway)
|
| 306 |
+
|
| 307 |
+
@gateway.app.get("/aggregate/user/{user_id}")
|
| 308 |
+
async def aggregate_user_endpoint(user_id: str):
|
| 309 |
+
"""Aggregate user data."""
|
| 310 |
+
return await aggregator.aggregate_user_data(user_id)
|
| 311 |
+
```
|
| 312 |
+
|
| 313 |
+
### 5. Circuit Breaker Pattern
|
| 314 |
+
Implement circuit breaker for fault tolerance.
|
| 315 |
+
|
| 316 |
+
```python
|
| 317 |
+
from enum import Enum
|
| 318 |
+
import asyncio
|
| 319 |
+
|
| 320 |
+
class CircuitState(Enum):
|
| 321 |
+
CLOSED = "closed" # Normal operation
|
| 322 |
+
OPEN = "open" # Failing, reject requests
|
| 323 |
+
HALF_OPEN = "half_open" # Testing if service recovered
|
| 324 |
+
|
| 325 |
+
class CircuitBreaker:
|
| 326 |
+
"""Circuit breaker for service resilience."""
|
| 327 |
+
|
| 328 |
+
def __init__(self, failure_threshold: int = 5, timeout: int = 60):
|
| 329 |
+
self.failure_threshold = failure_threshold
|
| 330 |
+
self.timeout = timeout
|
| 331 |
+
self.failure_count = 0
|
| 332 |
+
self.state = CircuitState.CLOSED
|
| 333 |
+
self.last_failure_time = None
|
| 334 |
+
|
| 335 |
+
def record_success(self):
|
| 336 |
+
"""Record successful request."""
|
| 337 |
+
self.failure_count = 0
|
| 338 |
+
if self.state == CircuitState.HALF_OPEN:
|
| 339 |
+
self.state = CircuitState.CLOSED
|
| 340 |
+
|
| 341 |
+
def record_failure(self):
|
| 342 |
+
"""Record failed request."""
|
| 343 |
+
self.failure_count += 1
|
| 344 |
+
self.last_failure_time = time.time()
|
| 345 |
+
|
| 346 |
+
if self.failure_count >= self.failure_threshold:
|
| 347 |
+
self.state = CircuitState.OPEN
|
| 348 |
+
|
| 349 |
+
def can_attempt(self) -> bool:
|
| 350 |
+
"""Check if request can be attempted."""
|
| 351 |
+
if self.state == CircuitState.CLOSED:
|
| 352 |
+
return True
|
| 353 |
+
|
| 354 |
+
if self.state == CircuitState.OPEN:
|
| 355 |
+
if time.time() - self.last_failure_time > self.timeout:
|
| 356 |
+
self.state = CircuitState.HALF_OPEN
|
| 357 |
+
return True
|
| 358 |
+
return False
|
| 359 |
+
|
| 360 |
+
return True # HALF_OPEN state
|
| 361 |
+
|
| 362 |
+
class CircuitBreakerMiddleware:
|
| 363 |
+
"""Circuit breaker middleware."""
|
| 364 |
+
|
| 365 |
+
def __init__(self):
|
| 366 |
+
self.circuit_breakers: Dict[str, CircuitBreaker] = {}
|
| 367 |
+
|
| 368 |
+
def get_breaker(self, service_name: str) -> CircuitBreaker:
|
| 369 |
+
"""Get or create circuit breaker for service."""
|
| 370 |
+
if service_name not in self.circuit_breakers:
|
| 371 |
+
self.circuit_breakers[service_name] = CircuitBreaker()
|
| 372 |
+
return self.circuit_breakers[service_name]
|
| 373 |
+
|
| 374 |
+
async def call_with_breaker(self, service_name: str, callable_func, *args, **kwargs):
|
| 375 |
+
"""Call service with circuit breaker protection."""
|
| 376 |
+
breaker = self.get_breaker(service_name)
|
| 377 |
+
|
| 378 |
+
if not breaker.can_attempt():
|
| 379 |
+
raise HTTPException(
|
| 380 |
+
status_code=503,
|
| 381 |
+
detail=f"Service '{service_name}' is temporarily unavailable"
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
try:
|
| 385 |
+
result = await callable_func(*args, **kwargs)
|
| 386 |
+
breaker.record_success()
|
| 387 |
+
return result
|
| 388 |
+
except Exception as e:
|
| 389 |
+
breaker.record_failure()
|
| 390 |
+
raise e
|
| 391 |
+
|
| 392 |
+
# Usage
|
| 393 |
+
circuit_breaker = CircuitBreakerMiddleware()
|
| 394 |
+
|
| 395 |
+
@gateway.app.get("/products/{product_id}")
|
| 396 |
+
async def get_product(product_id: str):
|
| 397 |
+
"""Get product with circuit breaker protection."""
|
| 398 |
+
async def fetch_product():
|
| 399 |
+
service_url = gateway.services.get("products")
|
| 400 |
+
response = await gateway.client.get(f"{service_url}/products/{product_id}")
|
| 401 |
+
return response.json()
|
| 402 |
+
|
| 403 |
+
return await circuit_breaker.call_with_breaker("products", fetch_product)
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
### 6. Configuration and Service Discovery
|
| 407 |
+
Implement service discovery and configuration management.
|
| 408 |
+
|
| 409 |
+
```python
|
| 410 |
+
import consul
|
| 411 |
+
|
| 412 |
+
class ServiceRegistry:
|
| 413 |
+
"""Service registry using Consul."""
|
| 414 |
+
|
| 415 |
+
def __init__(self, consul_host: str = "localhost", consul_port: int = 8500):
|
| 416 |
+
self.consul = consul.Consul(host=consul_host, port=consul_port)
|
| 417 |
+
self.logger = logging.getLogger("ServiceRegistry")
|
| 418 |
+
|
| 419 |
+
def register_service(self, service_name: str, service_id: str, address: str, port: int):
|
| 420 |
+
"""Register service with Consul."""
|
| 421 |
+
self.consul.agent.service.register(
|
| 422 |
+
name=service_name,
|
| 423 |
+
service_id=service_id,
|
| 424 |
+
address=address,
|
| 425 |
+
port=port,
|
| 426 |
+
check=consul.Check.http(f"http://{address}:{port}/health", interval="10s")
|
| 427 |
+
)
|
| 428 |
+
self.logger.info(f"Registered service: {service_name} ({service_id})")
|
| 429 |
+
|
| 430 |
+
def deregister_service(self, service_id: str):
|
| 431 |
+
"""Deregister service from Consul."""
|
| 432 |
+
self.consul.agent.service.deregister(service_id)
|
| 433 |
+
self.logger.info(f"Deregistered service: {service_id}")
|
| 434 |
+
|
| 435 |
+
def discover_service(self, service_name: str) -> list:
|
| 436 |
+
"""Discover service instances."""
|
| 437 |
+
_, services = self.consul.health.service(service_name, passing=True)
|
| 438 |
+
|
| 439 |
+
instances = []
|
| 440 |
+
for service in services:
|
| 441 |
+
instance = {
|
| 442 |
+
"id": service["Service"]["ID"],
|
| 443 |
+
"address": service["Service"]["Address"],
|
| 444 |
+
"port": service["Service"]["Port"]
|
| 445 |
+
}
|
| 446 |
+
instances.append(instance)
|
| 447 |
+
|
| 448 |
+
return instances
|
| 449 |
+
|
| 450 |
+
def get_service_url(self, service_name: str) -> str:
|
| 451 |
+
"""Get service URL (load balanced)."""
|
| 452 |
+
instances = self.discover_service(service_name)
|
| 453 |
+
if not instances:
|
| 454 |
+
raise ValueError(f"No healthy instances found for {service_name}")
|
| 455 |
+
|
| 456 |
+
# Simple round-robin selection
|
| 457 |
+
instance = instances[0] # Could implement proper load balancing
|
| 458 |
+
return f"http://{instance['address']}:{instance['port']}"
|
| 459 |
+
|
| 460 |
+
# Integrate service discovery with gateway
|
| 461 |
+
service_registry = ServiceRegistry()
|
| 462 |
+
|
| 463 |
+
# Auto-discover and register services
|
| 464 |
+
async def refresh_services():
|
| 465 |
+
"""Refresh service registry."""
|
| 466 |
+
service_names = ["users", "products", "orders"]
|
| 467 |
+
|
| 468 |
+
for service_name in service_names:
|
| 469 |
+
try:
|
| 470 |
+
service_url = service_registry.get_service_url(service_name)
|
| 471 |
+
gateway.register_service(service_name, service_url)
|
| 472 |
+
except ValueError as e:
|
| 473 |
+
gateway.logger.warning(f"Service {service_name} not available: {str(e)}")
|
| 474 |
+
|
| 475 |
+
# Schedule periodic refresh
|
| 476 |
+
# asyncio.create_task(periodic_refresh())
|
| 477 |
+
```
|
| 478 |
+
|
| 479 |
+
## Constraints
|
| 480 |
+
- **Single Point of Failure**: API gateway can become a bottleneck - implement high availability
|
| 481 |
+
- **Performance**: Gateway adds latency - optimize routing and caching
|
| 482 |
+
- **Complexity**: Gateway logic can become complex - keep it focused
|
| 483 |
+
- **Scalability**: Design gateway to scale horizontally
|
| 484 |
+
- **Security**: Implement proper authentication and authorization
|
| 485 |
+
- **Monitoring**: Monitor gateway performance and service health
|
| 486 |
+
|
| 487 |
+
## Expected Output
|
| 488 |
+
A robust API gateway that provides single entry point, authentication, rate limiting, service aggregation, and fault tolerance for microservices architecture.
|
TRAE-Skills/architecture/Adapter_Pattern_TypeScript.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill: Adapter Pattern in TypeScript
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
To allow incompatible interfaces to work together by wrapping an object in an adapter that translates its interface into one that a client expects.
|
| 5 |
+
|
| 6 |
+
## When to Use
|
| 7 |
+
- When integrating a third-party library whose interface doesn't match your application's internal requirements.
|
| 8 |
+
- When you want to standardize multiple different implementations of a service (e.g., different payment gateways).
|
| 9 |
+
- When you need to provide a stable interface while the underlying dependency is subject to change.
|
| 10 |
+
|
| 11 |
+
## Procedure
|
| 12 |
+
|
| 13 |
+
### 1. Define the Target Interface
|
| 14 |
+
This is the interface your application expects to use.
|
| 15 |
+
|
| 16 |
+
```typescript
|
| 17 |
+
// logger.interface.ts
|
| 18 |
+
export interface ILogger {
|
| 19 |
+
log(message: string): void;
|
| 20 |
+
error(message: string): void;
|
| 21 |
+
}
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### 2. The Incompatible Service (Adaptee)
|
| 25 |
+
An external library or old code with a different interface.
|
| 26 |
+
|
| 27 |
+
```typescript
|
| 28 |
+
// legacy-logger.ts
|
| 29 |
+
export class LegacyLogger {
|
| 30 |
+
printMessage(msg: string) {
|
| 31 |
+
console.log(`[LEGACY]: ${msg}`);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
reportFailure(err: string) {
|
| 35 |
+
console.error(`[LEGACY ERROR]: ${err}`);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 3. Implement the Adapter
|
| 41 |
+
The adapter implements the `Target` interface and delegates work to the `Adaptee`.
|
| 42 |
+
|
| 43 |
+
```typescript
|
| 44 |
+
// logger-adapter.ts
|
| 45 |
+
import { ILogger } from './logger.interface';
|
| 46 |
+
import { LegacyLogger } from './legacy-logger';
|
| 47 |
+
|
| 48 |
+
export class LoggerAdapter implements ILogger {
|
| 49 |
+
private legacyLogger: LegacyLogger;
|
| 50 |
+
|
| 51 |
+
constructor(legacyLogger: LegacyLogger) {
|
| 52 |
+
this.legacyLogger = legacyLogger;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
log(message: string): void {
|
| 56 |
+
// Translate the call
|
| 57 |
+
this.legacyLogger.printMessage(message);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
error(message: string): void {
|
| 61 |
+
// Translate the call
|
| 62 |
+
this.legacyLogger.reportFailure(message);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 4. Usage in Client Code
|
| 68 |
+
The client only knows about the `ILogger` interface.
|
| 69 |
+
|
| 70 |
+
```typescript
|
| 71 |
+
function app(logger: ILogger) {
|
| 72 |
+
logger.log("Application started");
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
const legacy = new LegacyLogger();
|
| 76 |
+
const adapter = new LoggerAdapter(legacy);
|
| 77 |
+
|
| 78 |
+
app(adapter);
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## Constraints
|
| 82 |
+
- **Complexity**: Don't use the pattern if you can easily modify the original class to match the interface.
|
| 83 |
+
- **Performance**: While negligible, the extra layer of indirection adds a tiny overhead.
|
| 84 |
+
- **Single Responsibility**: The adapter should only focus on translation, not adding new business logic.
|
| 85 |
+
|
| 86 |
+
## Expected Output
|
| 87 |
+
A wrapper class that successfully bridges two incompatible interfaces, allowing them to communicate without changing their existing code.
|
TRAE-Skills/architecture/Authentication_Strategy_Selection.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authentication Strategy Selection
|
| 2 |
+
|
| 3 |
+
## Strategies
|
| 4 |
+
1. **Session-Based**: Server stores session ID in cookie/DB. Simple, stateful. Good for monoliths.
|
| 5 |
+
2. **JWT (Stateless)**: Server signs token, client stores it. Good for microservices/mobile. Harder to revoke.
|
| 6 |
+
3. **OAuth/OIDC**: Delegated auth (Login with Google). Best for user convenience and security.
|
| 7 |
+
4. **Passwordless**: Magic links, OTPs. Reduces friction.
|
| 8 |
+
|
| 9 |
+
## Security Considerations
|
| 10 |
+
- Always use HTTPS.
|
| 11 |
+
- Store passwords using bcrypt/argon2.
|
| 12 |
+
- Use `httpOnly` and `secure` cookies for storage where possible to prevent XSS.
|