# Session 6: Foundry Local – Models as Tools ## Abstract Treat models as composable tools inside a local AI operating layer. This session shows how to chain multiple specialized SLM/LLM calls, selectively route tasks, and expose a unified SDK surface to applications. You will build a lightweight model router + task planner, integrate it into an app script, and outline the scaling path to Azure AI Foundry for production workloads. ## Learning Objectives - **Conceptualize** models as atomic tools with declared capabilities - **Route** requests based on intent / heuristic scoring - **Chain** outputs across multi-step tasks (decompose → solve → refine) - **Integrate** a unified client API for downstream applications - **Scale** design to cloud (same OpenAI-compatible contract) ## Prerequisites - Sessions 1–5 completed - Multiple local models cached (e.g., `phi-4-mini`, `deepseek-coder-1.3b`, `qwen2.5-0.5b`) ### Cross-Platform Environment Snippet Windows PowerShell: ```powershell py -m venv .venv .\.venv\Scripts\Activate.ps1 pip install --upgrade pip pip install foundry-local-sdk openai ``` macOS / Linux: ```bash python3 -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip pip install foundry-local-sdk openai ``` Remote/VM service access from macOS: ```bash export FOUNDRY_LOCAL_ENDPOINT=http://:5273/v1 ``` ## Demo Flow (30 min) ### 1. Tool Capability Declaration (5 min) Create `samples/06-tools/models_catalog.py`: ```python CATALOG = { "phi-4-mini": { "capabilities": ["general", "reasoning", "summarize"], "priority": 2 }, "deepseek-coder-1.3b": { "capabilities": ["code", "refactor", "explain_code"], "priority": 1 }, "qwen2.5-0.5b": { "capabilities": ["fast", "classification", "lightweight"], "priority": 3 } } ``` ### 2. Intent Detection & Routing (8 min) Create `samples/06-tools/router.py`: ```python #!/usr/bin/env python3 """Model-as-tool router using Foundry Local OpenAI-compatible endpoint.""" from openai import OpenAI from models_catalog import CATALOG import re client = OpenAI(base_url="http://localhost:5273/v1", api_key="not-needed") INTENT_RULES = [ (re.compile(r"code|function|refactor|bug|optimi", re.I), "code"), (re.compile(r"summari|abstract|tl;dr", re.I), "summarize"), (re.compile(r"classif|label|category", re.I), "classification"), ] def detect_intent(prompt: str) -> str: for pat, intent in INTENT_RULES: if pat.search(prompt): return intent return "general" def select_model(intent: str) -> str: # Score catalog: capability match first, then priority scored = [] for name, meta in CATALOG.items(): caps = meta["capabilities"] match = intent in caps scored.append((name, match, meta["priority"])) # Sort: match True first, then lowest priority value scored.sort(key=lambda t: (not t[1], t[2])) return scored[0][0] def run(prompt: str): intent = detect_intent(prompt) model = select_model(intent) resp = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=400, temperature=0.5 ) return {"intent": intent, "model": model, "output": resp.choices[0].message.content} if __name__ == "__main__": tests = [ "Refactor this Python function for readability", "Summarize the importance of local AI governance", "Classify this feedback: 'The UI is slow and confusing'" ] for t in tests: r = run(t) print(f"Prompt: {t}\nModel: {r['model']} (intent={r['intent']})\nOutput: {r['output'][:160]}...\n") ``` ### 3. Multi-Step Task Chaining (7 min) Create `samples/06-tools/pipeline.py`: ```python #!/usr/bin/env python3 """Multi-step pipeline: plan -> solve -> refine using specialized models.""" from openai import OpenAI from router import detect_intent, select_model client = OpenAI(base_url="http://localhost:5273/v1", api_key="not-needed") def chat(model, content, temp=0.4): r = client.chat.completions.create( model=model, messages=[{"role": "user", "content": content}], max_tokens=350, temperature=temp ) return r.choices[0].message.content def pipeline(task: str): plan_model = select_model("general") plan = chat(plan_model, f"Break the task into 3 ordered steps. Task: {task}") steps = [s for s in plan.split('\n') if s.strip()][:3] outputs = [] for step in steps: intent = detect_intent(step) model = select_model(intent) out = chat(model, step) outputs.append((step, model, out)) refine_model = select_model("summarize") combined = '\n'.join(o[2] for o in outputs) refined = chat(refine_model, f"Condense results into a cohesive answer:\n{combined}") return {"plan": plan, "steps": outputs, "final": refined} if __name__ == '__main__': result = pipeline("Generate a refactored version of a slow Python loop and summarize performance gains.") print("PLAN:\n", result['plan']) print("FINAL:\n", result['final'][:400]) ``` ### 4. Starter Project: Adapt `06-models-as-tools` (5 min) Enhancements: - Add streaming token support (progressive UI update) - Add confidence scoring: lexical overlap or prompt rubric - Export trace JSON (intent → model → latency → token usage) - Implement cache reuse for repeated substeps ### 5. Scaling Path to Azure (5 min) | Layer | Local (Foundry) | Cloud (Azure AI Foundry) | Transition Strategy | |-------|-----------------|--------------------------|---------------------| | Routing | Heuristic Python | Durable microservice | Containerize & deploy API | | Models | SLMs cached | Managed deployments | Map local names to deployment IDs | | Observability | CLI stats/manual | Central logging & metrics | Add structured trace events | | Security | Local host only | Azure auth / networking | Introduce key vault for secrets | | Cost | Device resource | Consumption billing | Add budget guardrails | ## Validation Checklist ```powershell foundry model run phi-4-mini foundry model run deepseek-coder-1.3b python samples/06-tools/router.py python samples/06-tools/pipeline.py ``` Expect intent-based model selection and final refined output. ## Troubleshooting | Problem | Cause | Fix | |---------|-------|-----| | All tasks routed to same model | Weak rules | Enrich INTENT_RULES regex set | | Pipeline fails mid-step | Missing model loaded | Run `foundry model run ` | | Low output cohesion | No refine phase | Add summarization/validation pass | ## References - Foundry Local SDK: https://github.com/microsoft/Foundry-Local/tree/main/sdk/python - Azure AI Foundry Docs: https://learn.microsoft.com/azure/ai-foundry - Prompt Quality Patterns: See Session 2 --- **Session Duration**: 30 min **Difficulty**: Expert ## Sample Scenario & Workshop Mapping | Workshop Scripts / Notebooks | Scenario | Objective | Dataset / Catalog Source | |------------------------------|----------|-----------|---------------------------| | `samples/session06/models_router.py` / `notebooks/session06_models_router.ipynb` | Developer assistant handling mixed intent prompts (refactor, summarize, classify) | Heuristic intent → model alias routing with token usage | Inline `CATALOG` + regex `RULES` | | `samples/session06/models_pipeline.py` / `notebooks/session06_models_pipeline.ipynb` | Multi-step planning & refinement for complex coding assistance task | Decompose → specialized execution → summarization refine step | Same `CATALOG`; steps derived from plan output | ### Scenario Narrative An engineering productivity tool receives heterogeneous tasks: refactor code, summarize architectural notes, classify feedback. To minimize latency & resource usage, a small general model plans and summarizes, a code‑specialized model handles refactoring, and a lightweight classification-capable model labels feedback. The pipeline script demonstrates chaining + refinement; the router script isolates adaptive single‑prompt routing. ### Catalog Snapshot ```python CATALOG = { "phi-4-mini": {"capabilities": ["general", "summarize"], "priority": 2}, "deepseek-coder-1.3b": {"capabilities": ["code", "refactor"], "priority": 1}, "qwen2.5-0.5b": {"capabilities": ["classification", "fast"], "priority": 3} } ``` ### Example Test Prompts ```json [ "Refactor this Python function for readability", "Summarize the importance of small language models", "Classify this feedback: The UI is slow but pretty", "Generate a refactored version of a slow Python loop and summarize performance gains." ] ``` ### Trace Extension (Optional) Add per-step trace JSON lines for `models_pipeline.py`: ```python trace.append({ "step": step_idx, "intent": intent, "alias": alias, "latency_ms": round((end-start)*1000,2), "tokens": getattr(usage,'total_tokens',None) }) ``` ### Escalation Heuristic (Idea) If plan contains keywords like "optimize", "security", or step length > 280 chars → escalate to bigger model (e.g., `gpt-oss-20b`) for that step only. ### Optional Enhancements | Area | Enhancement | Value | Hint | |------|-------------|-------|------| | Caching | Reuse manager + client objects | Lower latency, less overhead | Use `workshop_utils.get_client` | | Usage Metrics | Capture tokens & per-step latency | Profiling & optimization | Time each routed call; store in trace list | | Adaptive Routing | Confidence / cost aware | Better quality-cost trade-off | Add scoring: if prompt > N chars or regex matches domain → escalate to larger model | | Dynamic Capability Registry | Hot reload catalog | No restart redeploy | Load `catalog.json` at runtime; watch file timestamp | | Fallback Strategy | Robustness under failures | Higher availability | Try primary → on exception fallback alias | | Streaming Pipeline | Early feedback | UX improvement | Stream each step and buffer final refine input | | Vector Intent Embeddings | More nuanced routing | Higher intent accuracy | Embed prompt, cluster & map centroid → capability | | Trace Export | Auditable chain | Compliance/reporting | Emit JSON lines: step, intent, model, latency_ms, tokens | | Cost Simulation | Pre-cloud estimation | Budget planning | Assign notional cost/token per model & aggregate per task | | Deterministic Mode | Repro reproducibility | Stable benchmarking | Env: `temperature=0`, fixed steps count | #### Trace Structure Example ```python trace.append({ "step": idx, "intent": intent, "alias": alias, "latency_ms": round((end-start)*1000,2), "tokens": getattr(usage,'total_tokens',None) }) ``` #### Adaptive Escalation Sketch ```python if len(prompt) > 280 or 'compliance' in prompt.lower(): # escalate to larger reasoning model if available alias = 'gpt-oss-20b' ``` #### Model Catalog Hot Reload ```python import json, time, os CATALOG_PATH = 'catalog.json' last_mtime = 0 def get_catalog(): global last_mtime, CATALOG m = os.path.getmtime(CATALOG_PATH) if m != last_mtime: CATALOG = json.load(open(CATALOG_PATH)) last_mtime = m return CATALOG ```