[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-launchdarkly-migrate":3,"mdc-epjsqs-key":34,"related-org-launchdarkly-migrate":7719,"related-repo-launchdarkly-migrate":7852},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":20,"repoUrl":21,"updatedAt":22,"license":23,"forks":24,"topics":25,"repo":29,"sourceUrl":32,"mdContent":33},"migrate","migrate prompts to AgentControl","Migrate an application with hardcoded LLM prompts to a full LaunchDarkly AgentControl implementation in five stages: audit the code, wrap the call, move the tools, add tracking, attach evaluators. Use when the user wants to externalize model\u002Fprompt configuration, move from direct provider calls (OpenAI, Anthropic, Bedrock, Gemini, Strands) to a managed config, or stage a full hardcoded-to-LaunchDarkly migration.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"launchdarkly","LaunchDarkly","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Flaunchdarkly.png",[12,16,19],{"name":13,"slug":14,"type":15},"Migration","migration","tag",{"name":17,"slug":18,"type":15},"Agents","agents",{"name":9,"slug":8,"type":15},20,"https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fai-tooling","2026-05-22T06:55:28.162109","Apache-2.0",6,[26,27,28],"agent-skills","launchdarkly-ai","managed-by-terraform",{"repoUrl":21,"stars":20,"forks":24,"topics":30,"description":31},[26,27,28],"LaunchDarkly's official AI tooling","https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fai-tooling\u002Ftree\u002FHEAD\u002Fskills\u002Fagentcontrol\u002Fmigrate","---\nname: migrate\ndescription: \"Migrate an application with hardcoded LLM prompts to a full LaunchDarkly AgentControl implementation in five stages: audit the code, wrap the call, move the tools, add tracking, attach evaluators. Use when the user wants to externalize model\u002Fprompt configuration, move from direct provider calls (OpenAI, Anthropic, Bedrock, Gemini, Strands) to a managed config, or stage a full hardcoded-to-LaunchDarkly migration.\"\nlicense: Apache-2.0\ncompatibility: Requires the remotely hosted LaunchDarkly MCP server\nmetadata:\n  author: launchdarkly\n  version: \"0.1.0\"\n---\n\n# Migrate to AgentControl\n\nYou're using a skill that will guide you through migrating an application from hardcoded LLM prompts to a full LaunchDarkly AgentControl implementation. Your job is to run the migration in **five stages**, stopping at each stage for the user to confirm:\n\n1. **Audit the code** — read-only scan that produces a structured list of everything hardcoded (prompt, model, parameters, tools, app-scoped knobs).\n2. **Wrap the call** — install the SDK, create the config in LaunchDarkly with a fallback that mirrors the hardcoded values, and rewrite the call site to fetch the config fresh on every request.\n3. **Move the tools** — extract each tool's JSON schema, attach it to the config, and swap every call site that references the old tool list.\n4. **Add tracking** — wire the per-request tracker (duration, tokens, success\u002Ferror) around the provider call.\n5. **Attach evaluators** — either offline evals via the Playground + Datasets, or online judges that score sampled traffic automatically.\n\n> **⚠️ Three first-run failure modes to avoid.**\n>\n> 1. **Tracker in the wrong scope.** For an agent with a loop, mint `create_tracker()` once per user turn in a `setup_run` entry node — not inside `call_model`. Per-iteration factory calls produce N `runId`s and trip the at-most-once guards. See [agent-mode-frameworks.md § Custom `StateGraph`](references\u002Fagent-mode-frameworks.md).\n> 2. **`load_chat_model` wrapper reuse.** Templates like `langchain-ai\u002Freact-agent` ship a `load_chat_model(f\"{provider}\u002F{name}\")` helper that wraps `init_chat_model(...)` and silently drops every variation parameter. **Delete it** (don't just avoid using it) and replace call sites with `create_langchain_model(ai_config)`.\n> 3. **Fallthrough not flipped after `\u002Fconfigs-create`.** A freshly-created config's fallthrough points at an auto-generated disabled variation, so the SDK returns `enabled=False` until `\u002Fconfigs-targeting` runs. Flip it before Stage 2 verification.\n\n## Coverage — which shapes are well-trodden vs require extrapolation\n\nThe skill is optimized for Python and Node.js \u002F TypeScript; other languages are install-only. Within Python and Node the coverage tiers are:\n\n| Shape | Python | Node.js | Reference |\n|-------|--------|---------|-----------|\n| One-shot completion (direct OpenAI \u002F Anthropic \u002F Bedrock \u002F Gemini call) | ✅ Worked example | ✅ Worked example | [before-after-examples.md](references\u002Fbefore-after-examples.md), per-provider docs in `built-in-metrics\u002Freferences\u002F` |\n| Chat loop via managed runner (`ManagedModel`) | ✅ Tier 1 pattern | ✅ Tier 1 pattern | [built-in-metrics SKILL.md](..\u002Fbuilt-in-metrics\u002FSKILL.md) |\n| LangChain single-call | ✅ Worked example | ✅ Worked example | [langchain-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Flangchain-tracking.md) |\n| LangGraph prebuilt agent (Python `langchain.agents.create_agent`, Node `createReactAgent`) | ✅ Worked example | ✅ Worked example | [agent-mode-frameworks.md § LangGraph](references\u002Fagent-mode-frameworks.md) |\n| LangGraph custom `StateGraph` with run-scoped tracker (setup_run + call_model + finalize) | ✅ Deep worked example | ⚠️ Mentioned — translate from Python | [agent-mode-frameworks.md § Custom `StateGraph`](references\u002Fagent-mode-frameworks.md) |\n| CrewAI `Agent` | ✅ Worked example | — (not a Node framework) | [agent-mode-frameworks.md § CrewAI](references\u002Fagent-mode-frameworks.md) |\n| Strands `Agent` | ✅ Worked example | ⚠️ BedrockModel + OpenAIModel only (no Anthropic) | [agent-mode-frameworks.md § Strands](references\u002Fagent-mode-frameworks.md) |\n| Custom ReAct loop (hand-rolled, any framework or none) | ✅ Worked example | ⚠️ Apply framework-agnostic invariants; translate from Python | [agent-mode-frameworks.md § Custom ReAct loop](references\u002Fagent-mode-frameworks.md) |\n| Vercel AI SDK (`generateText` \u002F `streamText`) | — (not a Python framework) | ⚠️ Provider package exists; no worked example in skill | `built-in-metrics` provider-package matrix |\n| Streaming (SSE \u002F WebSocket) | ⚠️ Delegated to `built-in-metrics` streaming doc | ⚠️ Same — use `trackStreamMetricsOf` + manual TTFT | [streaming-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fstreaming-tracking.md) |\n| Multi-agent graph (supervisor + workers) | ⚠️ Out of main scope; see reference | ⚠️ Out of main scope; see reference | [agent-graph-reference.md](references\u002Fagent-graph-reference.md) |\n| Non-LangGraph agent frameworks (Pydantic AI, DSPy, AutoGen, Haystack, LlamaIndex agents, Semantic Kernel) | ⚠️ Apply the three invariants; no framework-specific example | ⚠️ Same | [agent-mode-frameworks.md § Framework-agnostic invariants](references\u002Fagent-mode-frameworks.md) |\n| Go, Ruby, .NET | ℹ️ Install commands only | ℹ️ Install commands only | [phase-1-analysis-checklist.md § SDK routing table](references\u002Fphase-1-analysis-checklist.md) |\n\n**Reading the key:** ✅ = follow the skill verbatim; ⚠️ = the architecture applies but you'll translate idioms or cross-reference another skill; ℹ️ = skill doesn't go past the install step.\n\nIf the target app is in the ⚠️ column, start by reading [agent-mode-frameworks.md § Framework-agnostic invariants](references\u002Fagent-mode-frameworks.md) — those three rules (one `agent_config` per turn, one tracker per turn, at-most-once methods fire once at turn end) apply regardless of framework, and every code snippet in this skill is an instantiation of them. Translate the Python example's shape onto the target framework's primitives.\n\n## Prerequisites\n\nThis skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment, and an application that already calls an LLM provider with hardcoded model, prompt, and parameter values.\n\n**Required environment:**\n- `LD_SDK_KEY` — server-side SDK key (starts with `sdk-`) from the target LaunchDarkly project\n\n**MCP tools used directly by this skill:** none — every LaunchDarkly write happens in a focused sibling skill.\n\n**Check the SDK CHANGELOG before applying any pattern.** The API surface described throughout this skill targets the SDK behavior at the time of the skill's last update; SDK releases can rename, remove, or split methods after that. Before you start, fetch the latest CHANGELOG for the SDK(s) you'll target and skim for anything that contradicts the pattern you're about to apply:\n\n- Python: https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fpython-server-sdk-ai\u002Fblob\u002Fmain\u002Fpackages\u002Fsdk\u002Fserver-ai\u002FCHANGELOG.md (and per-provider CHANGELOGs under `packages\u002Fai-providers\u002Fserver-ai-{openai,langchain}\u002FCHANGELOG.md`)\n- Node: https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fjs-core\u002Fblob\u002Fmain\u002Fpackages\u002Fsdk\u002Fserver-ai\u002FCHANGELOG.md (and per-provider CHANGELOGs under `packages\u002Fai-providers\u002Fserver-ai-{openai,langchain,vercel}\u002FCHANGELOG.md`)\n\nIf a CHANGELOG entry post-dates this skill and changes an API you're about to use, the CHANGELOG wins — and the skill should be updated.\n\n**Hand-off model.** This skill does **not** auto-invoke other skills. At each stage that needs a LaunchDarkly write, this skill prepares the inputs (config key, mode, model, prompt, tool schemas, judge keys) and then **tells the user to run the next slash-command themselves**. After the user finishes that sibling skill, return to the next step here. Treat the \"Delegate\" lines below as next-step instructions, not auto-handoffs.\n\n**Sibling skills the user runs at each stage:**\n- `projects` — pre-Stage 2, only if no project exists yet\n- `configs-create` — Stage 2 (creates the config and first variation)\n- `tools` — Stage 3 (creates tool definitions and attaches them)\n- `configs-targeting` — between Stage 2 and Stage 4 (promotes the new variation to fallthrough so the SDK actually serves it)\n- `online-evals` — Stage 5 (attaches judges, creates custom judges)\n\n## Core Principles\n\n1. **Inspect before you mutate.** Every stage begins with a read-only audit. Do not touch code until Step 1 is confirmed by the user.\n2. **Replace config, not business logic.** The SDK call is a drop-in for the place where the model, parameters, and prompt are *defined* — not for the provider call itself. OpenAI\u002FAnthropic\u002FBedrock calls stay where they are.\n3. **Fallback mirrors current behavior.** The fallback passed to `completion_config` \u002F `agent_config` must preserve the hardcoded values you removed, so the app is unchanged if LaunchDarkly is unreachable.\n4. **Stages are ordered.** Wrap before you add tools. Add tools before you track. Track before you add evals. Skipping ahead produces configs without traffic, metrics without context, and judges with nothing to score.\n5. **Hand off to focused skills, manually.** Each stage that needs a LaunchDarkly write tells the user to run a sibling slash-command (`\u002Fconfigs-create`, `\u002Ftools`, `\u002Fconfigs-targeting`, `\u002Fonline-evals`) and waits for them to come back. This skill does **not** auto-invoke other skills.\n\n## Workflow\n\n### Minimum viable migration\n\nStages 1–4 (audit, wrap, tools, tracker) are independently shippable. **A migration that stops after Stage 4 is complete, production-ready, and delivers the core value** — externalized prompts and model config, targeting, variation A\u002FB testing, and Monitoring-tab metrics. Stage 5 (evaluators) is a quality-of-life addition, not a gate. Do not block a Stage-4 rollout on evaluators; ship the run-scoped tracker path, verify metrics flow, then come back for Stage 5 when the team has time to curate a dataset.\n\nThat said, do not *skip* Stage 4. A migration without the tracker gives you externalized prompts but no visibility, which is most of the payoff left on the floor.\n\n### Step 1: Audit the codebase (Stage 1)\n\nThis is the first stage. It is **read-only** — no code writes, no LaunchDarkly resources created. The goal is to scan the repo and produce a structured manifest of every hardcoded value that needs to move, then hand the manifest back to the user for confirmation before any code is touched in Stage 2.\n\nUse [phase-1-analysis-checklist.md](references\u002Fphase-1-analysis-checklist.md) to scan:\n\n1. **Language and package manager** — Python (pip\u002Fpoetry\u002Fuv), TypeScript\u002FJavaScript (npm\u002Fpnpm\u002Fyarn), Go, Ruby, .NET\n2. **LLM provider** — OpenAI, Anthropic, Bedrock, Gemini, LangChain, LangGraph, CrewAI, Strands\n3. **Existing LaunchDarkly usage** — any pre-existing `LDClient` or `ldclient` initialization to reuse\n4. **Hardcoded model configs** — model name string literals, temperature \u002F max_tokens \u002F top_p, system prompts, instruction strings\n5. **Template placeholders in prompts** — `.format()` calls, f-strings in prompt constants, JS\u002FTS template literals, `%(var)s`, hand-rolled `str.replace(\"__VAR__\", ...)`. Flag each placeholder name and its runtime-value source; all get rewritten to Mustache `{{ variable }}` in Stage 2.\n6. **Externalized prompt files** — scan YAML \u002F JSON \u002F TOML \u002F Markdown \u002F `.prompt` \u002F `.j2` files **and** prompt-template registries (`langchain.hub.pull(...)`, LangSmith `client.pull_prompt(...)`) for prompts loaded at runtime. Common shapes: CrewAI `agents.yaml` \u002F `tasks.yaml`, LangChain Promptfiles, k8s ConfigMap overlays, Pydantic Settings classes with `prompt_*` fields. Same Mustache rewrite (sub-step 5 of Stage 2) applies if the placeholder syntax differs. See [phase-1-analysis-checklist.md § 4](references\u002Fphase-1-analysis-checklist.md).\n7. **Hardcoded app-scoped knobs** — search-result limits, retry budgets, tool-timeout overrides, feature toggles, any config-dataclass field that isn't a prompt or model parameter but still governs agent behavior. These belong in `model.custom` on the variation (not `model.parameters`, which is forwarded to the provider SDK and will crash on unknown kwargs).\n8. **Mode decision** — completion mode (chat messages array) or agent mode (single instructions string). Completion mode is the default and the only mode that supports judges attached in the UI.\n\nFor each hardcoded target the audit finds, record:\n\n- File path and line range\n- Current value (model name, full prompt text, parameter dict)\n- Target config field (`model.name`, `model.parameters.temperature`, `messages[].content`, `instructions`)\n- Whether the surrounding call uses function calling \u002F tools (drives Stage 3)\n- Whether the surrounding call has retry logic (affects where Stage 4 tracker calls go)\n\nThis manifest is the contract for the next four stages.\n\n**Stage 1 output** (return to user as a structured summary):\n\n```\nLanguage: Python 3.12\nPackage manager: uv\nLLM provider: OpenAI\nExisting LD SDK: none\nTarget mode: completion\nHardcoded targets:\n  - src\u002Fchat.py:42   model=\"gpt-4o\"\n  - src\u002Fchat.py:43   temperature=0.7, max_tokens=2000\n  - src\u002Fchat.py:45   system=\"You are a helpful assistant...\"\nExternalized prompt files: none (or e.g. \"prompts\u002Fagents.yaml — CrewAI role\u002Fgoal\u002Fbackstory\")\nPrompt-template registries: none (or e.g. langchain.hub.pull(\"rlm\u002Frag-prompt\") at app.py:14)\nCoverage totals: 3 hardcoded code targets · 0 externalized prompt files · 0 registry pulls\nProposed plan: single config key `chat-assistant`, mirror fallback, Stage 3 (tools) skipped (no function calling), Stage 4 (tracking) inline, Stage 5 (evals) attach built-in accuracy judge.\n```\n\n**STOP.** Present this summary, state the coverage totals out loud (e.g. \"I found **N** hardcoded code targets and **M** externalized prompt files — does that match what you expected?\"), and wait for the user to reply with one of four explicit forms:\n\n- **`confirm`** — proceed to Stage 2.\n- **`add: \u003Cfiles or paths>`** — re-run the audit with the new locations and present an updated summary.\n- **`fix: \u003Ccorrection>`** — update a target in the list (provider, mode, prompt content, etc.) and ask again.\n- **`stop`** — pause the migration here.\n\nDo not interpret any other word — including `skip`, `next`, `go`, `ok`, `proceed` — as confirmation; ask the user to pick one of the four forms. **This is the most important checkpoint in the workflow** — if the audit is wrong, every stage after this will be wrong. The user should cross-check the hardcoded-targets list against what they know is in the code before giving the go-ahead.\n\n### Step 2: Wrap the call in the AI SDK (Stage 2)\n\nThis is the first stage that writes code. It has nine sub-steps.\n\n1. **Delete any hand-rolled model \u002F tool wrappers the audit flagged.** Do this *before* installing the new SDK so the replacement lands in a repo without confusing fallback imports. The two shapes the Stage 1 audit should have surfaced:\n   - **`load_chat_model(f\"{provider}\u002F{name}\")` or any `init_chat_model(...)` wrapper.** Ships with `langchain-ai\u002Freact-agent` and many derivative repos. Delete the function and its module; the replacement is `create_langchain_model(ai_config)` (installed in the next sub-step). Leaving the wrapper in place means the next edit in this repo will import the familiar helper and silently drop variation parameters.\n   - **Hand-rolled `resolve_tools` \u002F `TOOL_REGISTRY` \u002F `ALL_TOOLS` helpers that hard-code a static tool list.** Delete them; `ldai_langchain.langchain_helper.build_structured_tools(ai_config, TOOL_REGISTRY_DICT)` is the canonical replacement and gets wired in Stage 3. If you leave the hand-rolled version, both shapes will live side-by-side and the next contributor will pick the familiar one.\n\n   Commit the deletion separately from the SDK install if the repo's review process benefits from it — otherwise bundle with sub-step 2.\n\n2. **Install the AI SDK.** Detect the package manager from Step 1, then install:\n   - Python: `launchdarkly-server-sdk` + `launchdarkly-server-sdk-ai>=0.20.0`\n   - Node.js\u002FTypeScript: `@launchdarkly\u002Fnode-server-sdk` + `@launchdarkly\u002Fserver-sdk-ai@^0.20.0`\n   - Go: `github.com\u002Flaunchdarkly\u002Fgo-server-sdk\u002Fv7` + `github.com\u002Flaunchdarkly\u002Fgo-server-sdk\u002Fldai`\n\n   Tier-2 provider packages (install in Stage 4, only if you're using the matching provider):\n   - OpenAI: `launchdarkly-server-sdk-ai-openai>=0.4.0` (Python) \u002F `@launchdarkly\u002Fserver-sdk-ai-openai@^0.5.5` (Node)\n   - LangChain \u002F LangGraph: `launchdarkly-server-sdk-ai-langchain>=0.5.0` (Python) \u002F `@launchdarkly\u002Fserver-sdk-ai-langchain@^0.5.5` (Node)\n   - Vercel AI SDK (Node only): `@launchdarkly\u002Fserver-sdk-ai-vercel@^0.5.5`\n   - Anthropic, Gemini, Bedrock — no provider package published; use Tier-3 custom extractor (see `built-in-metrics`)\n\n3. **Initialize `LDAIClient` once at startup.** Reuse any existing `LDClient` — do not create a second base client. Place the initialization in the same module that owns existing app config.\n\n   **Python:**\n   ```python\n   import os\n   import ldclient\n   from ldclient.config import Config\n   from ldai.client import LDAIClient\n\n   # Order matters: ldclient.get() raises if called before ldclient.set_config().\n   # The set_config call is what initializes the singleton; .get() just returns it.\n   sdk_key = os.environ.get(\"LD_SDK_KEY\")\n   if sdk_key:\n       ldclient.set_config(Config(sdk_key))\n   else:\n       # Missing key: init in offline mode so the app still starts and the fallback\n       # path runs on every call. Never raise at import time for a missing env var —\n       # that turns a config gap into a boot failure.\n       import logging\n       logging.getLogger(__name__).warning(\n           \"LD_SDK_KEY not set; configs will use fallback values only.\"\n       )\n       ldclient.set_config(Config(\"\", offline=True))\n\n   ai_client = LDAIClient(ldclient.get())\n   ```\n\n   **Node.js\u002FTypeScript:**\n   ```typescript\n   import { init } from '@launchdarkly\u002Fnode-server-sdk';\n   import { initAi } from '@launchdarkly\u002Fserver-sdk-ai';\n\n   \u002F\u002F The Node SDK does not have an explicit offline mode — a missing or invalid\n   \u002F\u002F key fails fast during waitForInitialization, and every agent_config \u002F\n   \u002F\u002F completion_config call returns the fallback. Log a warning; do not throw.\n   if (!process.env.LD_SDK_KEY) {\n     console.warn('LD_SDK_KEY not set; configs will use fallback values only.');\n   }\n   const ldClient = init(process.env.LD_SDK_KEY ?? 'sdk-offline');\n   await ldClient.waitForInitialization({ timeout: 10 }).catch(() => {\n     \u002F\u002F Swallow init failures in offline mode; fallback path runs.\n   });\n   const aiClient = initAi(ldClient);\n   ```\n\n4. **Hand off to `configs-create`.** Print the extracted model, prompt\u002Finstructions, parameters, and mode from the Stage 1 manifest, then tell the user: *\"Run `\u002Fconfigs-create` with these inputs, then come back here.\"* Supply the config key you want the code to call (e.g. `chat-assistant`). Do not attempt to auto-invoke the sibling skill — wait for the user to finish it before continuing.\n\n   **After `configs-create` finishes, the user must also run `\u002Fconfigs-targeting` to promote the new variation to fallthrough.** A freshly created variation returns `enabled=False` to every consumer until targeting is updated. Skip this and Stage 2 verification (sub-step 9 below) will silently take the fallback path on every request.\n\n5. **Rewrite template placeholders to Mustache syntax.** If the hardcoded prompt interpolates runtime values with Python `.format()`, f-strings, JS template literals, or any other non-Mustache syntax (e.g. `{system_time}`, `${userName}`, `%(topic)s`), rewrite every placeholder to `{{ variable }}` Mustache form. Do this in **both** the file you're about to send to `\u002Fconfigs-create` *and* the fallback string you'll write in sub-step 6. The AI SDK interpolates variables through a Mustache renderer on the LD-served path *and* the fallback path using the fourth-argument `variables` dict to `completion_config(...)` \u002F `completionConfig(...)`. Leaving a Python-style `{system_time}` literal in the fallback ships a silent regression when LaunchDarkly is unreachable — the renderer won't match the single-brace form and the literal `{system_time}` goes to the provider as part of the prompt.\n\n   **Before:**\n   ```python\n   SYSTEM_PROMPT = \"You are a helpful assistant. The time is {system_time}.\"\n   prompt = SYSTEM_PROMPT.format(system_time=datetime.now().isoformat())\n   ```\n\n   **After (in source):**\n   ```python\n   SYSTEM_PROMPT = \"You are a helpful assistant. The time is {{ system_time }}.\"\n   # .format() is removed at the call site — the SDK interpolates via `variables`\n   config = ai_client.completion_config(\n       CONFIG_KEY,\n       context,\n       fallback,\n       variables={\"system_time\": datetime.now().isoformat()},\n   )\n   ```\n\n   Common shapes to rewrite:\n   - Python `\"{var}\"` \u002F `\"{var!s}\"` \u002F `\"%(var)s\"` → `\"{{ var }}\"`\n   - JS\u002FTS `` `${var}` `` template literals inside prompt strings → `\"{{ var }}\"`\n   - Any hand-rolled `str.replace(\"__VAR__\", value)` scheme → `\"{{ var }}\"`\n\n   See [fallback-defaults-pattern.md § Template placeholders](references\u002Ffallback-defaults-pattern.md) for the fallback-specific variant.\n\n6. **Build the fallback.** Mirror the hardcoded values you extracted. Use `AICompletionConfigDefault` \u002F `AIAgentConfigDefault` in Python, plain object literals in Node. See [fallback-defaults-pattern.md](references\u002Ffallback-defaults-pattern.md) for inline, file-backed, and bootstrap-generated patterns.\n\n   **Python fallback (completion mode):**\n   ```python\n   from ldai.client import AICompletionConfigDefault, ModelConfig, ProviderConfig, LDMessage\n\n   fallback = AICompletionConfigDefault(\n       enabled=True,\n       model=ModelConfig(name=\"gpt-4o\", parameters={\"temperature\": 0.7, \"max_tokens\": 2000}),\n       provider=ProviderConfig(name=\"openai\"),\n       messages=[LDMessage(role=\"system\", content=\"You are a helpful assistant...\")],\n   )\n   ```\n\n7. **Replace the hardcoded call site.** Swap the hardcoded model\u002Fprompt\u002Fparams for a `completion_config` \u002F `completionConfig` (or `agent_config` \u002F `agentConfig`) call, then read the returned fields into the existing provider call. Keep the provider call intact.\n\n   **Python — before:**\n   ```python\n   response = openai_client.chat.completions.create(\n       model=\"gpt-4o\",\n       temperature=0.7,\n       max_tokens=2000,\n       messages=[\n           {\"role\": \"system\", \"content\": \"You are a helpful assistant...\"},\n           {\"role\": \"user\", \"content\": user_input},\n       ],\n   )\n   ```\n\n   **Python — after:**\n   ```python\n   context = Context.builder(user_id).set(\"email\", user.email).build()\n   config = ai_client.completion_config(\"chat-assistant\", context, fallback)\n\n   if not config.enabled:\n       return disabled_response()\n\n   params = config.model.parameters or {}\n   response = openai_client.chat.completions.create(\n       model=config.model.name,\n       temperature=params.get(\"temperature\"),\n       max_tokens=params.get(\"max_tokens\"),\n       messages=[m.to_dict() for m in (config.messages or [])] + [\n           {\"role\": \"user\", \"content\": user_input},\n       ],\n   )\n   ```\n\n   **Python — after (agent mode)** — for LangGraph, CrewAI, or any framework that takes a goal\u002Finstructions string:\n\n   ```python\n   context = Context.builder(user_id).kind(\"user\").build()\n   config = ai_client.agent_config(\"support-agent\", context, FALLBACK)\n\n   if not config.enabled:\n       return disabled_response()\n\n   # config is a single AIAgentConfig object — NOT a (config, tracker) tuple.\n   # Obtain the tracker once per execution via the factory: tracker = config.create_tracker()\n   model_name = f\"{config.provider.name}\u002F{config.model.name}\"\n   instructions = config.instructions\n   params = config.model.parameters or {}\n\n   # Pass model_name + instructions into your framework's agent constructor.\n   # Example: LangGraph prebuilt agent (Python — `from langchain.agents import create_agent`;\n   # this replaces `langgraph.prebuilt.create_react_agent`, deprecated in LangGraph 1.0\n   # and removed in 2.0. Same return shape; `prompt=` was renamed to `system_prompt=`.)\n   # agent = create_agent(\n   #     create_langchain_model(config),  # forwards every variation parameter\n   #     TOOLS,                            # Stage 3 will replace this with a config.tools loader\n   #     system_prompt=instructions,\n   # )\n   ```\n\n   See [before-after-examples.md](references\u002Fbefore-after-examples.md) for full Python OpenAI, Node Anthropic, and LangGraph agent-mode paired snippets.\n\n8. **Check `config.enabled`.** If it returns `False`, handle the disabled path without crashing and without calling the provider. The check is required — not optional.\n\n9. **Verify.** Run the app with a valid `LD_SDK_KEY`; confirm the call succeeds and the response matches pre-migration output. Then temporarily set `LD_SDK_KEY=sdk-invalid` (or unset it) and confirm the fallback path runs without error. Both paths must work before moving to Stage 3.\n\nDelegate: **`configs-create`** (sub-step 4).\n\n### Step 3: Move tools into the config (Stage 3)\n\nSkip this step if the audited app has no function calling \u002F tools. Otherwise:\n\n1. **Enumerate the tools currently registered.** Common shapes to look for:\n\n   - `openai.chat.completions.create(tools=[...])` — OpenAI direct\n   - `anthropic.messages.create(tools=[...])` — Anthropic direct\n   - `create_agent(llm, tools=[...], system_prompt=...)` — LangGraph prebuilt (Python, `langchain.agents`; replaces deprecated `langgraph.prebuilt.create_react_agent`)\n   - `createReactAgent({ llm, tools: [...] })` — LangGraph.js prebuilt (Node, `@langchain\u002Flanggraph\u002Fprebuilt`)\n   - `Agent(tools=[...])` — CrewAI\n   - `Agent(tools=[...])` — Strands (Python `@tool`-decorated callables passed through the constructor; TS SDK uses Zod-schema tools)\n   - **Custom `StateGraph`** — module-level `TOOLS = [...]` list referenced in **both** `model.bind_tools(TOOLS)` and `ToolNode(TOOLS)`. This is the `langchain-ai\u002Freact-agent` template shape; the list is usually in a `tools.py` module. Grep for `bind_tools(` and `ToolNode(` together — they will point at the same list.\n\n   Record each tool's name, description, and JSON schema.\n\n   For LangChain\u002FLangGraph tools defined with `@tool`, extract the schema via `tool.args_schema.model_json_schema()` (or the equivalent Pydantic `model_json_schema()` call). For plain async callables used as tools (common in custom StateGraph shapes), LangChain infers the schema from the function signature at bind time — extract it via `StructuredTool.from_function(fn).args_schema.model_json_schema()`. Do not hand-write the schema.\n\n2. **Hand off to `tools`.** Print the extracted tool names, descriptions, and schemas, then tell the user: *\"Run `\u002Ftools` with these tools and the variation key, then come back here.\"* The sibling skill creates tool definitions (`create-ai-tool`) and attaches them to the variation (`update-ai-config-variation`). Wait for the user to finish before proceeding to sub-step 3. Do not auto-invoke.\n\n3. **Replace the hardcoded tools array at the call site** with a read from `config.tools` (or the SDK equivalent for your language). Load the actual implementation functions dynamically from the tool names — see [agent-mode-frameworks.md](references\u002Fagent-mode-frameworks.md) for the dynamic-tool-factory pattern from the devrel agents tutorial.\n\n   **For custom `StateGraph` shapes**, you must update **both** call sites: `.bind_tools(TOOLS)` and `ToolNode(TOOLS)` must both read from the same `config.tools`-derived list. Forgetting one leaves the LLM seeing the new tools but the executor still running the old ones, or vice versa.\n\n4. **Verify.** Run the app; confirm the tool flows still execute correctly. `get-ai-config` (via the delegate) confirms the tools are attached server-side.\n\nDelegate: **`tools`** (sub-step 2).\n\n### Step 4: Instrument the tracker (Stage 4)\n\nDelegate: **`built-in-metrics`** wires the per-request `tracker.track_*` calls (duration, tokens, success\u002Ferror, feedback) around the provider call. Use **`custom-metrics`** alongside it if the app needs business metrics beyond the built-in agent ones. Note: do not confuse this with `launchdarkly-metric-instrument`, which is for `ldClient.track()` feature metrics — a different API. See [sdk-ai-tracker-patterns.md](references\u002Fsdk-ai-tracker-patterns.md) for the full per-method Python + Node matrix that the delegate skill draws on.\n\nHand off: print the config key, variation key, provider, and whether the call is streaming, then tell the user: *\"Run `\u002Fbuilt-in-metrics` with these inputs, then come back here.\"* Do not auto-invoke. Return here for sub-step 5 (verify) once they're done.\n\n1. **Create the tracker.** Obtain a per-execution tracker via the factory on the config returned in Stage 2: `tracker = config.create_tracker()` (Python) or `const tracker = aiConfig.createTracker();` (Node). Call the factory **once per user turn** and reuse the returned `tracker` for every tracking call in that turn — each call mints a fresh `runId` that tags every event emitted from the turn so they can be correlated via exported events or downstream queries. (The Monitoring tab aggregates today; run-level grouping is a downstream concern — but the `runId` is also what the SDK's at-most-once guards are keyed on, so minting a new one mid-turn breaks the guard semantics regardless of where the events end up.)\n\n   **Where to call the factory depends on the call shape:**\n\n   - **Completion mode \u002F one-shot provider call:** mint the tracker right after `completion_config(...)` returns, in the same function that handles the request.\n   - **Agent mode with a ReAct loop (LangGraph, LangChain, custom):** mint the tracker in a dedicated `setup_run` entry node that executes **once** before the loop, stash it on graph state, and read it from state in `call_model` \u002F tool handlers \u002F a terminal `finalize` node. Emitting `track_duration` \u002F `track_tokens` \u002F `track_success` inside the loop body will trip the at-most-once guards. See [agent-mode-frameworks.md § Custom `StateGraph` (run-scoped architecture)](references\u002Fagent-mode-frameworks.md) for the full `setup_run` + `call_model` + `finalize` pattern.\n   - **Managed runner (Tier 1):** skip this step entirely. `ManagedModel` mints the tracker internally per `run()` \u002F `invoke()`. Move to sub-step 4 if that's what the app uses.\n\n2. **Pick a tier from the four-tier ladder.** See [sdk-ai-tracker-patterns.md § Tier decision table](references\u002Fsdk-ai-tracker-patterns.md) for the full table (chat loop → Tier 1; provider-package call → Tier 2; custom extractor → Tier 3; streaming\u002Fmanual → Tier 4).\n\n3. **Wire the chosen tier.** The delegate skill has full Python + Node examples for each tier plus per-provider files. A condensed Tier 2\u002F3 example for reference — OpenAI via the provider package:\n\n   **Python:**\n   ```python\n   from ldai_openai import get_ai_metrics_from_response\n   import openai\n\n   client = openai.OpenAI()\n\n   tracker = config.create_tracker()\n\n   def call_openai():\n       return client.chat.completions.create(\n           model=config.model.name,\n           messages=[{\"role\": \"system\", \"content\": config.messages[0].content},\n                     {\"role\": \"user\", \"content\": user_prompt}],\n       )\n\n   # Exceptions are tracked automatically — track_metrics_of catches\n   # exceptions, records tracker.track_error(), and re-raises. Wrap your\n   # own try\u002Fexcept only for local handling (logging, fallback).\n   response = tracker.track_metrics_of(get_ai_metrics_from_response, call_openai)\n   ```\n\n   **Node:**\n   ```typescript\n   import { getAIMetricsFromResponse } from '@launchdarkly\u002Fserver-sdk-ai-openai';\n\n   const tracker = aiConfig.createTracker();\n   \u002F\u002F Exceptions are tracked automatically — trackMetricsOf catches\n   \u002F\u002F exceptions, records tracker.trackError(), and re-throws.\n   const response = await tracker.trackMetricsOf(\n     getAIMetricsFromResponse,\n     () => openaiClient.chat.completions.create({\n       model: aiConfig.model!.name,\n       messages: [...aiConfig.messages, { role: 'user', content: userPrompt }],\n     }),\n   );\n   ```\n\n   For Anthropic direct, Bedrock (no provider package), Gemini, and custom HTTP, write a small extractor returning `LDAIMetrics` — see the delegate skill's [anthropic-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fanthropic-tracking.md), [bedrock-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fbedrock-tracking.md), and [gemini-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fgemini-tracking.md). LangChain single-node and LangGraph go through the `launchdarkly-server-sdk-ai-langchain` \u002F `@launchdarkly\u002Fserver-sdk-ai-langchain` provider package. Build the model with `create_langchain_model(config)` (Python) \u002F `createLangChainModel(config)` (Node) — both forward all variation parameters — and track with `get_ai_metrics_from_response` \u002F `getAIMetricsFromResponse`. See [langchain-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Flangchain-tracking.md).\n\n4. **Wire feedback tracking if the app has thumbs-up\u002Fdown UI.** Both SDKs expose `trackFeedback` with a `{kind}` argument.\n\n   **Python:**\n   ```python\n   from ldai.tracker import FeedbackKind\n   tracker.track_feedback({\"kind\": FeedbackKind.Positive})\n   ```\n\n   **Node:**\n   ```typescript\n   import { LDFeedbackKind } from '@launchdarkly\u002Fserver-sdk-ai';\n   tracker.trackFeedback({ kind: LDFeedbackKind.Positive });\n   ```\n\n   **Deferred feedback across processes.** If the thumbs-up UI fires in a different process than the one that produced the response, do **not** call `create_tracker()` again in the consumer — that mints a new `runId`. Persist the tracker's resumption token (`tracker.resumption_token` in Python, `tracker.resumptionToken` in Node) alongside the message, then rehydrate the tracker with `LDAIConfigTracker.from_resumption_token(...)` (Python) or `aiClient.createTracker(token, context)` (Node) in the feedback handler.\n\n5. **Verify.** Hit the wrapped endpoint in staging, then open the config in LaunchDarkly → Monitoring tab. Duration, token, and generation counts should appear within 1–2 minutes. If nothing shows up, walk the checklist in [sdk-ai-tracker-patterns.md](references\u002Fsdk-ai-tracker-patterns.md) under \"Troubleshooting.\"\n\n### Step 5: Attach evaluations (Stage 5)\n\n1. **Decide between three evaluation paths.** This is the most commonly misunderstood stage — there are **three** paths, not two, and the right default for a migration context is often the one people skip.\n\n   | Path | When to use | Supports agent mode? |\n   |------|-------------|---------------------|\n   | **Offline eval** (recommended default for migration) | Pre-ship regression: run a fixed dataset through the new variation in the LD Playground and score against baseline. Best fit for migration because you want to prove the new config behaves at least as well as the hardcoded version before shipping. | Yes — all modes |\n   | **UI-attached auto judges** | Attach one or more judges to a variation in the LD UI; judges run on sampled live requests automatically. Zero code changes. | Completion mode only (the UI widget is completion-only today) |\n   | **Programmatic direct-judge** | Call `ai_client.create_judge(...)` inside the request handler and `judge.evaluate(input, output)` on each call. Adds per-request cost and code complexity. Best for continuous live scoring of workflows where sampled auto-judges aren't enough. | Yes — all modes (the SDK handles both identically) |\n\n   **Most migration users should start with offline eval**, then add programmatic direct-judge only if they need continuous live scoring after the rollout is stable.\n\n2. **For agent-mode migrations, default to offline eval.** UI-attached auto judges are completion-mode only today. The documented path for agent mode is either (a) **offline regression** via the LD Playground + Datasets (works for all modes), or (b) **programmatic direct-judge** wired into the call site. Generate a starter dataset CSV from the audit manifest (one representative input per row) and point the user at the [Offline Evals guide](https:\u002F\u002Fdocs.launchdarkly.com\u002Fguides\u002Fai-configs\u002Foffline-evaluations) for the Playground walkthrough. Only wire programmatic direct-judge into production code if the user explicitly asks for continuous live scoring.\n\n   **Recommended offline-eval shape for a migration:**\n   - Run the `default` variation (or whichever variation mirrors the pre-migration hardcoded behavior) against the dataset first — this is the baseline.\n   - Clone it into a second variation pointing at a **different model family** (e.g., if the baseline is `anthropic\u002Fclaude-sonnet-4-5`, clone to `openai\u002Fgpt-4o` or `openai\u002Fgpt-4o-mini`). The comparison is most informative across families, not across siblings.\n   - Attach the built-in **Accuracy** judge with a pass threshold of **0.85**, and run both variations against the same dataset.\n   - Promote the winner to fallthrough via `\u002Fconfigs-targeting` only if it beats the baseline on Accuracy and does not regress on Relevance or Toxicity.\n\n   Write this shape into the project's `datasets\u002FREADME.md` (or equivalent) so the comparison pattern is reproducible after the migration ships.\n\n3. **Hand off to `online-evals`** — only for UI-attached judges (completion mode) or to create custom judge configs that will be referenced by the programmatic path. Tell the user: *\"Run `\u002Fonline-evals` with these inputs, then come back here.\"* Do not auto-invoke. Pass:\n   - The parent config key and variation key\n   - A list of built-in judges (Accuracy, Relevance, Toxicity) or custom judge keys to create\u002Fattach\n   - Target environment\n\n   The delegate handles creating custom judge configs, attaching them via the variation PATCH endpoint, and setting fallthrough on each judge config. Offline eval does **not** go through this delegate — it's a Playground workflow, not an API write.\n\n4. **For programmatic direct-judge: wire `create_judge` + `evaluate` + `track_judge_result`.** This is the only path at Stage 5 that writes code. The Python shape:\n\n   ```python\n   from ldai.client import AIJudgeConfigDefault\n\n   judge = ai_client.create_judge(\n       judge_key,                               # judge config key in LD\n       ld_context,\n       AIJudgeConfigDefault(enabled=False),     # fallback: skip eval on SDK miss\n   )\n\n   if judge and judge.enabled:\n       result = await judge.evaluate(\n           input_text,\n           output_text,\n           sampling_rate=0.25,                  # optional; default 1.0 (always eval)\n       )\n       if result.sampled:\n           tracker.track_judge_result(result)\n   ```\n\n   Four rules:\n   - **`create_judge` returns `Optional[Judge]`.** Always guard with `if judge and judge.enabled:` — it returns `None` if the judge config is disabled for the context or the provider is missing. A direct `.evaluate()` on a `None` return will raise `AttributeError`.\n   - **Pass `AIJudgeConfigDefault`**, not `AICompletionConfigDefault`. The `create_judge` `default` parameter is typed `Optional[AIJudgeConfigDefault]`; passing the completion type will not type-check and is a doc-level bug in some older examples.\n   - **`sampling_rate` is a parameter on `evaluate()`**, not on `create_judge`. It defaults to `1.0` (evaluate every call). For live paths, pass something lower (0.1–0.25) to control cost.\n   - **`evaluate()` returns a `JudgeResult`** (never `None`). Check `result.sampled` to know whether the evaluation actually ran, and call `track_judge_result(result)`. Node uses `trackJudgeResult(result)` and `LDJudgeResult` with the same `sampled` field.\n\n   **Ask the user which judge config key to use.** LaunchDarkly ships three built-in judges — Accuracy, Relevance, Toxicity — but the actual config **keys** for the built-ins are not canonical SDK constants and aren't documented. Have the user open **AgentControl > Library** in the LD UI and copy the key of the judge they want to reference, or create a custom judge config via `configs-create` first.\n\n5. **Verify.**\n   - **UI-attached auto judges:** trigger a request in staging, open the Monitoring tab → \"Evaluator metrics\" dropdown. Scores appear within 1–2 minutes at the configured sampling rate.\n   - **Programmatic direct-judge:** hit the wrapped endpoint and confirm `track_judge_result` lands on the parent config's Monitoring tab.\n   - **Offline eval:** run the dataset through the LD Playground, compare baseline vs new-variation scores side by side. No runtime wiring required.\n\nDelegate: **`online-evals`** (sub-step 3, optional — only for UI-attached judges or custom-judge creation; offline eval doesn't delegate).\n\n## Edge Cases\n\n| Situation | Action |\n|-----------|--------|\n| App already initializes `LDClient` for feature flags | Reuse it — pass the existing client to `LDAIClient()` \u002F `initAi()`, do not create a second client |\n| App uses LangChain `ChatOpenAI(model=...)` | Replace the hand-rolled model construction with `create_langchain_model(config)` (Python) or `createLangChainModel(config)` (Node). Do not read `config.model.name` and pass it to `ChatOpenAI(model=...)` by hand — that pattern drops every variation parameter except the ones you explicitly name |\n| Retry wrapper around the provider call | The tracker is minted once at the top of the user turn; the retry loop is inside that scope. Every retry attempt shares the same `runId`. Tracker calls (`track_duration` \u002F `track_tokens` \u002F `track_success` \u002F `track_error`) live *outside* the retry body — one call at the end of the turn, on the success path or the final-failure path |\n| App has no tools — Stage 3 skipped | Move directly from Stage 2 verification to Stage 4 (tracking) |\n| Mode mismatch: user said agent, audit shows one-shot chat | Choose completion mode unless the app uses a LangGraph prebuilt agent (`langchain.agents.create_agent` in Python or `createReactAgent` in Node), CrewAI `Agent`, Strands `Agent`, or a similar goal-driven framework |\n| App uses Strands Agents (Python) | Agent mode. Build a `create_strands_model` dispatcher keyed on `agent_config.provider.name` that returns `AnthropicModel(model_id=..., max_tokens=...)` or `OpenAIModel(model_id=..., params=...)`. Drop `parameters.tools` before passing params to the model class — Strands receives tools via `Agent(tools=[...])`. Tracking is Tier 3: wrap `invoke_async` with `tracker.track_duration_of(...)` and record tokens from `result.metrics.accumulated_usage`. See [agent-mode-frameworks.md § Strands Agent](references\u002Fagent-mode-frameworks.md) and [strands-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fstrands-tracking.md) |\n| Strands app on TypeScript | TS SDK ships `BedrockModel` and `OpenAIModel` only — cannot serve Anthropic-backed variations. Use the Python SDK if multi-provider variations are required |\n| TypeScript app using Anthropic SDK | No `trackAnthropicMetrics` helper exists. Use Tier 3: `trackMetricsOf` with a small custom extractor that reads `response.usage.input_tokens` \u002F `response.usage.output_tokens` and returns `LDAIMetrics`. See [anthropic-tracking.md](..\u002Fbuilt-in-metrics\u002Freferences\u002Fanthropic-tracking.md) in the `built-in-metrics` skill for the exact extractor |\n| Fallback would silently crash because `LD_SDK_KEY` is missing | Log a startup warning; proceed with the fallback. Never raise at import time |\n| Multi-agent graph (supervisor + workers) | Stop after migrating a single agent. Agent Graph Definitions are available in **both** SDKs — Python via `launchdarkly-server-sdk-ai.agent_graph` and Node via the graph API in `@launchdarkly\u002Fserver-sdk-ai`. Read [agent-graph-reference.md](references\u002Fagent-graph-reference.md) for the graph-level migration path — it is deliberately out of this skill's main scope |\n| Single-agent (ReAct, tool loop) + agent mode | Default to offline eval via the LD Playground + Datasets for Stage 5. UI-attached judges are completion-only today, and programmatic direct-judge adds per-call cost that is usually not worth it until after the migration is live and stable. Point at the [Offline Evals guide](https:\u002F\u002Fdocs.launchdarkly.com\u002Fguides\u002Fai-configs\u002Foffline-evaluations) |\n| Tool with a Pydantic `args_schema` (LangChain `@tool`) | Extract the schema via `tool.args_schema.model_json_schema()`; do not hand-write the JSON schema for the delegate |\n| Custom `StateGraph` with module-level `TOOLS` list bound via `.bind_tools(TOOLS)` and run through `ToolNode(TOOLS)` (e.g. the `langchain-ai\u002Freact-agent` template) | Find the `TOOLS` list (usually in a separate `tools.py` module). Extract schemas the same way. Swap **both** call sites — `.bind_tools(...)` and `ToolNode(...)` — to read from the same `config.tools`-derived list |\n| App has already externalized config into a `Context` dataclass with env-var fallback (e.g. `react-agent` template's `context.py`) | Replace the consumers of `runtime.context.model` \u002F `runtime.context.system_prompt` with `ai_client.agent_config(...)` and read from the returned `AIAgentConfig`. **Empty the dataclass** rather than keeping it as the fallback shape — the canonical fallback is `FALLBACK = AIAgentConfigDefault(...)` in Python (a top-level constant near the `agent_config` call), not a parallel Python dataclass. Two sources of truth for fallback values drift. An empty `Context` is a placeholder satisfying LangGraph's `context_schema` requirement only; `thread_id` and any other per-request plumbing comes through `config: RunnableConfig` instead (see [agent-mode-frameworks.md § Custom `StateGraph`](references\u002Fagent-mode-frameworks.md)) |\n\n## What NOT to Do\n\nThese are ordered by how likely they are to show up as a first-run failure. The first three rules — about tracker and config lifetime — account for most of the \"migration looks done but the Monitoring tab is fragmented \u002F wrong\" reports.\n\n### Tracker and config lifetime (most common failure mode)\n\n- **Don't call `create_tracker()` \u002F `createTracker()` more than once per user turn.** One turn = the full request\u002Fresponse cycle including every ReAct iteration, tool call, and retry. See Stage 4 Step 1 for the canonical placement in each app shape (completion \u002F agent loop \u002F managed runner).\n- **Don't call `track_duration` \u002F `track_tokens` \u002F `track_success` \u002F `track_error` \u002F `track_time_to_first_token` inside a loop body.** These are at-most-once per tracker; second calls are dropped. Accumulate inside the loop, emit once in a terminal\u002Ffinalize node. Per-event methods (`track_tool_call`, `track_tool_calls`, `track_feedback`, `track_judge_result`) are safe to call repeatedly. Full matrix: [sdk-ai-tracker-patterns.md § At-most-once guards](references\u002Fsdk-ai-tracker-patterns.md).\n- **Don't call `agent_config()` \u002F `completion_config()` more than once per user turn.** Each call is a flag evaluation and emits a `$ld:ai:agent:config` event. Re-fetching inside a loop step or a tool body inflates agent-config counts on the Monitoring tab and lets a mid-turn targeting change swap the variation between LLM calls in a single turn. Resolve once at the top, stash on state, and have every subsequent consumer read from state. Tools that need variation-scoped knobs should use the tool-factory pattern (`make_search(ai_config)` that closes over the knob at setup time) — see [agent-mode-frameworks.md § Getting knobs into tools](references\u002Fagent-mode-frameworks.md).\n- Don't cache the config object *across* requests — resolve once per turn, yes, but still resolve once per turn. Caching at module scope defeats the targeting-change mechanism entirely.\n- Don't delete the fallback once LaunchDarkly is wired up. It is required for the `enabled=False` and SDK-unreachable paths.\n- Don't tuple-unpack the return of `completion_config` \u002F `agent_config` \u002F `completionConfig` \u002F `agentConfig`. They return a **single** config object (e.g. `AIAgentConfig`, `AICompletionConfig`), not `(config, tracker)`. Obtain the tracker by calling `config.create_tracker()` \u002F `aiConfig.createTracker()`. LLMs hallucinate both the tuple shape and a `config.tracker` property — the actual API is a factory.\n\n### LangChain \u002F LangGraph patterns (second most common failure mode)\n\n- **If the repo already contains a `load_chat_model(f\"{provider}\u002F{name}\")` helper, delete it — don't just avoid using it.** This exact shape ships with `langchain-ai\u002Freact-agent` and is copied into dozens of derivative repos; look for `utils.load_chat_model`, `utils.build_model`, or any one-arg `init_chat_model` wrapper that splits a `\"provider\u002Fmodel\"` string. Re-using it is the first-run failure mode: every variation parameter (temperature, max_tokens, top_p, stop sequences) silently drops on the floor because `init_chat_model` only receives the name and provider. `create_langchain_model(ai_config)` is a one-for-one replacement that forwards the whole `model.parameters` dict. Replace every call site, then delete the wrapper file-side so the next reader can't reach for it.\n- **Same rule applies to hand-rolled `resolve_tools` \u002F `TOOL_REGISTRY` \u002F `ALL_TOOLS` helpers.** If the template already has a `resolve_tools(tool_keys)` or an `ALL_TOOLS` module-level list, import `build_structured_tools` from `ldai_langchain.langchain_helper` and delete the hand-rolled version. `build_structured_tools(ai_config, TOOL_REGISTRY_DICT)` reads `ai_config.model.parameters.tools` and wraps the matching callables as LangChain `StructuredTool`s with the LD tool key as the `StructuredTool.name` — so `ToolNode` lookup works without a second mapping. Don't leave both in the repo.\n- Don't put app-scoped knobs directly in `model.parameters`. `create_langchain_model` forwards every key in `parameters` to the provider SDK via `init_chat_model`, so a `max_search_results` \u002F `retry_budget` \u002F `feature_toggle` entry will crash the provider with an unexpected-keyword-argument error. The correct home is `model.custom`, which the provider helpers ignore and the app reads via `ai_config.model.get_custom(\"key\")`. The MCP `update-ai-config-variation` tool does not currently expose top-level `custom`, so pick one of two paths: (a) PATCH the variation via the REST API to set `model.custom` directly, or (b) set it via MCP inside `parameters.custom` (as a nested dict) and use a defensive accessor that reads both locations. Full walk-through with code samples in [langchain-tracking.md § MCP caveat](..\u002Fbuilt-in-metrics\u002Freferences\u002Flangchain-tracking.md).\n- Don't re-encode tool schemas inside the fallback. When LaunchDarkly is unreachable the fallback should run without tools (or with whatever minimal provider-bound parameters the app needs to keep operating). Building a `_FALLBACK_TOOLS` array that duplicates the config's tool schema re-introduces the hardcoded config the migration was supposed to move out of code.\n- Don't import `LaunchDarklyCallbackHandler` from `ldai.langchain` — neither the class nor the dotted module path exists. The Python LangChain helper package is `ldai_langchain` (top-level module, underscore). Use `create_langchain_model(config)` + `track_metrics_of_async(get_ai_metrics_from_response, lambda: llm.ainvoke(messages))` as the canonical pattern.\n\n### Stage \u002F handoff discipline\n\n- Don't skip Step 1 even when the user says \"just wrap it.\" Without the audit, the fallback will drift from the hardcoded behavior.\n- Don't delegate to `configs-create` before extracting the prompt and model — the delegate needs them as inputs.\n- Don't try to attach tools during initial `setup-ai-config`. Tool attachment is a separate step owned by `tools`.\n- Don't claim you \"delegated to `configs-create`\" or any other sibling skill. This skill does not auto-invoke. At each handoff, print the inputs and tell the user to run the sibling slash-command, then wait. Anything else misleads the user about what just happened.\n- Don't skip the `\u002Fconfigs-targeting` step between Stage 2 and Stage 4. A freshly created variation returns `enabled=False` until targeting promotes it to fallthrough — Stage 2 verification will silently take the fallback path on every request.\n- Don't attempt a multi-agent graph migration in one pass. Migrate a single agent first; use [agent-graph-reference.md](references\u002Fagent-graph-reference.md) as the next-step read.\n\n### Stage 5 evaluations\n\n- Don't wire evals before the tracker is in place. Judges score traffic; without Stage 4 traffic, there is nothing to judge.\n- Don't frame Stage 5 as \"either UI or programmatic.\" There are **three** paths: offline eval (recommended default for migration), UI-attached auto judges (completion-mode only), and programmatic direct-judge. Offline eval is the one most people skip and usually the right starting point.\n- Don't pass `sampling_rate` to `create_judge` — it's a parameter on `Judge.evaluate()`, not `create_judge()`.\n- Don't hardcode judge config keys (`\"accuracy-judge\"`, `\"relevance-judge\"`, etc). The built-in keys are not canonical SDK constants; ask the user to look them up in **AgentControl > Library** in the LD UI.\n- Don't forget the `if judge and judge.enabled:` guard after `create_judge`. It returns `Optional[Judge]` and returns `None` when the judge config is disabled for the context.\n\n### API surface gotchas\n\n- Don't use `launchdarkly-metric-instrument` for Stage 4 (tracking). That skill is for `ldClient.track()` feature metrics, not agent `tracker.track_*` calls — they are different APIs.\n- Don't use `track_request()` in Python — it does not exist in `launchdarkly-server-sdk-ai`. Use `track_metrics_of` with a provider-package or custom extractor, or drop to explicit `track_duration` + `track_tokens` + `track_success` \u002F `track_error` if you're on the streaming path.\n- Don't pass `graph_key=...` to `tracker.track_*()` methods in Python — it is not an accepted argument. Trackers obtained inside a graph traversal are automatically configured with the correct graph key.\n\n## Related Skills\n\n- `configs-create` — called by Stage 2 to create the config\n- `tools` — called by Stage 3 to create and attach tool definitions\n- `online-evals` — called by Stage 5 to attach judges\n- `configs-variations` — add variations for A\u002FB testing after migration is complete\n- `configs-targeting` — roll out new variations to users after migration is complete\n- `configs-update` — modify config properties as your app evolves\n- `launchdarkly-metric-instrument` — for `ldClient.track()` feature metrics (NOT for agent tracker calls)\n\n## References\n\n- [phase-1-analysis-checklist.md](references\u002Fphase-1-analysis-checklist.md) — Step 1 audit checklist, grep patterns, SDK routing table, mode decision tree\n- [before-after-examples.md](references\u002Fbefore-after-examples.md) — Paired hardcoded-to-wrapped snippets for Python OpenAI, Node Anthropic, Python LangGraph\n- [sdk-ai-tracker-patterns.md](references\u002Fsdk-ai-tracker-patterns.md) — Every `tracker.track_*` method in Python and Node side by side, auto-helper matrix, and common gotchas\n- [agent-mode-frameworks.md](references\u002Fagent-mode-frameworks.md) — How to wire `agent_config` into LangGraph, CrewAI, and custom react loops; dynamic tool loading pattern\n- [fallback-defaults-pattern.md](references\u002Ffallback-defaults-pattern.md) — Three fallback patterns (inline, file-backed, bootstrap-generated) and when to use each\n- [agent-graph-reference.md](references\u002Fagent-graph-reference.md) — Out-of-scope pointer doc for multi-agent migrations\n",{"data":35,"body":39},{"name":4,"description":6,"license":23,"compatibility":36,"metadata":37},"Requires the remotely hosted LaunchDarkly MCP server",{"author":8,"version":38},"0.1.0",{"type":40,"children":41},"root",[42,51,65,120,280,287,292,749,759,778,784,789,797,820,830,840,883,888,912,920,978,984,1093,1099,1106,1118,1130,1136,1148,1160,1374,1379,1435,1440,1450,1462,1486,1545,1592,1598,1603,3502,3517,3523,3528,3884,3898,3904,3961,3979,5085,5091,5876,5890,5896,6621,6627,6632,6638,6891,6897,7238,7244,7325,7331,7441,7447,7548,7554,7636,7642,7713],{"type":43,"tag":44,"props":45,"children":47},"element","h1",{"id":46},"migrate-to-agentcontrol",[48],{"type":49,"value":50},"text","Migrate to AgentControl",{"type":43,"tag":52,"props":53,"children":54},"p",{},[55,57,63],{"type":49,"value":56},"You're using a skill that will guide you through migrating an application from hardcoded LLM prompts to a full LaunchDarkly AgentControl implementation. Your job is to run the migration in ",{"type":43,"tag":58,"props":59,"children":60},"strong",{},[61],{"type":49,"value":62},"five stages",{"type":49,"value":64},", stopping at each stage for the user to confirm:",{"type":43,"tag":66,"props":67,"children":68},"ol",{},[69,80,90,100,110],{"type":43,"tag":70,"props":71,"children":72},"li",{},[73,78],{"type":43,"tag":58,"props":74,"children":75},{},[76],{"type":49,"value":77},"Audit the code",{"type":49,"value":79}," — read-only scan that produces a structured list of everything hardcoded (prompt, model, parameters, tools, app-scoped knobs).",{"type":43,"tag":70,"props":81,"children":82},{},[83,88],{"type":43,"tag":58,"props":84,"children":85},{},[86],{"type":49,"value":87},"Wrap the call",{"type":49,"value":89}," — install the SDK, create the config in LaunchDarkly with a fallback that mirrors the hardcoded values, and rewrite the call site to fetch the config fresh on every request.",{"type":43,"tag":70,"props":91,"children":92},{},[93,98],{"type":43,"tag":58,"props":94,"children":95},{},[96],{"type":49,"value":97},"Move the tools",{"type":49,"value":99}," — extract each tool's JSON schema, attach it to the config, and swap every call site that references the old tool list.",{"type":43,"tag":70,"props":101,"children":102},{},[103,108],{"type":43,"tag":58,"props":104,"children":105},{},[106],{"type":49,"value":107},"Add tracking",{"type":49,"value":109}," — wire the per-request tracker (duration, tokens, success\u002Ferror) around the provider call.",{"type":43,"tag":70,"props":111,"children":112},{},[113,118],{"type":43,"tag":58,"props":114,"children":115},{},[116],{"type":49,"value":117},"Attach evaluators",{"type":49,"value":119}," — either offline evals via the Playground + Datasets, or online judges that score sampled traffic automatically.",{"type":43,"tag":121,"props":122,"children":123},"blockquote",{},[124,132],{"type":43,"tag":52,"props":125,"children":126},{},[127],{"type":43,"tag":58,"props":128,"children":129},{},[130],{"type":49,"value":131},"⚠️ Three first-run failure modes to avoid.",{"type":43,"tag":66,"props":133,"children":134},{},[135,193,247],{"type":43,"tag":70,"props":136,"children":137},{},[138,143,145,152,154,160,162,168,170,176,178,191],{"type":43,"tag":58,"props":139,"children":140},{},[141],{"type":49,"value":142},"Tracker in the wrong scope.",{"type":49,"value":144}," For an agent with a loop, mint ",{"type":43,"tag":146,"props":147,"children":149},"code",{"className":148},[],[150],{"type":49,"value":151},"create_tracker()",{"type":49,"value":153}," once per user turn in a ",{"type":43,"tag":146,"props":155,"children":157},{"className":156},[],[158],{"type":49,"value":159},"setup_run",{"type":49,"value":161}," entry node — not inside ",{"type":43,"tag":146,"props":163,"children":165},{"className":164},[],[166],{"type":49,"value":167},"call_model",{"type":49,"value":169},". Per-iteration factory calls produce N ",{"type":43,"tag":146,"props":171,"children":173},{"className":172},[],[174],{"type":49,"value":175},"runId",{"type":49,"value":177},"s and trip the at-most-once guards. See ",{"type":43,"tag":179,"props":180,"children":182},"a",{"href":181},"references\u002Fagent-mode-frameworks.md",[183,185],{"type":49,"value":184},"agent-mode-frameworks.md § Custom ",{"type":43,"tag":146,"props":186,"children":188},{"className":187},[],[189],{"type":49,"value":190},"StateGraph",{"type":49,"value":192},".",{"type":43,"tag":70,"props":194,"children":195},{},[196,207,209,215,217,223,225,231,233,238,240,246],{"type":43,"tag":58,"props":197,"children":198},{},[199,205],{"type":43,"tag":146,"props":200,"children":202},{"className":201},[],[203],{"type":49,"value":204},"load_chat_model",{"type":49,"value":206}," wrapper reuse.",{"type":49,"value":208}," Templates like ",{"type":43,"tag":146,"props":210,"children":212},{"className":211},[],[213],{"type":49,"value":214},"langchain-ai\u002Freact-agent",{"type":49,"value":216}," ship a ",{"type":43,"tag":146,"props":218,"children":220},{"className":219},[],[221],{"type":49,"value":222},"load_chat_model(f\"{provider}\u002F{name}\")",{"type":49,"value":224}," helper that wraps ",{"type":43,"tag":146,"props":226,"children":228},{"className":227},[],[229],{"type":49,"value":230},"init_chat_model(...)",{"type":49,"value":232}," and silently drops every variation parameter. ",{"type":43,"tag":58,"props":234,"children":235},{},[236],{"type":49,"value":237},"Delete it",{"type":49,"value":239}," (don't just avoid using it) and replace call sites with ",{"type":43,"tag":146,"props":241,"children":243},{"className":242},[],[244],{"type":49,"value":245},"create_langchain_model(ai_config)",{"type":49,"value":192},{"type":43,"tag":70,"props":248,"children":249},{},[250,262,264,270,272,278],{"type":43,"tag":58,"props":251,"children":252},{},[253,255,261],{"type":49,"value":254},"Fallthrough not flipped after ",{"type":43,"tag":146,"props":256,"children":258},{"className":257},[],[259],{"type":49,"value":260},"\u002Fconfigs-create",{"type":49,"value":192},{"type":49,"value":263}," A freshly-created config's fallthrough points at an auto-generated disabled variation, so the SDK returns ",{"type":43,"tag":146,"props":265,"children":267},{"className":266},[],[268],{"type":49,"value":269},"enabled=False",{"type":49,"value":271}," until ",{"type":43,"tag":146,"props":273,"children":275},{"className":274},[],[276],{"type":49,"value":277},"\u002Fconfigs-targeting",{"type":49,"value":279}," runs. Flip it before Stage 2 verification.",{"type":43,"tag":281,"props":282,"children":284},"h2",{"id":283},"coverage-which-shapes-are-well-trodden-vs-require-extrapolation",[285],{"type":49,"value":286},"Coverage — which shapes are well-trodden vs require extrapolation",{"type":43,"tag":52,"props":288,"children":289},{},[290],{"type":49,"value":291},"The skill is optimized for Python and Node.js \u002F TypeScript; other languages are install-only. Within Python and Node the coverage tiers are:",{"type":43,"tag":293,"props":294,"children":295},"table",{},[296,325],{"type":43,"tag":297,"props":298,"children":299},"thead",{},[300],{"type":43,"tag":301,"props":302,"children":303},"tr",{},[304,310,315,320],{"type":43,"tag":305,"props":306,"children":307},"th",{},[308],{"type":49,"value":309},"Shape",{"type":43,"tag":305,"props":311,"children":312},{},[313],{"type":49,"value":314},"Python",{"type":43,"tag":305,"props":316,"children":317},{},[318],{"type":49,"value":319},"Node.js",{"type":43,"tag":305,"props":321,"children":322},{},[323],{"type":49,"value":324},"Reference",{"type":43,"tag":326,"props":327,"children":328},"tbody",{},[329,364,398,423,462,499,530,560,585,629,671,697,723],{"type":43,"tag":301,"props":330,"children":331},{},[332,338,343,347],{"type":43,"tag":333,"props":334,"children":335},"td",{},[336],{"type":49,"value":337},"One-shot completion (direct OpenAI \u002F Anthropic \u002F Bedrock \u002F Gemini call)",{"type":43,"tag":333,"props":339,"children":340},{},[341],{"type":49,"value":342},"✅ Worked example",{"type":43,"tag":333,"props":344,"children":345},{},[346],{"type":49,"value":342},{"type":43,"tag":333,"props":348,"children":349},{},[350,356,358],{"type":43,"tag":179,"props":351,"children":353},{"href":352},"references\u002Fbefore-after-examples.md",[354],{"type":49,"value":355},"before-after-examples.md",{"type":49,"value":357},", per-provider docs in ",{"type":43,"tag":146,"props":359,"children":361},{"className":360},[],[362],{"type":49,"value":363},"built-in-metrics\u002Freferences\u002F",{"type":43,"tag":301,"props":365,"children":366},{},[367,380,385,389],{"type":43,"tag":333,"props":368,"children":369},{},[370,372,378],{"type":49,"value":371},"Chat loop via managed runner (",{"type":43,"tag":146,"props":373,"children":375},{"className":374},[],[376],{"type":49,"value":377},"ManagedModel",{"type":49,"value":379},")",{"type":43,"tag":333,"props":381,"children":382},{},[383],{"type":49,"value":384},"✅ Tier 1 pattern",{"type":43,"tag":333,"props":386,"children":387},{},[388],{"type":49,"value":384},{"type":43,"tag":333,"props":390,"children":391},{},[392],{"type":43,"tag":179,"props":393,"children":395},{"href":394},"..\u002Fbuilt-in-metrics\u002FSKILL.md",[396],{"type":49,"value":397},"built-in-metrics SKILL.md",{"type":43,"tag":301,"props":399,"children":400},{},[401,406,410,414],{"type":43,"tag":333,"props":402,"children":403},{},[404],{"type":49,"value":405},"LangChain single-call",{"type":43,"tag":333,"props":407,"children":408},{},[409],{"type":49,"value":342},{"type":43,"tag":333,"props":411,"children":412},{},[413],{"type":49,"value":342},{"type":43,"tag":333,"props":415,"children":416},{},[417],{"type":43,"tag":179,"props":418,"children":420},{"href":419},"..\u002Fbuilt-in-metrics\u002Freferences\u002Flangchain-tracking.md",[421],{"type":49,"value":422},"langchain-tracking.md",{"type":43,"tag":301,"props":424,"children":425},{},[426,446,450,454],{"type":43,"tag":333,"props":427,"children":428},{},[429,431,437,439,445],{"type":49,"value":430},"LangGraph prebuilt agent (Python ",{"type":43,"tag":146,"props":432,"children":434},{"className":433},[],[435],{"type":49,"value":436},"langchain.agents.create_agent",{"type":49,"value":438},", Node ",{"type":43,"tag":146,"props":440,"children":442},{"className":441},[],[443],{"type":49,"value":444},"createReactAgent",{"type":49,"value":379},{"type":43,"tag":333,"props":447,"children":448},{},[449],{"type":49,"value":342},{"type":43,"tag":333,"props":451,"children":452},{},[453],{"type":49,"value":342},{"type":43,"tag":333,"props":455,"children":456},{},[457],{"type":43,"tag":179,"props":458,"children":459},{"href":181},[460],{"type":49,"value":461},"agent-mode-frameworks.md § LangGraph",{"type":43,"tag":301,"props":463,"children":464},{},[465,477,482,487],{"type":43,"tag":333,"props":466,"children":467},{},[468,470,475],{"type":49,"value":469},"LangGraph custom ",{"type":43,"tag":146,"props":471,"children":473},{"className":472},[],[474],{"type":49,"value":190},{"type":49,"value":476}," with run-scoped tracker (setup_run + call_model + finalize)",{"type":43,"tag":333,"props":478,"children":479},{},[480],{"type":49,"value":481},"✅ Deep worked example",{"type":43,"tag":333,"props":483,"children":484},{},[485],{"type":49,"value":486},"⚠️ Mentioned — translate from Python",{"type":43,"tag":333,"props":488,"children":489},{},[490],{"type":43,"tag":179,"props":491,"children":492},{"href":181},[493,494],{"type":49,"value":184},{"type":43,"tag":146,"props":495,"children":497},{"className":496},[],[498],{"type":49,"value":190},{"type":43,"tag":301,"props":500,"children":501},{},[502,513,517,522],{"type":43,"tag":333,"props":503,"children":504},{},[505,507],{"type":49,"value":506},"CrewAI ",{"type":43,"tag":146,"props":508,"children":510},{"className":509},[],[511],{"type":49,"value":512},"Agent",{"type":43,"tag":333,"props":514,"children":515},{},[516],{"type":49,"value":342},{"type":43,"tag":333,"props":518,"children":519},{},[520],{"type":49,"value":521},"— (not a Node framework)",{"type":43,"tag":333,"props":523,"children":524},{},[525],{"type":43,"tag":179,"props":526,"children":527},{"href":181},[528],{"type":49,"value":529},"agent-mode-frameworks.md § CrewAI",{"type":43,"tag":301,"props":531,"children":532},{},[533,543,547,552],{"type":43,"tag":333,"props":534,"children":535},{},[536,538],{"type":49,"value":537},"Strands ",{"type":43,"tag":146,"props":539,"children":541},{"className":540},[],[542],{"type":49,"value":512},{"type":43,"tag":333,"props":544,"children":545},{},[546],{"type":49,"value":342},{"type":43,"tag":333,"props":548,"children":549},{},[550],{"type":49,"value":551},"⚠️ BedrockModel + OpenAIModel only (no Anthropic)",{"type":43,"tag":333,"props":553,"children":554},{},[555],{"type":43,"tag":179,"props":556,"children":557},{"href":181},[558],{"type":49,"value":559},"agent-mode-frameworks.md § Strands",{"type":43,"tag":301,"props":561,"children":562},{},[563,568,572,577],{"type":43,"tag":333,"props":564,"children":565},{},[566],{"type":49,"value":567},"Custom ReAct loop (hand-rolled, any framework or none)",{"type":43,"tag":333,"props":569,"children":570},{},[571],{"type":49,"value":342},{"type":43,"tag":333,"props":573,"children":574},{},[575],{"type":49,"value":576},"⚠️ Apply framework-agnostic invariants; translate from Python",{"type":43,"tag":333,"props":578,"children":579},{},[580],{"type":43,"tag":179,"props":581,"children":582},{"href":181},[583],{"type":49,"value":584},"agent-mode-frameworks.md § Custom ReAct loop",{"type":43,"tag":301,"props":586,"children":587},{},[588,608,613,618],{"type":43,"tag":333,"props":589,"children":590},{},[591,593,599,601,607],{"type":49,"value":592},"Vercel AI SDK (",{"type":43,"tag":146,"props":594,"children":596},{"className":595},[],[597],{"type":49,"value":598},"generateText",{"type":49,"value":600}," \u002F ",{"type":43,"tag":146,"props":602,"children":604},{"className":603},[],[605],{"type":49,"value":606},"streamText",{"type":49,"value":379},{"type":43,"tag":333,"props":609,"children":610},{},[611],{"type":49,"value":612},"— (not a Python framework)",{"type":43,"tag":333,"props":614,"children":615},{},[616],{"type":49,"value":617},"⚠️ Provider package exists; no worked example in skill",{"type":43,"tag":333,"props":619,"children":620},{},[621,627],{"type":43,"tag":146,"props":622,"children":624},{"className":623},[],[625],{"type":49,"value":626},"built-in-metrics",{"type":49,"value":628}," provider-package matrix",{"type":43,"tag":301,"props":630,"children":631},{},[632,637,649,662],{"type":43,"tag":333,"props":633,"children":634},{},[635],{"type":49,"value":636},"Streaming (SSE \u002F WebSocket)",{"type":43,"tag":333,"props":638,"children":639},{},[640,642,647],{"type":49,"value":641},"⚠️ Delegated to ",{"type":43,"tag":146,"props":643,"children":645},{"className":644},[],[646],{"type":49,"value":626},{"type":49,"value":648}," streaming doc",{"type":43,"tag":333,"props":650,"children":651},{},[652,654,660],{"type":49,"value":653},"⚠️ Same — use ",{"type":43,"tag":146,"props":655,"children":657},{"className":656},[],[658],{"type":49,"value":659},"trackStreamMetricsOf",{"type":49,"value":661}," + manual TTFT",{"type":43,"tag":333,"props":663,"children":664},{},[665],{"type":43,"tag":179,"props":666,"children":668},{"href":667},"..\u002Fbuilt-in-metrics\u002Freferences\u002Fstreaming-tracking.md",[669],{"type":49,"value":670},"streaming-tracking.md",{"type":43,"tag":301,"props":672,"children":673},{},[674,679,684,688],{"type":43,"tag":333,"props":675,"children":676},{},[677],{"type":49,"value":678},"Multi-agent graph (supervisor + workers)",{"type":43,"tag":333,"props":680,"children":681},{},[682],{"type":49,"value":683},"⚠️ Out of main scope; see reference",{"type":43,"tag":333,"props":685,"children":686},{},[687],{"type":49,"value":683},{"type":43,"tag":333,"props":689,"children":690},{},[691],{"type":43,"tag":179,"props":692,"children":694},{"href":693},"references\u002Fagent-graph-reference.md",[695],{"type":49,"value":696},"agent-graph-reference.md",{"type":43,"tag":301,"props":698,"children":699},{},[700,705,710,715],{"type":43,"tag":333,"props":701,"children":702},{},[703],{"type":49,"value":704},"Non-LangGraph agent frameworks (Pydantic AI, DSPy, AutoGen, Haystack, LlamaIndex agents, Semantic Kernel)",{"type":43,"tag":333,"props":706,"children":707},{},[708],{"type":49,"value":709},"⚠️ Apply the three invariants; no framework-specific example",{"type":43,"tag":333,"props":711,"children":712},{},[713],{"type":49,"value":714},"⚠️ Same",{"type":43,"tag":333,"props":716,"children":717},{},[718],{"type":43,"tag":179,"props":719,"children":720},{"href":181},[721],{"type":49,"value":722},"agent-mode-frameworks.md § Framework-agnostic invariants",{"type":43,"tag":301,"props":724,"children":725},{},[726,731,736,740],{"type":43,"tag":333,"props":727,"children":728},{},[729],{"type":49,"value":730},"Go, Ruby, .NET",{"type":43,"tag":333,"props":732,"children":733},{},[734],{"type":49,"value":735},"ℹ️ Install commands only",{"type":43,"tag":333,"props":737,"children":738},{},[739],{"type":49,"value":735},{"type":43,"tag":333,"props":741,"children":742},{},[743],{"type":43,"tag":179,"props":744,"children":746},{"href":745},"references\u002Fphase-1-analysis-checklist.md",[747],{"type":49,"value":748},"phase-1-analysis-checklist.md § SDK routing table",{"type":43,"tag":52,"props":750,"children":751},{},[752,757],{"type":43,"tag":58,"props":753,"children":754},{},[755],{"type":49,"value":756},"Reading the key:",{"type":49,"value":758}," ✅ = follow the skill verbatim; ⚠️ = the architecture applies but you'll translate idioms or cross-reference another skill; ℹ️ = skill doesn't go past the install step.",{"type":43,"tag":52,"props":760,"children":761},{},[762,764,768,770,776],{"type":49,"value":763},"If the target app is in the ⚠️ column, start by reading ",{"type":43,"tag":179,"props":765,"children":766},{"href":181},[767],{"type":49,"value":722},{"type":49,"value":769}," — those three rules (one ",{"type":43,"tag":146,"props":771,"children":773},{"className":772},[],[774],{"type":49,"value":775},"agent_config",{"type":49,"value":777}," per turn, one tracker per turn, at-most-once methods fire once at turn end) apply regardless of framework, and every code snippet in this skill is an instantiation of them. Translate the Python example's shape onto the target framework's primitives.",{"type":43,"tag":281,"props":779,"children":781},{"id":780},"prerequisites",[782],{"type":49,"value":783},"Prerequisites",{"type":43,"tag":52,"props":785,"children":786},{},[787],{"type":49,"value":788},"This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment, and an application that already calls an LLM provider with hardcoded model, prompt, and parameter values.",{"type":43,"tag":52,"props":790,"children":791},{},[792],{"type":43,"tag":58,"props":793,"children":794},{},[795],{"type":49,"value":796},"Required environment:",{"type":43,"tag":798,"props":799,"children":800},"ul",{},[801],{"type":43,"tag":70,"props":802,"children":803},{},[804,810,812,818],{"type":43,"tag":146,"props":805,"children":807},{"className":806},[],[808],{"type":49,"value":809},"LD_SDK_KEY",{"type":49,"value":811}," — server-side SDK key (starts with ",{"type":43,"tag":146,"props":813,"children":815},{"className":814},[],[816],{"type":49,"value":817},"sdk-",{"type":49,"value":819},") from the target LaunchDarkly project",{"type":43,"tag":52,"props":821,"children":822},{},[823,828],{"type":43,"tag":58,"props":824,"children":825},{},[826],{"type":49,"value":827},"MCP tools used directly by this skill:",{"type":49,"value":829}," none — every LaunchDarkly write happens in a focused sibling skill.",{"type":43,"tag":52,"props":831,"children":832},{},[833,838],{"type":43,"tag":58,"props":834,"children":835},{},[836],{"type":49,"value":837},"Check the SDK CHANGELOG before applying any pattern.",{"type":49,"value":839}," The API surface described throughout this skill targets the SDK behavior at the time of the skill's last update; SDK releases can rename, remove, or split methods after that. Before you start, fetch the latest CHANGELOG for the SDK(s) you'll target and skim for anything that contradicts the pattern you're about to apply:",{"type":43,"tag":798,"props":841,"children":842},{},[843,864],{"type":43,"tag":70,"props":844,"children":845},{},[846,848,855,857,863],{"type":49,"value":847},"Python: ",{"type":43,"tag":179,"props":849,"children":853},{"href":850,"rel":851},"https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fpython-server-sdk-ai\u002Fblob\u002Fmain\u002Fpackages\u002Fsdk\u002Fserver-ai\u002FCHANGELOG.md",[852],"nofollow",[854],{"type":49,"value":850},{"type":49,"value":856}," (and per-provider CHANGELOGs under ",{"type":43,"tag":146,"props":858,"children":860},{"className":859},[],[861],{"type":49,"value":862},"packages\u002Fai-providers\u002Fserver-ai-{openai,langchain}\u002FCHANGELOG.md",{"type":49,"value":379},{"type":43,"tag":70,"props":865,"children":866},{},[867,869,875,876,882],{"type":49,"value":868},"Node: ",{"type":43,"tag":179,"props":870,"children":873},{"href":871,"rel":872},"https:\u002F\u002Fgithub.com\u002Flaunchdarkly\u002Fjs-core\u002Fblob\u002Fmain\u002Fpackages\u002Fsdk\u002Fserver-ai\u002FCHANGELOG.md",[852],[874],{"type":49,"value":871},{"type":49,"value":856},{"type":43,"tag":146,"props":877,"children":879},{"className":878},[],[880],{"type":49,"value":881},"packages\u002Fai-providers\u002Fserver-ai-{openai,langchain,vercel}\u002FCHANGELOG.md",{"type":49,"value":379},{"type":43,"tag":52,"props":884,"children":885},{},[886],{"type":49,"value":887},"If a CHANGELOG entry post-dates this skill and changes an API you're about to use, the CHANGELOG wins — and the skill should be updated.",{"type":43,"tag":52,"props":889,"children":890},{},[891,896,898,903,905,910],{"type":43,"tag":58,"props":892,"children":893},{},[894],{"type":49,"value":895},"Hand-off model.",{"type":49,"value":897}," This skill does ",{"type":43,"tag":58,"props":899,"children":900},{},[901],{"type":49,"value":902},"not",{"type":49,"value":904}," auto-invoke other skills. At each stage that needs a LaunchDarkly write, this skill prepares the inputs (config key, mode, model, prompt, tool schemas, judge keys) and then ",{"type":43,"tag":58,"props":906,"children":907},{},[908],{"type":49,"value":909},"tells the user to run the next slash-command themselves",{"type":49,"value":911},". After the user finishes that sibling skill, return to the next step here. Treat the \"Delegate\" lines below as next-step instructions, not auto-handoffs.",{"type":43,"tag":52,"props":913,"children":914},{},[915],{"type":43,"tag":58,"props":916,"children":917},{},[918],{"type":49,"value":919},"Sibling skills the user runs at each stage:",{"type":43,"tag":798,"props":921,"children":922},{},[923,934,945,956,967],{"type":43,"tag":70,"props":924,"children":925},{},[926,932],{"type":43,"tag":146,"props":927,"children":929},{"className":928},[],[930],{"type":49,"value":931},"projects",{"type":49,"value":933}," — pre-Stage 2, only if no project exists yet",{"type":43,"tag":70,"props":935,"children":936},{},[937,943],{"type":43,"tag":146,"props":938,"children":940},{"className":939},[],[941],{"type":49,"value":942},"configs-create",{"type":49,"value":944}," — Stage 2 (creates the config and first variation)",{"type":43,"tag":70,"props":946,"children":947},{},[948,954],{"type":43,"tag":146,"props":949,"children":951},{"className":950},[],[952],{"type":49,"value":953},"tools",{"type":49,"value":955}," — Stage 3 (creates tool definitions and attaches them)",{"type":43,"tag":70,"props":957,"children":958},{},[959,965],{"type":43,"tag":146,"props":960,"children":962},{"className":961},[],[963],{"type":49,"value":964},"configs-targeting",{"type":49,"value":966}," — between Stage 2 and Stage 4 (promotes the new variation to fallthrough so the SDK actually serves it)",{"type":43,"tag":70,"props":968,"children":969},{},[970,976],{"type":43,"tag":146,"props":971,"children":973},{"className":972},[],[974],{"type":49,"value":975},"online-evals",{"type":49,"value":977}," — Stage 5 (attaches judges, creates custom judges)",{"type":43,"tag":281,"props":979,"children":981},{"id":980},"core-principles",[982],{"type":49,"value":983},"Core Principles",{"type":43,"tag":66,"props":985,"children":986},{},[987,997,1015,1039,1049],{"type":43,"tag":70,"props":988,"children":989},{},[990,995],{"type":43,"tag":58,"props":991,"children":992},{},[993],{"type":49,"value":994},"Inspect before you mutate.",{"type":49,"value":996}," Every stage begins with a read-only audit. Do not touch code until Step 1 is confirmed by the user.",{"type":43,"tag":70,"props":998,"children":999},{},[1000,1005,1007,1013],{"type":43,"tag":58,"props":1001,"children":1002},{},[1003],{"type":49,"value":1004},"Replace config, not business logic.",{"type":49,"value":1006}," The SDK call is a drop-in for the place where the model, parameters, and prompt are ",{"type":43,"tag":1008,"props":1009,"children":1010},"em",{},[1011],{"type":49,"value":1012},"defined",{"type":49,"value":1014}," — not for the provider call itself. OpenAI\u002FAnthropic\u002FBedrock calls stay where they are.",{"type":43,"tag":70,"props":1016,"children":1017},{},[1018,1023,1025,1031,1032,1037],{"type":43,"tag":58,"props":1019,"children":1020},{},[1021],{"type":49,"value":1022},"Fallback mirrors current behavior.",{"type":49,"value":1024}," The fallback passed to ",{"type":43,"tag":146,"props":1026,"children":1028},{"className":1027},[],[1029],{"type":49,"value":1030},"completion_config",{"type":49,"value":600},{"type":43,"tag":146,"props":1033,"children":1035},{"className":1034},[],[1036],{"type":49,"value":775},{"type":49,"value":1038}," must preserve the hardcoded values you removed, so the app is unchanged if LaunchDarkly is unreachable.",{"type":43,"tag":70,"props":1040,"children":1041},{},[1042,1047],{"type":43,"tag":58,"props":1043,"children":1044},{},[1045],{"type":49,"value":1046},"Stages are ordered.",{"type":49,"value":1048}," Wrap before you add tools. Add tools before you track. Track before you add evals. Skipping ahead produces configs without traffic, metrics without context, and judges with nothing to score.",{"type":43,"tag":70,"props":1050,"children":1051},{},[1052,1057,1059,1064,1066,1072,1073,1078,1079,1085,1087,1091],{"type":43,"tag":58,"props":1053,"children":1054},{},[1055],{"type":49,"value":1056},"Hand off to focused skills, manually.",{"type":49,"value":1058}," Each stage that needs a LaunchDarkly write tells the user to run a sibling slash-command (",{"type":43,"tag":146,"props":1060,"children":1062},{"className":1061},[],[1063],{"type":49,"value":260},{"type":49,"value":1065},", ",{"type":43,"tag":146,"props":1067,"children":1069},{"className":1068},[],[1070],{"type":49,"value":1071},"\u002Ftools",{"type":49,"value":1065},{"type":43,"tag":146,"props":1074,"children":1076},{"className":1075},[],[1077],{"type":49,"value":277},{"type":49,"value":1065},{"type":43,"tag":146,"props":1080,"children":1082},{"className":1081},[],[1083],{"type":49,"value":1084},"\u002Fonline-evals",{"type":49,"value":1086},") and waits for them to come back. This skill does ",{"type":43,"tag":58,"props":1088,"children":1089},{},[1090],{"type":49,"value":902},{"type":49,"value":1092}," auto-invoke other skills.",{"type":43,"tag":281,"props":1094,"children":1096},{"id":1095},"workflow",[1097],{"type":49,"value":1098},"Workflow",{"type":43,"tag":1100,"props":1101,"children":1103},"h3",{"id":1102},"minimum-viable-migration",[1104],{"type":49,"value":1105},"Minimum viable migration",{"type":43,"tag":52,"props":1107,"children":1108},{},[1109,1111,1116],{"type":49,"value":1110},"Stages 1–4 (audit, wrap, tools, tracker) are independently shippable. ",{"type":43,"tag":58,"props":1112,"children":1113},{},[1114],{"type":49,"value":1115},"A migration that stops after Stage 4 is complete, production-ready, and delivers the core value",{"type":49,"value":1117}," — externalized prompts and model config, targeting, variation A\u002FB testing, and Monitoring-tab metrics. Stage 5 (evaluators) is a quality-of-life addition, not a gate. Do not block a Stage-4 rollout on evaluators; ship the run-scoped tracker path, verify metrics flow, then come back for Stage 5 when the team has time to curate a dataset.",{"type":43,"tag":52,"props":1119,"children":1120},{},[1121,1123,1128],{"type":49,"value":1122},"That said, do not ",{"type":43,"tag":1008,"props":1124,"children":1125},{},[1126],{"type":49,"value":1127},"skip",{"type":49,"value":1129}," Stage 4. A migration without the tracker gives you externalized prompts but no visibility, which is most of the payoff left on the floor.",{"type":43,"tag":1100,"props":1131,"children":1133},{"id":1132},"step-1-audit-the-codebase-stage-1",[1134],{"type":49,"value":1135},"Step 1: Audit the codebase (Stage 1)",{"type":43,"tag":52,"props":1137,"children":1138},{},[1139,1141,1146],{"type":49,"value":1140},"This is the first stage. It is ",{"type":43,"tag":58,"props":1142,"children":1143},{},[1144],{"type":49,"value":1145},"read-only",{"type":49,"value":1147}," — no code writes, no LaunchDarkly resources created. The goal is to scan the repo and produce a structured manifest of every hardcoded value that needs to move, then hand the manifest back to the user for confirmation before any code is touched in Stage 2.",{"type":43,"tag":52,"props":1149,"children":1150},{},[1151,1153,1158],{"type":49,"value":1152},"Use ",{"type":43,"tag":179,"props":1154,"children":1155},{"href":745},[1156],{"type":49,"value":1157},"phase-1-analysis-checklist.md",{"type":49,"value":1159}," to scan:",{"type":43,"tag":66,"props":1161,"children":1162},{},[1163,1173,1183,1209,1219,1261,1338,1364],{"type":43,"tag":70,"props":1164,"children":1165},{},[1166,1171],{"type":43,"tag":58,"props":1167,"children":1168},{},[1169],{"type":49,"value":1170},"Language and package manager",{"type":49,"value":1172}," — Python (pip\u002Fpoetry\u002Fuv), TypeScript\u002FJavaScript (npm\u002Fpnpm\u002Fyarn), Go, Ruby, .NET",{"type":43,"tag":70,"props":1174,"children":1175},{},[1176,1181],{"type":43,"tag":58,"props":1177,"children":1178},{},[1179],{"type":49,"value":1180},"LLM provider",{"type":49,"value":1182}," — OpenAI, Anthropic, Bedrock, Gemini, LangChain, LangGraph, CrewAI, Strands",{"type":43,"tag":70,"props":1184,"children":1185},{},[1186,1191,1193,1199,1201,1207],{"type":43,"tag":58,"props":1187,"children":1188},{},[1189],{"type":49,"value":1190},"Existing LaunchDarkly usage",{"type":49,"value":1192}," — any pre-existing ",{"type":43,"tag":146,"props":1194,"children":1196},{"className":1195},[],[1197],{"type":49,"value":1198},"LDClient",{"type":49,"value":1200}," or ",{"type":43,"tag":146,"props":1202,"children":1204},{"className":1203},[],[1205],{"type":49,"value":1206},"ldclient",{"type":49,"value":1208}," initialization to reuse",{"type":43,"tag":70,"props":1210,"children":1211},{},[1212,1217],{"type":43,"tag":58,"props":1213,"children":1214},{},[1215],{"type":49,"value":1216},"Hardcoded model configs",{"type":49,"value":1218}," — model name string literals, temperature \u002F max_tokens \u002F top_p, system prompts, instruction strings",{"type":43,"tag":70,"props":1220,"children":1221},{},[1222,1227,1229,1235,1237,1243,1245,1251,1253,1259],{"type":43,"tag":58,"props":1223,"children":1224},{},[1225],{"type":49,"value":1226},"Template placeholders in prompts",{"type":49,"value":1228}," — ",{"type":43,"tag":146,"props":1230,"children":1232},{"className":1231},[],[1233],{"type":49,"value":1234},".format()",{"type":49,"value":1236}," calls, f-strings in prompt constants, JS\u002FTS template literals, ",{"type":43,"tag":146,"props":1238,"children":1240},{"className":1239},[],[1241],{"type":49,"value":1242},"%(var)s",{"type":49,"value":1244},", hand-rolled ",{"type":43,"tag":146,"props":1246,"children":1248},{"className":1247},[],[1249],{"type":49,"value":1250},"str.replace(\"__VAR__\", ...)",{"type":49,"value":1252},". Flag each placeholder name and its runtime-value source; all get rewritten to Mustache ",{"type":43,"tag":146,"props":1254,"children":1256},{"className":1255},[],[1257],{"type":49,"value":1258},"{{ variable }}",{"type":49,"value":1260}," in Stage 2.",{"type":43,"tag":70,"props":1262,"children":1263},{},[1264,1269,1271,1277,1278,1284,1286,1291,1293,1299,1301,1307,1309,1315,1316,1322,1324,1330,1332,1337],{"type":43,"tag":58,"props":1265,"children":1266},{},[1267],{"type":49,"value":1268},"Externalized prompt files",{"type":49,"value":1270}," — scan YAML \u002F JSON \u002F TOML \u002F Markdown \u002F ",{"type":43,"tag":146,"props":1272,"children":1274},{"className":1273},[],[1275],{"type":49,"value":1276},".prompt",{"type":49,"value":600},{"type":43,"tag":146,"props":1279,"children":1281},{"className":1280},[],[1282],{"type":49,"value":1283},".j2",{"type":49,"value":1285}," files ",{"type":43,"tag":58,"props":1287,"children":1288},{},[1289],{"type":49,"value":1290},"and",{"type":49,"value":1292}," prompt-template registries (",{"type":43,"tag":146,"props":1294,"children":1296},{"className":1295},[],[1297],{"type":49,"value":1298},"langchain.hub.pull(...)",{"type":49,"value":1300},", LangSmith ",{"type":43,"tag":146,"props":1302,"children":1304},{"className":1303},[],[1305],{"type":49,"value":1306},"client.pull_prompt(...)",{"type":49,"value":1308},") for prompts loaded at runtime. Common shapes: CrewAI ",{"type":43,"tag":146,"props":1310,"children":1312},{"className":1311},[],[1313],{"type":49,"value":1314},"agents.yaml",{"type":49,"value":600},{"type":43,"tag":146,"props":1317,"children":1319},{"className":1318},[],[1320],{"type":49,"value":1321},"tasks.yaml",{"type":49,"value":1323},", LangChain Promptfiles, k8s ConfigMap overlays, Pydantic Settings classes with ",{"type":43,"tag":146,"props":1325,"children":1327},{"className":1326},[],[1328],{"type":49,"value":1329},"prompt_*",{"type":49,"value":1331}," fields. Same Mustache rewrite (sub-step 5 of Stage 2) applies if the placeholder syntax differs. See ",{"type":43,"tag":179,"props":1333,"children":1334},{"href":745},[1335],{"type":49,"value":1336},"phase-1-analysis-checklist.md § 4",{"type":49,"value":192},{"type":43,"tag":70,"props":1339,"children":1340},{},[1341,1346,1348,1354,1356,1362],{"type":43,"tag":58,"props":1342,"children":1343},{},[1344],{"type":49,"value":1345},"Hardcoded app-scoped knobs",{"type":49,"value":1347}," — search-result limits, retry budgets, tool-timeout overrides, feature toggles, any config-dataclass field that isn't a prompt or model parameter but still governs agent behavior. These belong in ",{"type":43,"tag":146,"props":1349,"children":1351},{"className":1350},[],[1352],{"type":49,"value":1353},"model.custom",{"type":49,"value":1355}," on the variation (not ",{"type":43,"tag":146,"props":1357,"children":1359},{"className":1358},[],[1360],{"type":49,"value":1361},"model.parameters",{"type":49,"value":1363},", which is forwarded to the provider SDK and will crash on unknown kwargs).",{"type":43,"tag":70,"props":1365,"children":1366},{},[1367,1372],{"type":43,"tag":58,"props":1368,"children":1369},{},[1370],{"type":49,"value":1371},"Mode decision",{"type":49,"value":1373}," — completion mode (chat messages array) or agent mode (single instructions string). Completion mode is the default and the only mode that supports judges attached in the UI.",{"type":43,"tag":52,"props":1375,"children":1376},{},[1377],{"type":49,"value":1378},"For each hardcoded target the audit finds, record:",{"type":43,"tag":798,"props":1380,"children":1381},{},[1382,1387,1392,1425,1430],{"type":43,"tag":70,"props":1383,"children":1384},{},[1385],{"type":49,"value":1386},"File path and line range",{"type":43,"tag":70,"props":1388,"children":1389},{},[1390],{"type":49,"value":1391},"Current value (model name, full prompt text, parameter dict)",{"type":43,"tag":70,"props":1393,"children":1394},{},[1395,1397,1403,1404,1410,1411,1417,1418,1424],{"type":49,"value":1396},"Target config field (",{"type":43,"tag":146,"props":1398,"children":1400},{"className":1399},[],[1401],{"type":49,"value":1402},"model.name",{"type":49,"value":1065},{"type":43,"tag":146,"props":1405,"children":1407},{"className":1406},[],[1408],{"type":49,"value":1409},"model.parameters.temperature",{"type":49,"value":1065},{"type":43,"tag":146,"props":1412,"children":1414},{"className":1413},[],[1415],{"type":49,"value":1416},"messages[].content",{"type":49,"value":1065},{"type":43,"tag":146,"props":1419,"children":1421},{"className":1420},[],[1422],{"type":49,"value":1423},"instructions",{"type":49,"value":379},{"type":43,"tag":70,"props":1426,"children":1427},{},[1428],{"type":49,"value":1429},"Whether the surrounding call uses function calling \u002F tools (drives Stage 3)",{"type":43,"tag":70,"props":1431,"children":1432},{},[1433],{"type":49,"value":1434},"Whether the surrounding call has retry logic (affects where Stage 4 tracker calls go)",{"type":43,"tag":52,"props":1436,"children":1437},{},[1438],{"type":49,"value":1439},"This manifest is the contract for the next four stages.",{"type":43,"tag":52,"props":1441,"children":1442},{},[1443,1448],{"type":43,"tag":58,"props":1444,"children":1445},{},[1446],{"type":49,"value":1447},"Stage 1 output",{"type":49,"value":1449}," (return to user as a structured summary):",{"type":43,"tag":1451,"props":1452,"children":1456},"pre",{"className":1453,"code":1455,"language":49},[1454],"language-text","Language: Python 3.12\nPackage manager: uv\nLLM provider: OpenAI\nExisting LD SDK: none\nTarget mode: completion\nHardcoded targets:\n  - src\u002Fchat.py:42   model=\"gpt-4o\"\n  - src\u002Fchat.py:43   temperature=0.7, max_tokens=2000\n  - src\u002Fchat.py:45   system=\"You are a helpful assistant...\"\nExternalized prompt files: none (or e.g. \"prompts\u002Fagents.yaml — CrewAI role\u002Fgoal\u002Fbackstory\")\nPrompt-template registries: none (or e.g. langchain.hub.pull(\"rlm\u002Frag-prompt\") at app.py:14)\nCoverage totals: 3 hardcoded code targets · 0 externalized prompt files · 0 registry pulls\nProposed plan: single config key `chat-assistant`, mirror fallback, Stage 3 (tools) skipped (no function calling), Stage 4 (tracking) inline, Stage 5 (evals) attach built-in accuracy judge.\n",[1457],{"type":43,"tag":146,"props":1458,"children":1460},{"__ignoreMap":1459},"",[1461],{"type":49,"value":1455},{"type":43,"tag":52,"props":1463,"children":1464},{},[1465,1470,1472,1477,1479,1484],{"type":43,"tag":58,"props":1466,"children":1467},{},[1468],{"type":49,"value":1469},"STOP.",{"type":49,"value":1471}," Present this summary, state the coverage totals out loud (e.g. \"I found ",{"type":43,"tag":58,"props":1473,"children":1474},{},[1475],{"type":49,"value":1476},"N",{"type":49,"value":1478}," hardcoded code targets and ",{"type":43,"tag":58,"props":1480,"children":1481},{},[1482],{"type":49,"value":1483},"M",{"type":49,"value":1485}," externalized prompt files — does that match what you expected?\"), and wait for the user to reply with one of four explicit forms:",{"type":43,"tag":798,"props":1487,"children":1488},{},[1489,1503,1517,1531],{"type":43,"tag":70,"props":1490,"children":1491},{},[1492,1501],{"type":43,"tag":58,"props":1493,"children":1494},{},[1495],{"type":43,"tag":146,"props":1496,"children":1498},{"className":1497},[],[1499],{"type":49,"value":1500},"confirm",{"type":49,"value":1502}," — proceed to Stage 2.",{"type":43,"tag":70,"props":1504,"children":1505},{},[1506,1515],{"type":43,"tag":58,"props":1507,"children":1508},{},[1509],{"type":43,"tag":146,"props":1510,"children":1512},{"className":1511},[],[1513],{"type":49,"value":1514},"add: \u003Cfiles or paths>",{"type":49,"value":1516}," — re-run the audit with the new locations and present an updated summary.",{"type":43,"tag":70,"props":1518,"children":1519},{},[1520,1529],{"type":43,"tag":58,"props":1521,"children":1522},{},[1523],{"type":43,"tag":146,"props":1524,"children":1526},{"className":1525},[],[1527],{"type":49,"value":1528},"fix: \u003Ccorrection>",{"type":49,"value":1530}," — update a target in the list (provider, mode, prompt content, etc.) and ask again.",{"type":43,"tag":70,"props":1532,"children":1533},{},[1534,1543],{"type":43,"tag":58,"props":1535,"children":1536},{},[1537],{"type":43,"tag":146,"props":1538,"children":1540},{"className":1539},[],[1541],{"type":49,"value":1542},"stop",{"type":49,"value":1544}," — pause the migration here.",{"type":43,"tag":52,"props":1546,"children":1547},{},[1548,1550,1555,1556,1562,1563,1569,1570,1576,1577,1583,1585,1590],{"type":49,"value":1549},"Do not interpret any other word — including ",{"type":43,"tag":146,"props":1551,"children":1553},{"className":1552},[],[1554],{"type":49,"value":1127},{"type":49,"value":1065},{"type":43,"tag":146,"props":1557,"children":1559},{"className":1558},[],[1560],{"type":49,"value":1561},"next",{"type":49,"value":1065},{"type":43,"tag":146,"props":1564,"children":1566},{"className":1565},[],[1567],{"type":49,"value":1568},"go",{"type":49,"value":1065},{"type":43,"tag":146,"props":1571,"children":1573},{"className":1572},[],[1574],{"type":49,"value":1575},"ok",{"type":49,"value":1065},{"type":43,"tag":146,"props":1578,"children":1580},{"className":1579},[],[1581],{"type":49,"value":1582},"proceed",{"type":49,"value":1584}," — as confirmation; ask the user to pick one of the four forms. ",{"type":43,"tag":58,"props":1586,"children":1587},{},[1588],{"type":49,"value":1589},"This is the most important checkpoint in the workflow",{"type":49,"value":1591}," — if the audit is wrong, every stage after this will be wrong. The user should cross-check the hardcoded-targets list against what they know is in the code before giving the go-ahead.",{"type":43,"tag":1100,"props":1593,"children":1595},{"id":1594},"step-2-wrap-the-call-in-the-ai-sdk-stage-2",[1596],{"type":49,"value":1597},"Step 2: Wrap the call in the AI SDK (Stage 2)",{"type":43,"tag":52,"props":1599,"children":1600},{},[1601],{"type":49,"value":1602},"This is the first stage that writes code. It has nine sub-steps.",{"type":43,"tag":66,"props":1604,"children":1605},{},[1606,1708,1845,2523,2592,2901,3010,3452,3477],{"type":43,"tag":70,"props":1607,"children":1608},{},[1609,1614,1616,1621,1623,1702,1706],{"type":43,"tag":58,"props":1610,"children":1611},{},[1612],{"type":49,"value":1613},"Delete any hand-rolled model \u002F tool wrappers the audit flagged.",{"type":49,"value":1615}," Do this ",{"type":43,"tag":1008,"props":1617,"children":1618},{},[1619],{"type":49,"value":1620},"before",{"type":49,"value":1622}," installing the new SDK so the replacement lands in a repo without confusing fallback imports. The two shapes the Stage 1 audit should have surfaced:",{"type":43,"tag":798,"props":1624,"children":1625},{},[1626,1662],{"type":43,"tag":70,"props":1627,"children":1628},{},[1629,1646,1648,1653,1655,1660],{"type":43,"tag":58,"props":1630,"children":1631},{},[1632,1637,1639,1644],{"type":43,"tag":146,"props":1633,"children":1635},{"className":1634},[],[1636],{"type":49,"value":222},{"type":49,"value":1638}," or any ",{"type":43,"tag":146,"props":1640,"children":1642},{"className":1641},[],[1643],{"type":49,"value":230},{"type":49,"value":1645}," wrapper.",{"type":49,"value":1647}," Ships with ",{"type":43,"tag":146,"props":1649,"children":1651},{"className":1650},[],[1652],{"type":49,"value":214},{"type":49,"value":1654}," and many derivative repos. Delete the function and its module; the replacement is ",{"type":43,"tag":146,"props":1656,"children":1658},{"className":1657},[],[1659],{"type":49,"value":245},{"type":49,"value":1661}," (installed in the next sub-step). Leaving the wrapper in place means the next edit in this repo will import the familiar helper and silently drop variation parameters.",{"type":43,"tag":70,"props":1663,"children":1664},{},[1665,1692,1694,1700],{"type":43,"tag":58,"props":1666,"children":1667},{},[1668,1670,1676,1677,1683,1684,1690],{"type":49,"value":1669},"Hand-rolled ",{"type":43,"tag":146,"props":1671,"children":1673},{"className":1672},[],[1674],{"type":49,"value":1675},"resolve_tools",{"type":49,"value":600},{"type":43,"tag":146,"props":1678,"children":1680},{"className":1679},[],[1681],{"type":49,"value":1682},"TOOL_REGISTRY",{"type":49,"value":600},{"type":43,"tag":146,"props":1685,"children":1687},{"className":1686},[],[1688],{"type":49,"value":1689},"ALL_TOOLS",{"type":49,"value":1691}," helpers that hard-code a static tool list.",{"type":49,"value":1693}," Delete them; ",{"type":43,"tag":146,"props":1695,"children":1697},{"className":1696},[],[1698],{"type":49,"value":1699},"ldai_langchain.langchain_helper.build_structured_tools(ai_config, TOOL_REGISTRY_DICT)",{"type":49,"value":1701}," is the canonical replacement and gets wired in Stage 3. If you leave the hand-rolled version, both shapes will live side-by-side and the next contributor will pick the familiar one.",{"type":43,"tag":1703,"props":1704,"children":1705},"br",{},[],{"type":49,"value":1707},"Commit the deletion separately from the SDK install if the repo's review process benefits from it — otherwise bundle with sub-step 2.",{"type":43,"tag":70,"props":1709,"children":1710},{},[1711,1716,1718,1775,1778,1780],{"type":43,"tag":58,"props":1712,"children":1713},{},[1714],{"type":49,"value":1715},"Install the AI SDK.",{"type":49,"value":1717}," Detect the package manager from Step 1, then install:",{"type":43,"tag":798,"props":1719,"children":1720},{},[1721,1739,1757],{"type":43,"tag":70,"props":1722,"children":1723},{},[1724,1725,1731,1733],{"type":49,"value":847},{"type":43,"tag":146,"props":1726,"children":1728},{"className":1727},[],[1729],{"type":49,"value":1730},"launchdarkly-server-sdk",{"type":49,"value":1732}," + ",{"type":43,"tag":146,"props":1734,"children":1736},{"className":1735},[],[1737],{"type":49,"value":1738},"launchdarkly-server-sdk-ai>=0.20.0",{"type":43,"tag":70,"props":1740,"children":1741},{},[1742,1744,1750,1751],{"type":49,"value":1743},"Node.js\u002FTypeScript: ",{"type":43,"tag":146,"props":1745,"children":1747},{"className":1746},[],[1748],{"type":49,"value":1749},"@launchdarkly\u002Fnode-server-sdk",{"type":49,"value":1732},{"type":43,"tag":146,"props":1752,"children":1754},{"className":1753},[],[1755],{"type":49,"value":1756},"@launchdarkly\u002Fserver-sdk-ai@^0.20.0",{"type":43,"tag":70,"props":1758,"children":1759},{},[1760,1762,1768,1769],{"type":49,"value":1761},"Go: ",{"type":43,"tag":146,"props":1763,"children":1765},{"className":1764},[],[1766],{"type":49,"value":1767},"github.com\u002Flaunchdarkly\u002Fgo-server-sdk\u002Fv7",{"type":49,"value":1732},{"type":43,"tag":146,"props":1770,"children":1772},{"className":1771},[],[1773],{"type":49,"value":1774},"github.com\u002Flaunchdarkly\u002Fgo-server-sdk\u002Fldai",{"type":43,"tag":1703,"props":1776,"children":1777},{},[],{"type":49,"value":1779},"Tier-2 provider packages (install in Stage 4, only if you're using the matching provider):",{"type":43,"tag":798,"props":1781,"children":1782},{},[1783,1804,1823,1834],{"type":43,"tag":70,"props":1784,"children":1785},{},[1786,1788,1794,1796,1802],{"type":49,"value":1787},"OpenAI: ",{"type":43,"tag":146,"props":1789,"children":1791},{"className":1790},[],[1792],{"type":49,"value":1793},"launchdarkly-server-sdk-ai-openai>=0.4.0",{"type":49,"value":1795}," (Python) \u002F ",{"type":43,"tag":146,"props":1797,"children":1799},{"className":1798},[],[1800],{"type":49,"value":1801},"@launchdarkly\u002Fserver-sdk-ai-openai@^0.5.5",{"type":49,"value":1803}," (Node)",{"type":43,"tag":70,"props":1805,"children":1806},{},[1807,1809,1815,1816,1822],{"type":49,"value":1808},"LangChain \u002F LangGraph: ",{"type":43,"tag":146,"props":1810,"children":1812},{"className":1811},[],[1813],{"type":49,"value":1814},"launchdarkly-server-sdk-ai-langchain>=0.5.0",{"type":49,"value":1795},{"type":43,"tag":146,"props":1817,"children":1819},{"className":1818},[],[1820],{"type":49,"value":1821},"@launchdarkly\u002Fserver-sdk-ai-langchain@^0.5.5",{"type":49,"value":1803},{"type":43,"tag":70,"props":1824,"children":1825},{},[1826,1828],{"type":49,"value":1827},"Vercel AI SDK (Node only): ",{"type":43,"tag":146,"props":1829,"children":1831},{"className":1830},[],[1832],{"type":49,"value":1833},"@launchdarkly\u002Fserver-sdk-ai-vercel@^0.5.5",{"type":43,"tag":70,"props":1835,"children":1836},{},[1837,1839,1844],{"type":49,"value":1838},"Anthropic, Gemini, Bedrock — no provider package published; use Tier-3 custom extractor (see ",{"type":43,"tag":146,"props":1840,"children":1842},{"className":1841},[],[1843],{"type":49,"value":626},{"type":49,"value":379},{"type":43,"tag":70,"props":1846,"children":1847},{},[1848,1861,1863,1868,1870,1873,1878,2076,2079,2084],{"type":43,"tag":58,"props":1849,"children":1850},{},[1851,1853,1859],{"type":49,"value":1852},"Initialize ",{"type":43,"tag":146,"props":1854,"children":1856},{"className":1855},[],[1857],{"type":49,"value":1858},"LDAIClient",{"type":49,"value":1860}," once at startup.",{"type":49,"value":1862}," Reuse any existing ",{"type":43,"tag":146,"props":1864,"children":1866},{"className":1865},[],[1867],{"type":49,"value":1198},{"type":49,"value":1869}," — do not create a second base client. Place the initialization in the same module that owns existing app config.",{"type":43,"tag":1703,"props":1871,"children":1872},{},[],{"type":43,"tag":58,"props":1874,"children":1875},{},[1876],{"type":49,"value":1877},"Python:",{"type":43,"tag":1451,"props":1879,"children":1883},{"className":1880,"code":1881,"language":1882,"meta":1459,"style":1459},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import os\nimport ldclient\nfrom ldclient.config import Config\nfrom ldai.client import LDAIClient\n\n# Order matters: ldclient.get() raises if called before ldclient.set_config().\n# The set_config call is what initializes the singleton; .get() just returns it.\nsdk_key = os.environ.get(\"LD_SDK_KEY\")\nif sdk_key:\n    ldclient.set_config(Config(sdk_key))\nelse:\n    # Missing key: init in offline mode so the app still starts and the fallback\n    # path runs on every call. Never raise at import time for a missing env var —\n    # that turns a config gap into a boot failure.\n    import logging\n    logging.getLogger(__name__).warning(\n        \"LD_SDK_KEY not set; configs will use fallback values only.\"\n    )\n    ldclient.set_config(Config(\"\", offline=True))\n\nai_client = LDAIClient(ldclient.get())\n","python",[1884],{"type":43,"tag":146,"props":1885,"children":1886},{"__ignoreMap":1459},[1887,1898,1907,1916,1925,1935,1943,1952,1961,1970,1979,1988,1997,2006,2015,2024,2033,2042,2051,2060,2067],{"type":43,"tag":1888,"props":1889,"children":1892},"span",{"class":1890,"line":1891},"line",1,[1893],{"type":43,"tag":1888,"props":1894,"children":1895},{},[1896],{"type":49,"value":1897},"import os\n",{"type":43,"tag":1888,"props":1899,"children":1901},{"class":1890,"line":1900},2,[1902],{"type":43,"tag":1888,"props":1903,"children":1904},{},[1905],{"type":49,"value":1906},"import ldclient\n",{"type":43,"tag":1888,"props":1908,"children":1910},{"class":1890,"line":1909},3,[1911],{"type":43,"tag":1888,"props":1912,"children":1913},{},[1914],{"type":49,"value":1915},"from ldclient.config import Config\n",{"type":43,"tag":1888,"props":1917,"children":1919},{"class":1890,"line":1918},4,[1920],{"type":43,"tag":1888,"props":1921,"children":1922},{},[1923],{"type":49,"value":1924},"from ldai.client import LDAIClient\n",{"type":43,"tag":1888,"props":1926,"children":1928},{"class":1890,"line":1927},5,[1929],{"type":43,"tag":1888,"props":1930,"children":1932},{"emptyLinePlaceholder":1931},true,[1933],{"type":49,"value":1934},"\n",{"type":43,"tag":1888,"props":1936,"children":1937},{"class":1890,"line":24},[1938],{"type":43,"tag":1888,"props":1939,"children":1940},{},[1941],{"type":49,"value":1942},"# Order matters: ldclient.get() raises if called before ldclient.set_config().\n",{"type":43,"tag":1888,"props":1944,"children":1946},{"class":1890,"line":1945},7,[1947],{"type":43,"tag":1888,"props":1948,"children":1949},{},[1950],{"type":49,"value":1951},"# The set_config call is what initializes the singleton; .get() just returns it.\n",{"type":43,"tag":1888,"props":1953,"children":1955},{"class":1890,"line":1954},8,[1956],{"type":43,"tag":1888,"props":1957,"children":1958},{},[1959],{"type":49,"value":1960},"sdk_key = os.environ.get(\"LD_SDK_KEY\")\n",{"type":43,"tag":1888,"props":1962,"children":1964},{"class":1890,"line":1963},9,[1965],{"type":43,"tag":1888,"props":1966,"children":1967},{},[1968],{"type":49,"value":1969},"if sdk_key:\n",{"type":43,"tag":1888,"props":1971,"children":1973},{"class":1890,"line":1972},10,[1974],{"type":43,"tag":1888,"props":1975,"children":1976},{},[1977],{"type":49,"value":1978},"    ldclient.set_config(Config(sdk_key))\n",{"type":43,"tag":1888,"props":1980,"children":1982},{"class":1890,"line":1981},11,[1983],{"type":43,"tag":1888,"props":1984,"children":1985},{},[1986],{"type":49,"value":1987},"else:\n",{"type":43,"tag":1888,"props":1989,"children":1991},{"class":1890,"line":1990},12,[1992],{"type":43,"tag":1888,"props":1993,"children":1994},{},[1995],{"type":49,"value":1996},"    # Missing key: init in offline mode so the app still starts and the fallback\n",{"type":43,"tag":1888,"props":1998,"children":2000},{"class":1890,"line":1999},13,[2001],{"type":43,"tag":1888,"props":2002,"children":2003},{},[2004],{"type":49,"value":2005},"    # path runs on every call. Never raise at import time for a missing env var —\n",{"type":43,"tag":1888,"props":2007,"children":2009},{"class":1890,"line":2008},14,[2010],{"type":43,"tag":1888,"props":2011,"children":2012},{},[2013],{"type":49,"value":2014},"    # that turns a config gap into a boot failure.\n",{"type":43,"tag":1888,"props":2016,"children":2018},{"class":1890,"line":2017},15,[2019],{"type":43,"tag":1888,"props":2020,"children":2021},{},[2022],{"type":49,"value":2023},"    import logging\n",{"type":43,"tag":1888,"props":2025,"children":2027},{"class":1890,"line":2026},16,[2028],{"type":43,"tag":1888,"props":2029,"children":2030},{},[2031],{"type":49,"value":2032},"    logging.getLogger(__name__).warning(\n",{"type":43,"tag":1888,"props":2034,"children":2036},{"class":1890,"line":2035},17,[2037],{"type":43,"tag":1888,"props":2038,"children":2039},{},[2040],{"type":49,"value":2041},"        \"LD_SDK_KEY not set; configs will use fallback values only.\"\n",{"type":43,"tag":1888,"props":2043,"children":2045},{"class":1890,"line":2044},18,[2046],{"type":43,"tag":1888,"props":2047,"children":2048},{},[2049],{"type":49,"value":2050},"    )\n",{"type":43,"tag":1888,"props":2052,"children":2054},{"class":1890,"line":2053},19,[2055],{"type":43,"tag":1888,"props":2056,"children":2057},{},[2058],{"type":49,"value":2059},"    ldclient.set_config(Config(\"\", offline=True))\n",{"type":43,"tag":1888,"props":2061,"children":2062},{"class":1890,"line":20},[2063],{"type":43,"tag":1888,"props":2064,"children":2065},{"emptyLinePlaceholder":1931},[2066],{"type":49,"value":1934},{"type":43,"tag":1888,"props":2068,"children":2070},{"class":1890,"line":2069},21,[2071],{"type":43,"tag":1888,"props":2072,"children":2073},{},[2074],{"type":49,"value":2075},"ai_client = LDAIClient(ldclient.get())\n",{"type":43,"tag":1703,"props":2077,"children":2078},{},[],{"type":43,"tag":58,"props":2080,"children":2081},{},[2082],{"type":49,"value":2083},"Node.js\u002FTypeScript:",{"type":43,"tag":1451,"props":2085,"children":2089},{"className":2086,"code":2087,"language":2088,"meta":1459,"style":1459},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { init } from '@launchdarkly\u002Fnode-server-sdk';\nimport { initAi } from '@launchdarkly\u002Fserver-sdk-ai';\n\n\u002F\u002F The Node SDK does not have an explicit offline mode — a missing or invalid\n\u002F\u002F key fails fast during waitForInitialization, and every agent_config \u002F\n\u002F\u002F completion_config call returns the fallback. Log a warning; do not throw.\nif (!process.env.LD_SDK_KEY) {\n  console.warn('LD_SDK_KEY not set; configs will use fallback values only.');\n}\nconst ldClient = init(process.env.LD_SDK_KEY ?? 'sdk-offline');\nawait ldClient.waitForInitialization({ timeout: 10 }).catch(() => {\n  \u002F\u002F Swallow init failures in offline mode; fallback path runs.\n});\nconst aiClient = initAi(ldClient);\n","typescript",[2090],{"type":43,"tag":146,"props":2091,"children":2092},{"__ignoreMap":1459},[2093,2144,2185,2192,2201,2209,2217,2263,2308,2316,2387,2470,2478,2494],{"type":43,"tag":1888,"props":2094,"children":2095},{"class":1890,"line":1891},[2096,2102,2108,2114,2119,2124,2129,2134,2139],{"type":43,"tag":1888,"props":2097,"children":2099},{"style":2098},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[2100],{"type":49,"value":2101},"import",{"type":43,"tag":1888,"props":2103,"children":2105},{"style":2104},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[2106],{"type":49,"value":2107}," {",{"type":43,"tag":1888,"props":2109,"children":2111},{"style":2110},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[2112],{"type":49,"value":2113}," init",{"type":43,"tag":1888,"props":2115,"children":2116},{"style":2104},[2117],{"type":49,"value":2118}," }",{"type":43,"tag":1888,"props":2120,"children":2121},{"style":2098},[2122],{"type":49,"value":2123}," from",{"type":43,"tag":1888,"props":2125,"children":2126},{"style":2104},[2127],{"type":49,"value":2128}," '",{"type":43,"tag":1888,"props":2130,"children":2132},{"style":2131},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[2133],{"type":49,"value":1749},{"type":43,"tag":1888,"props":2135,"children":2136},{"style":2104},[2137],{"type":49,"value":2138},"'",{"type":43,"tag":1888,"props":2140,"children":2141},{"style":2104},[2142],{"type":49,"value":2143},";\n",{"type":43,"tag":1888,"props":2145,"children":2146},{"class":1890,"line":1900},[2147,2151,2155,2160,2164,2168,2172,2177,2181],{"type":43,"tag":1888,"props":2148,"children":2149},{"style":2098},[2150],{"type":49,"value":2101},{"type":43,"tag":1888,"props":2152,"children":2153},{"style":2104},[2154],{"type":49,"value":2107},{"type":43,"tag":1888,"props":2156,"children":2157},{"style":2110},[2158],{"type":49,"value":2159}," initAi",{"type":43,"tag":1888,"props":2161,"children":2162},{"style":2104},[2163],{"type":49,"value":2118},{"type":43,"tag":1888,"props":2165,"children":2166},{"style":2098},[2167],{"type":49,"value":2123},{"type":43,"tag":1888,"props":2169,"children":2170},{"style":2104},[2171],{"type":49,"value":2128},{"type":43,"tag":1888,"props":2173,"children":2174},{"style":2131},[2175],{"type":49,"value":2176},"@launchdarkly\u002Fserver-sdk-ai",{"type":43,"tag":1888,"props":2178,"children":2179},{"style":2104},[2180],{"type":49,"value":2138},{"type":43,"tag":1888,"props":2182,"children":2183},{"style":2104},[2184],{"type":49,"value":2143},{"type":43,"tag":1888,"props":2186,"children":2187},{"class":1890,"line":1909},[2188],{"type":43,"tag":1888,"props":2189,"children":2190},{"emptyLinePlaceholder":1931},[2191],{"type":49,"value":1934},{"type":43,"tag":1888,"props":2193,"children":2194},{"class":1890,"line":1918},[2195],{"type":43,"tag":1888,"props":2196,"children":2198},{"style":2197},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[2199],{"type":49,"value":2200},"\u002F\u002F The Node SDK does not have an explicit offline mode — a missing or invalid\n",{"type":43,"tag":1888,"props":2202,"children":2203},{"class":1890,"line":1927},[2204],{"type":43,"tag":1888,"props":2205,"children":2206},{"style":2197},[2207],{"type":49,"value":2208},"\u002F\u002F key fails fast during waitForInitialization, and every agent_config \u002F\n",{"type":43,"tag":1888,"props":2210,"children":2211},{"class":1890,"line":24},[2212],{"type":43,"tag":1888,"props":2213,"children":2214},{"style":2197},[2215],{"type":49,"value":2216},"\u002F\u002F completion_config call returns the fallback. Log a warning; do not throw.\n",{"type":43,"tag":1888,"props":2218,"children":2219},{"class":1890,"line":1945},[2220,2225,2230,2235,2240,2244,2249,2253,2258],{"type":43,"tag":1888,"props":2221,"children":2222},{"style":2098},[2223],{"type":49,"value":2224},"if",{"type":43,"tag":1888,"props":2226,"children":2227},{"style":2110},[2228],{"type":49,"value":2229}," (",{"type":43,"tag":1888,"props":2231,"children":2232},{"style":2104},[2233],{"type":49,"value":2234},"!",{"type":43,"tag":1888,"props":2236,"children":2237},{"style":2110},[2238],{"type":49,"value":2239},"process",{"type":43,"tag":1888,"props":2241,"children":2242},{"style":2104},[2243],{"type":49,"value":192},{"type":43,"tag":1888,"props":2245,"children":2246},{"style":2110},[2247],{"type":49,"value":2248},"env",{"type":43,"tag":1888,"props":2250,"children":2251},{"style":2104},[2252],{"type":49,"value":192},{"type":43,"tag":1888,"props":2254,"children":2255},{"style":2110},[2256],{"type":49,"value":2257},"LD_SDK_KEY) ",{"type":43,"tag":1888,"props":2259,"children":2260},{"style":2104},[2261],{"type":49,"value":2262},"{\n",{"type":43,"tag":1888,"props":2264,"children":2265},{"class":1890,"line":1954},[2266,2271,2275,2281,2287,2291,2296,2300,2304],{"type":43,"tag":1888,"props":2267,"children":2268},{"style":2110},[2269],{"type":49,"value":2270},"  console",{"type":43,"tag":1888,"props":2272,"children":2273},{"style":2104},[2274],{"type":49,"value":192},{"type":43,"tag":1888,"props":2276,"children":2278},{"style":2277},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[2279],{"type":49,"value":2280},"warn",{"type":43,"tag":1888,"props":2282,"children":2284},{"style":2283},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[2285],{"type":49,"value":2286},"(",{"type":43,"tag":1888,"props":2288,"children":2289},{"style":2104},[2290],{"type":49,"value":2138},{"type":43,"tag":1888,"props":2292,"children":2293},{"style":2131},[2294],{"type":49,"value":2295},"LD_SDK_KEY not set; configs will use fallback values only.",{"type":43,"tag":1888,"props":2297,"children":2298},{"style":2104},[2299],{"type":49,"value":2138},{"type":43,"tag":1888,"props":2301,"children":2302},{"style":2283},[2303],{"type":49,"value":379},{"type":43,"tag":1888,"props":2305,"children":2306},{"style":2104},[2307],{"type":49,"value":2143},{"type":43,"tag":1888,"props":2309,"children":2310},{"class":1890,"line":1963},[2311],{"type":43,"tag":1888,"props":2312,"children":2313},{"style":2104},[2314],{"type":49,"value":2315},"}\n",{"type":43,"tag":1888,"props":2317,"children":2318},{"class":1890,"line":1972},[2319,2325,2330,2335,2339,2344,2348,2352,2356,2361,2366,2370,2375,2379,2383],{"type":43,"tag":1888,"props":2320,"children":2322},{"style":2321},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[2323],{"type":49,"value":2324},"const",{"type":43,"tag":1888,"props":2326,"children":2327},{"style":2110},[2328],{"type":49,"value":2329}," ldClient ",{"type":43,"tag":1888,"props":2331,"children":2332},{"style":2104},[2333],{"type":49,"value":2334},"=",{"type":43,"tag":1888,"props":2336,"children":2337},{"style":2277},[2338],{"type":49,"value":2113},{"type":43,"tag":1888,"props":2340,"children":2341},{"style":2110},[2342],{"type":49,"value":2343},"(process",{"type":43,"tag":1888,"props":2345,"children":2346},{"style":2104},[2347],{"type":49,"value":192},{"type":43,"tag":1888,"props":2349,"children":2350},{"style":2110},[2351],{"type":49,"value":2248},{"type":43,"tag":1888,"props":2353,"children":2354},{"style":2104},[2355],{"type":49,"value":192},{"type":43,"tag":1888,"props":2357,"children":2358},{"style":2110},[2359],{"type":49,"value":2360},"LD_SDK_KEY ",{"type":43,"tag":1888,"props":2362,"children":2363},{"style":2104},[2364],{"type":49,"value":2365},"??",{"type":43,"tag":1888,"props":2367,"children":2368},{"style":2104},[2369],{"type":49,"value":2128},{"type":43,"tag":1888,"props":2371,"children":2372},{"style":2131},[2373],{"type":49,"value":2374},"sdk-offline",{"type":43,"tag":1888,"props":2376,"children":2377},{"style":2104},[2378],{"type":49,"value":2138},{"type":43,"tag":1888,"props":2380,"children":2381},{"style":2110},[2382],{"type":49,"value":379},{"type":43,"tag":1888,"props":2384,"children":2385},{"style":2104},[2386],{"type":49,"value":2143},{"type":43,"tag":1888,"props":2388,"children":2389},{"class":1890,"line":1981},[2390,2395,2400,2404,2409,2413,2418,2423,2428,2434,2438,2442,2446,2451,2455,2460,2465],{"type":43,"tag":1888,"props":2391,"children":2392},{"style":2098},[2393],{"type":49,"value":2394},"await",{"type":43,"tag":1888,"props":2396,"children":2397},{"style":2110},[2398],{"type":49,"value":2399}," ldClient",{"type":43,"tag":1888,"props":2401,"children":2402},{"style":2104},[2403],{"type":49,"value":192},{"type":43,"tag":1888,"props":2405,"children":2406},{"style":2277},[2407],{"type":49,"value":2408},"waitForInitialization",{"type":43,"tag":1888,"props":2410,"children":2411},{"style":2110},[2412],{"type":49,"value":2286},{"type":43,"tag":1888,"props":2414,"children":2415},{"style":2104},[2416],{"type":49,"value":2417},"{",{"type":43,"tag":1888,"props":2419,"children":2420},{"style":2283},[2421],{"type":49,"value":2422}," timeout",{"type":43,"tag":1888,"props":2424,"children":2425},{"style":2104},[2426],{"type":49,"value":2427},":",{"type":43,"tag":1888,"props":2429,"children":2431},{"style":2430},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[2432],{"type":49,"value":2433}," 10",{"type":43,"tag":1888,"props":2435,"children":2436},{"style":2104},[2437],{"type":49,"value":2118},{"type":43,"tag":1888,"props":2439,"children":2440},{"style":2110},[2441],{"type":49,"value":379},{"type":43,"tag":1888,"props":2443,"children":2444},{"style":2104},[2445],{"type":49,"value":192},{"type":43,"tag":1888,"props":2447,"children":2448},{"style":2277},[2449],{"type":49,"value":2450},"catch",{"type":43,"tag":1888,"props":2452,"children":2453},{"style":2110},[2454],{"type":49,"value":2286},{"type":43,"tag":1888,"props":2456,"children":2457},{"style":2104},[2458],{"type":49,"value":2459},"()",{"type":43,"tag":1888,"props":2461,"children":2462},{"style":2321},[2463],{"type":49,"value":2464}," =>",{"type":43,"tag":1888,"props":2466,"children":2467},{"style":2104},[2468],{"type":49,"value":2469}," {\n",{"type":43,"tag":1888,"props":2471,"children":2472},{"class":1890,"line":1990},[2473],{"type":43,"tag":1888,"props":2474,"children":2475},{"style":2197},[2476],{"type":49,"value":2477},"  \u002F\u002F Swallow init failures in offline mode; fallback path runs.\n",{"type":43,"tag":1888,"props":2479,"children":2480},{"class":1890,"line":1999},[2481,2486,2490],{"type":43,"tag":1888,"props":2482,"children":2483},{"style":2104},[2484],{"type":49,"value":2485},"}",{"type":43,"tag":1888,"props":2487,"children":2488},{"style":2110},[2489],{"type":49,"value":379},{"type":43,"tag":1888,"props":2491,"children":2492},{"style":2104},[2493],{"type":49,"value":2143},{"type":43,"tag":1888,"props":2495,"children":2496},{"class":1890,"line":2008},[2497,2501,2506,2510,2514,2519],{"type":43,"tag":1888,"props":2498,"children":2499},{"style":2321},[2500],{"type":49,"value":2324},{"type":43,"tag":1888,"props":2502,"children":2503},{"style":2110},[2504],{"type":49,"value":2505}," aiClient ",{"type":43,"tag":1888,"props":2507,"children":2508},{"style":2104},[2509],{"type":49,"value":2334},{"type":43,"tag":1888,"props":2511,"children":2512},{"style":2277},[2513],{"type":49,"value":2159},{"type":43,"tag":1888,"props":2515,"children":2516},{"style":2110},[2517],{"type":49,"value":2518},"(ldClient)",{"type":43,"tag":1888,"props":2520,"children":2521},{"style":2104},[2522],{"type":49,"value":2143},{"type":43,"tag":70,"props":2524,"children":2525},{},[2526,2537,2539,2551,2553,2559,2561,2564,2583,2585,2590],{"type":43,"tag":58,"props":2527,"children":2528},{},[2529,2531,2536],{"type":49,"value":2530},"Hand off to ",{"type":43,"tag":146,"props":2532,"children":2534},{"className":2533},[],[2535],{"type":49,"value":942},{"type":49,"value":192},{"type":49,"value":2538}," Print the extracted model, prompt\u002Finstructions, parameters, and mode from the Stage 1 manifest, then tell the user: ",{"type":43,"tag":1008,"props":2540,"children":2541},{},[2542,2544,2549],{"type":49,"value":2543},"\"Run ",{"type":43,"tag":146,"props":2545,"children":2547},{"className":2546},[],[2548],{"type":49,"value":260},{"type":49,"value":2550}," with these inputs, then come back here.\"",{"type":49,"value":2552}," Supply the config key you want the code to call (e.g. ",{"type":43,"tag":146,"props":2554,"children":2556},{"className":2555},[],[2557],{"type":49,"value":2558},"chat-assistant",{"type":49,"value":2560},"). Do not attempt to auto-invoke the sibling skill — wait for the user to finish it before continuing.",{"type":43,"tag":1703,"props":2562,"children":2563},{},[],{"type":43,"tag":58,"props":2565,"children":2566},{},[2567,2569,2574,2576,2581],{"type":49,"value":2568},"After ",{"type":43,"tag":146,"props":2570,"children":2572},{"className":2571},[],[2573],{"type":49,"value":942},{"type":49,"value":2575}," finishes, the user must also run ",{"type":43,"tag":146,"props":2577,"children":2579},{"className":2578},[],[2580],{"type":49,"value":277},{"type":49,"value":2582}," to promote the new variation to fallthrough.",{"type":49,"value":2584}," A freshly created variation returns ",{"type":43,"tag":146,"props":2586,"children":2588},{"className":2587},[],[2589],{"type":49,"value":269},{"type":49,"value":2591}," to every consumer until targeting is updated. Skip this and Stage 2 verification (sub-step 9 below) will silently take the fallback path on every request.",{"type":43,"tag":70,"props":2593,"children":2594},{},[2595,2600,2602,2607,2609,2615,2616,2622,2623,2629,2631,2636,2638,2643,2645,2650,2652,2656,2658,2662,2664,2670,2672,2678,2679,2685,2687,2692,2694,2699,2701,2704,2709,2732,2735,2740,2811,2814,2816,2888,2891,2893,2899],{"type":43,"tag":58,"props":2596,"children":2597},{},[2598],{"type":49,"value":2599},"Rewrite template placeholders to Mustache syntax.",{"type":49,"value":2601}," If the hardcoded prompt interpolates runtime values with Python ",{"type":43,"tag":146,"props":2603,"children":2605},{"className":2604},[],[2606],{"type":49,"value":1234},{"type":49,"value":2608},", f-strings, JS template literals, or any other non-Mustache syntax (e.g. ",{"type":43,"tag":146,"props":2610,"children":2612},{"className":2611},[],[2613],{"type":49,"value":2614},"{system_time}",{"type":49,"value":1065},{"type":43,"tag":146,"props":2617,"children":2619},{"className":2618},[],[2620],{"type":49,"value":2621},"${userName}",{"type":49,"value":1065},{"type":43,"tag":146,"props":2624,"children":2626},{"className":2625},[],[2627],{"type":49,"value":2628},"%(topic)s",{"type":49,"value":2630},"), rewrite every placeholder to ",{"type":43,"tag":146,"props":2632,"children":2634},{"className":2633},[],[2635],{"type":49,"value":1258},{"type":49,"value":2637}," Mustache form. Do this in ",{"type":43,"tag":58,"props":2639,"children":2640},{},[2641],{"type":49,"value":2642},"both",{"type":49,"value":2644}," the file you're about to send to ",{"type":43,"tag":146,"props":2646,"children":2648},{"className":2647},[],[2649],{"type":49,"value":260},{"type":49,"value":2651}," ",{"type":43,"tag":1008,"props":2653,"children":2654},{},[2655],{"type":49,"value":1290},{"type":49,"value":2657}," the fallback string you'll write in sub-step 6. The AI SDK interpolates variables through a Mustache renderer on the LD-served path ",{"type":43,"tag":1008,"props":2659,"children":2660},{},[2661],{"type":49,"value":1290},{"type":49,"value":2663}," the fallback path using the fourth-argument ",{"type":43,"tag":146,"props":2665,"children":2667},{"className":2666},[],[2668],{"type":49,"value":2669},"variables",{"type":49,"value":2671}," dict to ",{"type":43,"tag":146,"props":2673,"children":2675},{"className":2674},[],[2676],{"type":49,"value":2677},"completion_config(...)",{"type":49,"value":600},{"type":43,"tag":146,"props":2680,"children":2682},{"className":2681},[],[2683],{"type":49,"value":2684},"completionConfig(...)",{"type":49,"value":2686},". Leaving a Python-style ",{"type":43,"tag":146,"props":2688,"children":2690},{"className":2689},[],[2691],{"type":49,"value":2614},{"type":49,"value":2693}," literal in the fallback ships a silent regression when LaunchDarkly is unreachable — the renderer won't match the single-brace form and the literal ",{"type":43,"tag":146,"props":2695,"children":2697},{"className":2696},[],[2698],{"type":49,"value":2614},{"type":49,"value":2700}," goes to the provider as part of the prompt.",{"type":43,"tag":1703,"props":2702,"children":2703},{},[],{"type":43,"tag":58,"props":2705,"children":2706},{},[2707],{"type":49,"value":2708},"Before:",{"type":43,"tag":1451,"props":2710,"children":2712},{"className":1880,"code":2711,"language":1882,"meta":1459,"style":1459},"SYSTEM_PROMPT = \"You are a helpful assistant. The time is {system_time}.\"\nprompt = SYSTEM_PROMPT.format(system_time=datetime.now().isoformat())\n",[2713],{"type":43,"tag":146,"props":2714,"children":2715},{"__ignoreMap":1459},[2716,2724],{"type":43,"tag":1888,"props":2717,"children":2718},{"class":1890,"line":1891},[2719],{"type":43,"tag":1888,"props":2720,"children":2721},{},[2722],{"type":49,"value":2723},"SYSTEM_PROMPT = \"You are a helpful assistant. The time is {system_time}.\"\n",{"type":43,"tag":1888,"props":2725,"children":2726},{"class":1890,"line":1900},[2727],{"type":43,"tag":1888,"props":2728,"children":2729},{},[2730],{"type":49,"value":2731},"prompt = SYSTEM_PROMPT.format(system_time=datetime.now().isoformat())\n",{"type":43,"tag":1703,"props":2733,"children":2734},{},[],{"type":43,"tag":58,"props":2736,"children":2737},{},[2738],{"type":49,"value":2739},"After (in source):",{"type":43,"tag":1451,"props":2741,"children":2743},{"className":1880,"code":2742,"language":1882,"meta":1459,"style":1459},"SYSTEM_PROMPT = \"You are a helpful assistant. The time is {{ system_time }}.\"\n# .format() is removed at the call site — the SDK interpolates via `variables`\nconfig = ai_client.completion_config(\n    CONFIG_KEY,\n    context,\n    fallback,\n    variables={\"system_time\": datetime.now().isoformat()},\n)\n",[2744],{"type":43,"tag":146,"props":2745,"children":2746},{"__ignoreMap":1459},[2747,2755,2763,2771,2779,2787,2795,2803],{"type":43,"tag":1888,"props":2748,"children":2749},{"class":1890,"line":1891},[2750],{"type":43,"tag":1888,"props":2751,"children":2752},{},[2753],{"type":49,"value":2754},"SYSTEM_PROMPT = \"You are a helpful assistant. The time is {{ system_time }}.\"\n",{"type":43,"tag":1888,"props":2756,"children":2757},{"class":1890,"line":1900},[2758],{"type":43,"tag":1888,"props":2759,"children":2760},{},[2761],{"type":49,"value":2762},"# .format() is removed at the call site — the SDK interpolates via `variables`\n",{"type":43,"tag":1888,"props":2764,"children":2765},{"class":1890,"line":1909},[2766],{"type":43,"tag":1888,"props":2767,"children":2768},{},[2769],{"type":49,"value":2770},"config = ai_client.completion_config(\n",{"type":43,"tag":1888,"props":2772,"children":2773},{"class":1890,"line":1918},[2774],{"type":43,"tag":1888,"props":2775,"children":2776},{},[2777],{"type":49,"value":2778},"    CONFIG_KEY,\n",{"type":43,"tag":1888,"props":2780,"children":2781},{"class":1890,"line":1927},[2782],{"type":43,"tag":1888,"props":2783,"children":2784},{},[2785],{"type":49,"value":2786},"    context,\n",{"type":43,"tag":1888,"props":2788,"children":2789},{"class":1890,"line":24},[2790],{"type":43,"tag":1888,"props":2791,"children":2792},{},[2793],{"type":49,"value":2794},"    fallback,\n",{"type":43,"tag":1888,"props":2796,"children":2797},{"class":1890,"line":1945},[2798],{"type":43,"tag":1888,"props":2799,"children":2800},{},[2801],{"type":49,"value":2802},"    variables={\"system_time\": datetime.now().isoformat()},\n",{"type":43,"tag":1888,"props":2804,"children":2805},{"class":1890,"line":1954},[2806],{"type":43,"tag":1888,"props":2807,"children":2808},{},[2809],{"type":49,"value":2810},")\n",{"type":43,"tag":1703,"props":2812,"children":2813},{},[],{"type":49,"value":2815},"Common shapes to rewrite:",{"type":43,"tag":798,"props":2817,"children":2818},{},[2819,2852,2870],{"type":43,"tag":70,"props":2820,"children":2821},{},[2822,2824,2830,2831,2837,2838,2844,2846],{"type":49,"value":2823},"Python ",{"type":43,"tag":146,"props":2825,"children":2827},{"className":2826},[],[2828],{"type":49,"value":2829},"\"{var}\"",{"type":49,"value":600},{"type":43,"tag":146,"props":2832,"children":2834},{"className":2833},[],[2835],{"type":49,"value":2836},"\"{var!s}\"",{"type":49,"value":600},{"type":43,"tag":146,"props":2839,"children":2841},{"className":2840},[],[2842],{"type":49,"value":2843},"\"%(var)s\"",{"type":49,"value":2845}," → ",{"type":43,"tag":146,"props":2847,"children":2849},{"className":2848},[],[2850],{"type":49,"value":2851},"\"{{ var }}\"",{"type":43,"tag":70,"props":2853,"children":2854},{},[2855,2857,2863,2865],{"type":49,"value":2856},"JS\u002FTS ",{"type":43,"tag":146,"props":2858,"children":2860},{"className":2859},[],[2861],{"type":49,"value":2862},"`${var}`",{"type":49,"value":2864}," template literals inside prompt strings → ",{"type":43,"tag":146,"props":2866,"children":2868},{"className":2867},[],[2869],{"type":49,"value":2851},{"type":43,"tag":70,"props":2871,"children":2872},{},[2873,2875,2881,2883],{"type":49,"value":2874},"Any hand-rolled ",{"type":43,"tag":146,"props":2876,"children":2878},{"className":2877},[],[2879],{"type":49,"value":2880},"str.replace(\"__VAR__\", value)",{"type":49,"value":2882}," scheme → ",{"type":43,"tag":146,"props":2884,"children":2886},{"className":2885},[],[2887],{"type":49,"value":2851},{"type":43,"tag":1703,"props":2889,"children":2890},{},[],{"type":49,"value":2892},"See ",{"type":43,"tag":179,"props":2894,"children":2896},{"href":2895},"references\u002Ffallback-defaults-pattern.md",[2897],{"type":49,"value":2898},"fallback-defaults-pattern.md § Template placeholders",{"type":49,"value":2900}," for the fallback-specific variant.",{"type":43,"tag":70,"props":2902,"children":2903},{},[2904,2909,2911,2917,2918,2924,2926,2931,2933,2936,2941],{"type":43,"tag":58,"props":2905,"children":2906},{},[2907],{"type":49,"value":2908},"Build the fallback.",{"type":49,"value":2910}," Mirror the hardcoded values you extracted. Use ",{"type":43,"tag":146,"props":2912,"children":2914},{"className":2913},[],[2915],{"type":49,"value":2916},"AICompletionConfigDefault",{"type":49,"value":600},{"type":43,"tag":146,"props":2919,"children":2921},{"className":2920},[],[2922],{"type":49,"value":2923},"AIAgentConfigDefault",{"type":49,"value":2925}," in Python, plain object literals in Node. See ",{"type":43,"tag":179,"props":2927,"children":2928},{"href":2895},[2929],{"type":49,"value":2930},"fallback-defaults-pattern.md",{"type":49,"value":2932}," for inline, file-backed, and bootstrap-generated patterns.",{"type":43,"tag":1703,"props":2934,"children":2935},{},[],{"type":43,"tag":58,"props":2937,"children":2938},{},[2939],{"type":49,"value":2940},"Python fallback (completion mode):",{"type":43,"tag":1451,"props":2942,"children":2944},{"className":1880,"code":2943,"language":1882,"meta":1459,"style":1459},"from ldai.client import AICompletionConfigDefault, ModelConfig, ProviderConfig, LDMessage\n\nfallback = AICompletionConfigDefault(\n    enabled=True,\n    model=ModelConfig(name=\"gpt-4o\", parameters={\"temperature\": 0.7, \"max_tokens\": 2000}),\n    provider=ProviderConfig(name=\"openai\"),\n    messages=[LDMessage(role=\"system\", content=\"You are a helpful assistant...\")],\n)\n",[2945],{"type":43,"tag":146,"props":2946,"children":2947},{"__ignoreMap":1459},[2948,2956,2963,2971,2979,2987,2995,3003],{"type":43,"tag":1888,"props":2949,"children":2950},{"class":1890,"line":1891},[2951],{"type":43,"tag":1888,"props":2952,"children":2953},{},[2954],{"type":49,"value":2955},"from ldai.client import AICompletionConfigDefault, ModelConfig, ProviderConfig, LDMessage\n",{"type":43,"tag":1888,"props":2957,"children":2958},{"class":1890,"line":1900},[2959],{"type":43,"tag":1888,"props":2960,"children":2961},{"emptyLinePlaceholder":1931},[2962],{"type":49,"value":1934},{"type":43,"tag":1888,"props":2964,"children":2965},{"class":1890,"line":1909},[2966],{"type":43,"tag":1888,"props":2967,"children":2968},{},[2969],{"type":49,"value":2970},"fallback = AICompletionConfigDefault(\n",{"type":43,"tag":1888,"props":2972,"children":2973},{"class":1890,"line":1918},[2974],{"type":43,"tag":1888,"props":2975,"children":2976},{},[2977],{"type":49,"value":2978},"    enabled=True,\n",{"type":43,"tag":1888,"props":2980,"children":2981},{"class":1890,"line":1927},[2982],{"type":43,"tag":1888,"props":2983,"children":2984},{},[2985],{"type":49,"value":2986},"    model=ModelConfig(name=\"gpt-4o\", parameters={\"temperature\": 0.7, \"max_tokens\": 2000}),\n",{"type":43,"tag":1888,"props":2988,"children":2989},{"class":1890,"line":24},[2990],{"type":43,"tag":1888,"props":2991,"children":2992},{},[2993],{"type":49,"value":2994},"    provider=ProviderConfig(name=\"openai\"),\n",{"type":43,"tag":1888,"props":2996,"children":2997},{"class":1890,"line":1945},[2998],{"type":43,"tag":1888,"props":2999,"children":3000},{},[3001],{"type":49,"value":3002},"    messages=[LDMessage(role=\"system\", content=\"You are a helpful assistant...\")],\n",{"type":43,"tag":1888,"props":3004,"children":3005},{"class":1890,"line":1954},[3006],{"type":43,"tag":1888,"props":3007,"children":3008},{},[3009],{"type":49,"value":2810},{"type":43,"tag":70,"props":3011,"children":3012},{},[3013,3018,3020,3025,3026,3032,3034,3039,3040,3046,3048,3051,3056,3134,3137,3142,3263,3266,3271,3273,3442,3445,3446,3450],{"type":43,"tag":58,"props":3014,"children":3015},{},[3016],{"type":49,"value":3017},"Replace the hardcoded call site.",{"type":49,"value":3019}," Swap the hardcoded model\u002Fprompt\u002Fparams for a ",{"type":43,"tag":146,"props":3021,"children":3023},{"className":3022},[],[3024],{"type":49,"value":1030},{"type":49,"value":600},{"type":43,"tag":146,"props":3027,"children":3029},{"className":3028},[],[3030],{"type":49,"value":3031},"completionConfig",{"type":49,"value":3033}," (or ",{"type":43,"tag":146,"props":3035,"children":3037},{"className":3036},[],[3038],{"type":49,"value":775},{"type":49,"value":600},{"type":43,"tag":146,"props":3041,"children":3043},{"className":3042},[],[3044],{"type":49,"value":3045},"agentConfig",{"type":49,"value":3047},") call, then read the returned fields into the existing provider call. Keep the provider call intact.",{"type":43,"tag":1703,"props":3049,"children":3050},{},[],{"type":43,"tag":58,"props":3052,"children":3053},{},[3054],{"type":49,"value":3055},"Python — before:",{"type":43,"tag":1451,"props":3057,"children":3059},{"className":1880,"code":3058,"language":1882,"meta":1459,"style":1459},"response = openai_client.chat.completions.create(\n    model=\"gpt-4o\",\n    temperature=0.7,\n    max_tokens=2000,\n    messages=[\n        {\"role\": \"system\", \"content\": \"You are a helpful assistant...\"},\n        {\"role\": \"user\", \"content\": user_input},\n    ],\n)\n",[3060],{"type":43,"tag":146,"props":3061,"children":3062},{"__ignoreMap":1459},[3063,3071,3079,3087,3095,3103,3111,3119,3127],{"type":43,"tag":1888,"props":3064,"children":3065},{"class":1890,"line":1891},[3066],{"type":43,"tag":1888,"props":3067,"children":3068},{},[3069],{"type":49,"value":3070},"response = openai_client.chat.completions.create(\n",{"type":43,"tag":1888,"props":3072,"children":3073},{"class":1890,"line":1900},[3074],{"type":43,"tag":1888,"props":3075,"children":3076},{},[3077],{"type":49,"value":3078},"    model=\"gpt-4o\",\n",{"type":43,"tag":1888,"props":3080,"children":3081},{"class":1890,"line":1909},[3082],{"type":43,"tag":1888,"props":3083,"children":3084},{},[3085],{"type":49,"value":3086},"    temperature=0.7,\n",{"type":43,"tag":1888,"props":3088,"children":3089},{"class":1890,"line":1918},[3090],{"type":43,"tag":1888,"props":3091,"children":3092},{},[3093],{"type":49,"value":3094},"    max_tokens=2000,\n",{"type":43,"tag":1888,"props":3096,"children":3097},{"class":1890,"line":1927},[3098],{"type":43,"tag":1888,"props":3099,"children":3100},{},[3101],{"type":49,"value":3102},"    messages=[\n",{"type":43,"tag":1888,"props":3104,"children":3105},{"class":1890,"line":24},[3106],{"type":43,"tag":1888,"props":3107,"children":3108},{},[3109],{"type":49,"value":3110},"        {\"role\": \"system\", \"content\": \"You are a helpful assistant...\"},\n",{"type":43,"tag":1888,"props":3112,"children":3113},{"class":1890,"line":1945},[3114],{"type":43,"tag":1888,"props":3115,"children":3116},{},[3117],{"type":49,"value":3118},"        {\"role\": \"user\", \"content\": user_input},\n",{"type":43,"tag":1888,"props":3120,"children":3121},{"class":1890,"line":1954},[3122],{"type":43,"tag":1888,"props":3123,"children":3124},{},[3125],{"type":49,"value":3126},"    ],\n",{"type":43,"tag":1888,"props":3128,"children":3129},{"class":1890,"line":1963},[3130],{"type":43,"tag":1888,"props":3131,"children":3132},{},[3133],{"type":49,"value":2810},{"type":43,"tag":1703,"props":3135,"children":3136},{},[],{"type":43,"tag":58,"props":3138,"children":3139},{},[3140],{"type":49,"value":3141},"Python — after:",{"type":43,"tag":1451,"props":3143,"children":3145},{"className":1880,"code":3144,"language":1882,"meta":1459,"style":1459},"context = Context.builder(user_id).set(\"email\", user.email).build()\nconfig = ai_client.completion_config(\"chat-assistant\", context, fallback)\n\nif not config.enabled:\n    return disabled_response()\n\nparams = config.model.parameters or {}\nresponse = openai_client.chat.completions.create(\n    model=config.model.name,\n    temperature=params.get(\"temperature\"),\n    max_tokens=params.get(\"max_tokens\"),\n    messages=[m.to_dict() for m in (config.messages or [])] + [\n        {\"role\": \"user\", \"content\": user_input},\n    ],\n)\n",[3146],{"type":43,"tag":146,"props":3147,"children":3148},{"__ignoreMap":1459},[3149,3157,3165,3172,3180,3188,3195,3203,3210,3218,3226,3234,3242,3249,3256],{"type":43,"tag":1888,"props":3150,"children":3151},{"class":1890,"line":1891},[3152],{"type":43,"tag":1888,"props":3153,"children":3154},{},[3155],{"type":49,"value":3156},"context = Context.builder(user_id).set(\"email\", user.email).build()\n",{"type":43,"tag":1888,"props":3158,"children":3159},{"class":1890,"line":1900},[3160],{"type":43,"tag":1888,"props":3161,"children":3162},{},[3163],{"type":49,"value":3164},"config = ai_client.completion_config(\"chat-assistant\", context, fallback)\n",{"type":43,"tag":1888,"props":3166,"children":3167},{"class":1890,"line":1909},[3168],{"type":43,"tag":1888,"props":3169,"children":3170},{"emptyLinePlaceholder":1931},[3171],{"type":49,"value":1934},{"type":43,"tag":1888,"props":3173,"children":3174},{"class":1890,"line":1918},[3175],{"type":43,"tag":1888,"props":3176,"children":3177},{},[3178],{"type":49,"value":3179},"if not config.enabled:\n",{"type":43,"tag":1888,"props":3181,"children":3182},{"class":1890,"line":1927},[3183],{"type":43,"tag":1888,"props":3184,"children":3185},{},[3186],{"type":49,"value":3187},"    return disabled_response()\n",{"type":43,"tag":1888,"props":3189,"children":3190},{"class":1890,"line":24},[3191],{"type":43,"tag":1888,"props":3192,"children":3193},{"emptyLinePlaceholder":1931},[3194],{"type":49,"value":1934},{"type":43,"tag":1888,"props":3196,"children":3197},{"class":1890,"line":1945},[3198],{"type":43,"tag":1888,"props":3199,"children":3200},{},[3201],{"type":49,"value":3202},"params = config.model.parameters or {}\n",{"type":43,"tag":1888,"props":3204,"children":3205},{"class":1890,"line":1954},[3206],{"type":43,"tag":1888,"props":3207,"children":3208},{},[3209],{"type":49,"value":3070},{"type":43,"tag":1888,"props":3211,"children":3212},{"class":1890,"line":1963},[3213],{"type":43,"tag":1888,"props":3214,"children":3215},{},[3216],{"type":49,"value":3217},"    model=config.model.name,\n",{"type":43,"tag":1888,"props":3219,"children":3220},{"class":1890,"line":1972},[3221],{"type":43,"tag":1888,"props":3222,"children":3223},{},[3224],{"type":49,"value":3225},"    temperature=params.get(\"temperature\"),\n",{"type":43,"tag":1888,"props":3227,"children":3228},{"class":1890,"line":1981},[3229],{"type":43,"tag":1888,"props":3230,"children":3231},{},[3232],{"type":49,"value":3233},"    max_tokens=params.get(\"max_tokens\"),\n",{"type":43,"tag":1888,"props":3235,"children":3236},{"class":1890,"line":1990},[3237],{"type":43,"tag":1888,"props":3238,"children":3239},{},[3240],{"type":49,"value":3241},"    messages=[m.to_dict() for m in (config.messages or [])] + [\n",{"type":43,"tag":1888,"props":3243,"children":3244},{"class":1890,"line":1999},[3245],{"type":43,"tag":1888,"props":3246,"children":3247},{},[3248],{"type":49,"value":3118},{"type":43,"tag":1888,"props":3250,"children":3251},{"class":1890,"line":2008},[3252],{"type":43,"tag":1888,"props":3253,"children":3254},{},[3255],{"type":49,"value":3126},{"type":43,"tag":1888,"props":3257,"children":3258},{"class":1890,"line":2017},[3259],{"type":43,"tag":1888,"props":3260,"children":3261},{},[3262],{"type":49,"value":2810},{"type":43,"tag":1703,"props":3264,"children":3265},{},[],{"type":43,"tag":58,"props":3267,"children":3268},{},[3269],{"type":49,"value":3270},"Python — after (agent mode)",{"type":49,"value":3272}," — for LangGraph, CrewAI, or any framework that takes a goal\u002Finstructions string:",{"type":43,"tag":1451,"props":3274,"children":3276},{"className":1880,"code":3275,"language":1882,"meta":1459,"style":1459},"context = Context.builder(user_id).kind(\"user\").build()\nconfig = ai_client.agent_config(\"support-agent\", context, FALLBACK)\n\nif not config.enabled:\n    return disabled_response()\n\n# config is a single AIAgentConfig object — NOT a (config, tracker) tuple.\n# Obtain the tracker once per execution via the factory: tracker = config.create_tracker()\nmodel_name = f\"{config.provider.name}\u002F{config.model.name}\"\ninstructions = config.instructions\nparams = config.model.parameters or {}\n\n# Pass model_name + instructions into your framework's agent constructor.\n# Example: LangGraph prebuilt agent (Python — `from langchain.agents import create_agent`;\n# this replaces `langgraph.prebuilt.create_react_agent`, deprecated in LangGraph 1.0\n# and removed in 2.0. Same return shape; `prompt=` was renamed to `system_prompt=`.)\n# agent = create_agent(\n#     create_langchain_model(config),  # forwards every variation parameter\n#     TOOLS,                            # Stage 3 will replace this with a config.tools loader\n#     system_prompt=instructions,\n# )\n",[3277],{"type":43,"tag":146,"props":3278,"children":3279},{"__ignoreMap":1459},[3280,3288,3296,3303,3310,3317,3324,3332,3340,3348,3356,3363,3370,3378,3386,3394,3402,3410,3418,3426,3434],{"type":43,"tag":1888,"props":3281,"children":3282},{"class":1890,"line":1891},[3283],{"type":43,"tag":1888,"props":3284,"children":3285},{},[3286],{"type":49,"value":3287},"context = Context.builder(user_id).kind(\"user\").build()\n",{"type":43,"tag":1888,"props":3289,"children":3290},{"class":1890,"line":1900},[3291],{"type":43,"tag":1888,"props":3292,"children":3293},{},[3294],{"type":49,"value":3295},"config = ai_client.agent_config(\"support-agent\", context, FALLBACK)\n",{"type":43,"tag":1888,"props":3297,"children":3298},{"class":1890,"line":1909},[3299],{"type":43,"tag":1888,"props":3300,"children":3301},{"emptyLinePlaceholder":1931},[3302],{"type":49,"value":1934},{"type":43,"tag":1888,"props":3304,"children":3305},{"class":1890,"line":1918},[3306],{"type":43,"tag":1888,"props":3307,"children":3308},{},[3309],{"type":49,"value":3179},{"type":43,"tag":1888,"props":3311,"children":3312},{"class":1890,"line":1927},[3313],{"type":43,"tag":1888,"props":3314,"children":3315},{},[3316],{"type":49,"value":3187},{"type":43,"tag":1888,"props":3318,"children":3319},{"class":1890,"line":24},[3320],{"type":43,"tag":1888,"props":3321,"children":3322},{"emptyLinePlaceholder":1931},[3323],{"type":49,"value":1934},{"type":43,"tag":1888,"props":3325,"children":3326},{"class":1890,"line":1945},[3327],{"type":43,"tag":1888,"props":3328,"children":3329},{},[3330],{"type":49,"value":3331},"# config is a single AIAgentConfig object — NOT a (config, tracker) tuple.\n",{"type":43,"tag":1888,"props":3333,"children":3334},{"class":1890,"line":1954},[3335],{"type":43,"tag":1888,"props":3336,"children":3337},{},[3338],{"type":49,"value":3339},"# Obtain the tracker once per execution via the factory: tracker = config.create_tracker()\n",{"type":43,"tag":1888,"props":3341,"children":3342},{"class":1890,"line":1963},[3343],{"type":43,"tag":1888,"props":3344,"children":3345},{},[3346],{"type":49,"value":3347},"model_name = f\"{config.provider.name}\u002F{config.model.name}\"\n",{"type":43,"tag":1888,"props":3349,"children":3350},{"class":1890,"line":1972},[3351],{"type":43,"tag":1888,"props":3352,"children":3353},{},[3354],{"type":49,"value":3355},"instructions = config.instructions\n",{"type":43,"tag":1888,"props":3357,"children":3358},{"class":1890,"line":1981},[3359],{"type":43,"tag":1888,"props":3360,"children":3361},{},[3362],{"type":49,"value":3202},{"type":43,"tag":1888,"props":3364,"children":3365},{"class":1890,"line":1990},[3366],{"type":43,"tag":1888,"props":3367,"children":3368},{"emptyLinePlaceholder":1931},[3369],{"type":49,"value":1934},{"type":43,"tag":1888,"props":3371,"children":3372},{"class":1890,"line":1999},[3373],{"type":43,"tag":1888,"props":3374,"children":3375},{},[3376],{"type":49,"value":3377},"# Pass model_name + instructions into your framework's agent constructor.\n",{"type":43,"tag":1888,"props":3379,"children":3380},{"class":1890,"line":2008},[3381],{"type":43,"tag":1888,"props":3382,"children":3383},{},[3384],{"type":49,"value":3385},"# Example: LangGraph prebuilt agent (Python — `from langchain.agents import create_agent`;\n",{"type":43,"tag":1888,"props":3387,"children":3388},{"class":1890,"line":2017},[3389],{"type":43,"tag":1888,"props":3390,"children":3391},{},[3392],{"type":49,"value":3393},"# this replaces `langgraph.prebuilt.create_react_agent`, deprecated in LangGraph 1.0\n",{"type":43,"tag":1888,"props":3395,"children":3396},{"class":1890,"line":2026},[3397],{"type":43,"tag":1888,"props":3398,"children":3399},{},[3400],{"type":49,"value":3401},"# and removed in 2.0. Same return shape; `prompt=` was renamed to `system_prompt=`.)\n",{"type":43,"tag":1888,"props":3403,"children":3404},{"class":1890,"line":2035},[3405],{"type":43,"tag":1888,"props":3406,"children":3407},{},[3408],{"type":49,"value":3409},"# agent = create_agent(\n",{"type":43,"tag":1888,"props":3411,"children":3412},{"class":1890,"line":2044},[3413],{"type":43,"tag":1888,"props":3414,"children":3415},{},[3416],{"type":49,"value":3417},"#     create_langchain_model(config),  # forwards every variation parameter\n",{"type":43,"tag":1888,"props":3419,"children":3420},{"class":1890,"line":2053},[3421],{"type":43,"tag":1888,"props":3422,"children":3423},{},[3424],{"type":49,"value":3425},"#     TOOLS,                            # Stage 3 will replace this with a config.tools loader\n",{"type":43,"tag":1888,"props":3427,"children":3428},{"class":1890,"line":20},[3429],{"type":43,"tag":1888,"props":3430,"children":3431},{},[3432],{"type":49,"value":3433},"#     system_prompt=instructions,\n",{"type":43,"tag":1888,"props":3435,"children":3436},{"class":1890,"line":2069},[3437],{"type":43,"tag":1888,"props":3438,"children":3439},{},[3440],{"type":49,"value":3441},"# )\n",{"type":43,"tag":1703,"props":3443,"children":3444},{},[],{"type":49,"value":2892},{"type":43,"tag":179,"props":3447,"children":3448},{"href":352},[3449],{"type":49,"value":355},{"type":49,"value":3451}," for full Python OpenAI, Node Anthropic, and LangGraph agent-mode paired snippets.",{"type":43,"tag":70,"props":3453,"children":3454},{},[3455,3467,3469,3475],{"type":43,"tag":58,"props":3456,"children":3457},{},[3458,3460,3466],{"type":49,"value":3459},"Check ",{"type":43,"tag":146,"props":3461,"children":3463},{"className":3462},[],[3464],{"type":49,"value":3465},"config.enabled",{"type":49,"value":192},{"type":49,"value":3468}," If it returns ",{"type":43,"tag":146,"props":3470,"children":3472},{"className":3471},[],[3473],{"type":49,"value":3474},"False",{"type":49,"value":3476},", handle the disabled path without crashing and without calling the provider. The check is required — not optional.",{"type":43,"tag":70,"props":3478,"children":3479},{},[3480,3485,3487,3492,3494,3500],{"type":43,"tag":58,"props":3481,"children":3482},{},[3483],{"type":49,"value":3484},"Verify.",{"type":49,"value":3486}," Run the app with a valid ",{"type":43,"tag":146,"props":3488,"children":3490},{"className":3489},[],[3491],{"type":49,"value":809},{"type":49,"value":3493},"; confirm the call succeeds and the response matches pre-migration output. Then temporarily set ",{"type":43,"tag":146,"props":3495,"children":3497},{"className":3496},[],[3498],{"type":49,"value":3499},"LD_SDK_KEY=sdk-invalid",{"type":49,"value":3501}," (or unset it) and confirm the fallback path runs without error. Both paths must work before moving to Stage 3.",{"type":43,"tag":52,"props":3503,"children":3504},{},[3505,3507,3515],{"type":49,"value":3506},"Delegate: ",{"type":43,"tag":58,"props":3508,"children":3509},{},[3510],{"type":43,"tag":146,"props":3511,"children":3513},{"className":3512},[],[3514],{"type":49,"value":942},{"type":49,"value":3516}," (sub-step 4).",{"type":43,"tag":1100,"props":3518,"children":3520},{"id":3519},"step-3-move-tools-into-the-config-stage-3",[3521],{"type":49,"value":3522},"Step 3: Move tools into the config (Stage 3)",{"type":43,"tag":52,"props":3524,"children":3525},{},[3526],{"type":49,"value":3527},"Skip this step if the audited app has no function calling \u002F tools. Otherwise:",{"type":43,"tag":66,"props":3529,"children":3530},{},[3531,3754,3798,3867],{"type":43,"tag":70,"props":3532,"children":3533},{},[3534,3539,3541,3713,3716,3718,3721,3723,3728,3730,3736,3738,3744,3746,3752],{"type":43,"tag":58,"props":3535,"children":3536},{},[3537],{"type":49,"value":3538},"Enumerate the tools currently registered.",{"type":49,"value":3540}," Common shapes to look for:",{"type":43,"tag":798,"props":3542,"children":3543},{},[3544,3555,3566,3592,3610,3621,3639],{"type":43,"tag":70,"props":3545,"children":3546},{},[3547,3553],{"type":43,"tag":146,"props":3548,"children":3550},{"className":3549},[],[3551],{"type":49,"value":3552},"openai.chat.completions.create(tools=[...])",{"type":49,"value":3554}," — OpenAI direct",{"type":43,"tag":70,"props":3556,"children":3557},{},[3558,3564],{"type":43,"tag":146,"props":3559,"children":3561},{"className":3560},[],[3562],{"type":49,"value":3563},"anthropic.messages.create(tools=[...])",{"type":49,"value":3565}," — Anthropic direct",{"type":43,"tag":70,"props":3567,"children":3568},{},[3569,3575,3577,3583,3585,3591],{"type":43,"tag":146,"props":3570,"children":3572},{"className":3571},[],[3573],{"type":49,"value":3574},"create_agent(llm, tools=[...], system_prompt=...)",{"type":49,"value":3576}," — LangGraph prebuilt (Python, ",{"type":43,"tag":146,"props":3578,"children":3580},{"className":3579},[],[3581],{"type":49,"value":3582},"langchain.agents",{"type":49,"value":3584},"; replaces deprecated ",{"type":43,"tag":146,"props":3586,"children":3588},{"className":3587},[],[3589],{"type":49,"value":3590},"langgraph.prebuilt.create_react_agent",{"type":49,"value":379},{"type":43,"tag":70,"props":3593,"children":3594},{},[3595,3601,3603,3609],{"type":43,"tag":146,"props":3596,"children":3598},{"className":3597},[],[3599],{"type":49,"value":3600},"createReactAgent({ llm, tools: [...] })",{"type":49,"value":3602}," — LangGraph.js prebuilt (Node, ",{"type":43,"tag":146,"props":3604,"children":3606},{"className":3605},[],[3607],{"type":49,"value":3608},"@langchain\u002Flanggraph\u002Fprebuilt",{"type":49,"value":379},{"type":43,"tag":70,"props":3611,"children":3612},{},[3613,3619],{"type":43,"tag":146,"props":3614,"children":3616},{"className":3615},[],[3617],{"type":49,"value":3618},"Agent(tools=[...])",{"type":49,"value":3620}," — CrewAI",{"type":43,"tag":70,"props":3622,"children":3623},{},[3624,3629,3631,3637],{"type":43,"tag":146,"props":3625,"children":3627},{"className":3626},[],[3628],{"type":49,"value":3618},{"type":49,"value":3630}," — Strands (Python ",{"type":43,"tag":146,"props":3632,"children":3634},{"className":3633},[],[3635],{"type":49,"value":3636},"@tool",{"type":49,"value":3638},"-decorated callables passed through the constructor; TS SDK uses Zod-schema tools)",{"type":43,"tag":70,"props":3640,"children":3641},{},[3642,3652,3654,3660,3662,3666,3667,3673,3675,3681,3683,3688,3690,3696,3698,3704,3705,3711],{"type":43,"tag":58,"props":3643,"children":3644},{},[3645,3647],{"type":49,"value":3646},"Custom ",{"type":43,"tag":146,"props":3648,"children":3650},{"className":3649},[],[3651],{"type":49,"value":190},{"type":49,"value":3653}," — module-level ",{"type":43,"tag":146,"props":3655,"children":3657},{"className":3656},[],[3658],{"type":49,"value":3659},"TOOLS = [...]",{"type":49,"value":3661}," list referenced in ",{"type":43,"tag":58,"props":3663,"children":3664},{},[3665],{"type":49,"value":2642},{"type":49,"value":2651},{"type":43,"tag":146,"props":3668,"children":3670},{"className":3669},[],[3671],{"type":49,"value":3672},"model.bind_tools(TOOLS)",{"type":49,"value":3674}," and ",{"type":43,"tag":146,"props":3676,"children":3678},{"className":3677},[],[3679],{"type":49,"value":3680},"ToolNode(TOOLS)",{"type":49,"value":3682},". This is the ",{"type":43,"tag":146,"props":3684,"children":3686},{"className":3685},[],[3687],{"type":49,"value":214},{"type":49,"value":3689}," template shape; the list is usually in a ",{"type":43,"tag":146,"props":3691,"children":3693},{"className":3692},[],[3694],{"type":49,"value":3695},"tools.py",{"type":49,"value":3697}," module. Grep for ",{"type":43,"tag":146,"props":3699,"children":3701},{"className":3700},[],[3702],{"type":49,"value":3703},"bind_tools(",{"type":49,"value":3674},{"type":43,"tag":146,"props":3706,"children":3708},{"className":3707},[],[3709],{"type":49,"value":3710},"ToolNode(",{"type":49,"value":3712}," together — they will point at the same list.",{"type":43,"tag":1703,"props":3714,"children":3715},{},[],{"type":49,"value":3717},"Record each tool's name, description, and JSON schema.",{"type":43,"tag":1703,"props":3719,"children":3720},{},[],{"type":49,"value":3722},"For LangChain\u002FLangGraph tools defined with ",{"type":43,"tag":146,"props":3724,"children":3726},{"className":3725},[],[3727],{"type":49,"value":3636},{"type":49,"value":3729},", extract the schema via ",{"type":43,"tag":146,"props":3731,"children":3733},{"className":3732},[],[3734],{"type":49,"value":3735},"tool.args_schema.model_json_schema()",{"type":49,"value":3737}," (or the equivalent Pydantic ",{"type":43,"tag":146,"props":3739,"children":3741},{"className":3740},[],[3742],{"type":49,"value":3743},"model_json_schema()",{"type":49,"value":3745}," call). For plain async callables used as tools (common in custom StateGraph shapes), LangChain infers the schema from the function signature at bind time — extract it via ",{"type":43,"tag":146,"props":3747,"children":3749},{"className":3748},[],[3750],{"type":49,"value":3751},"StructuredTool.from_function(fn).args_schema.model_json_schema()",{"type":49,"value":3753},". Do not hand-write the schema.",{"type":43,"tag":70,"props":3755,"children":3756},{},[3757,3767,3769,3780,3782,3788,3790,3796],{"type":43,"tag":58,"props":3758,"children":3759},{},[3760,3761,3766],{"type":49,"value":2530},{"type":43,"tag":146,"props":3762,"children":3764},{"className":3763},[],[3765],{"type":49,"value":953},{"type":49,"value":192},{"type":49,"value":3768}," Print the extracted tool names, descriptions, and schemas, then tell the user: ",{"type":43,"tag":1008,"props":3770,"children":3771},{},[3772,3773,3778],{"type":49,"value":2543},{"type":43,"tag":146,"props":3774,"children":3776},{"className":3775},[],[3777],{"type":49,"value":1071},{"type":49,"value":3779}," with these tools and the variation key, then come back here.\"",{"type":49,"value":3781}," The sibling skill creates tool definitions (",{"type":43,"tag":146,"props":3783,"children":3785},{"className":3784},[],[3786],{"type":49,"value":3787},"create-ai-tool",{"type":49,"value":3789},") and attaches them to the variation (",{"type":43,"tag":146,"props":3791,"children":3793},{"className":3792},[],[3794],{"type":49,"value":3795},"update-ai-config-variation",{"type":49,"value":3797},"). Wait for the user to finish before proceeding to sub-step 3. Do not auto-invoke.",{"type":43,"tag":70,"props":3799,"children":3800},{},[3801,3806,3808,3814,3816,3821,3823,3826,3838,3840,3844,3846,3852,3853,3858,3860,3865],{"type":43,"tag":58,"props":3802,"children":3803},{},[3804],{"type":49,"value":3805},"Replace the hardcoded tools array at the call site",{"type":49,"value":3807}," with a read from ",{"type":43,"tag":146,"props":3809,"children":3811},{"className":3810},[],[3812],{"type":49,"value":3813},"config.tools",{"type":49,"value":3815}," (or the SDK equivalent for your language). Load the actual implementation functions dynamically from the tool names — see ",{"type":43,"tag":179,"props":3817,"children":3818},{"href":181},[3819],{"type":49,"value":3820},"agent-mode-frameworks.md",{"type":49,"value":3822}," for the dynamic-tool-factory pattern from the devrel agents tutorial.",{"type":43,"tag":1703,"props":3824,"children":3825},{},[],{"type":43,"tag":58,"props":3827,"children":3828},{},[3829,3831,3836],{"type":49,"value":3830},"For custom ",{"type":43,"tag":146,"props":3832,"children":3834},{"className":3833},[],[3835],{"type":49,"value":190},{"type":49,"value":3837}," shapes",{"type":49,"value":3839},", you must update ",{"type":43,"tag":58,"props":3841,"children":3842},{},[3843],{"type":49,"value":2642},{"type":49,"value":3845}," call sites: ",{"type":43,"tag":146,"props":3847,"children":3849},{"className":3848},[],[3850],{"type":49,"value":3851},".bind_tools(TOOLS)",{"type":49,"value":3674},{"type":43,"tag":146,"props":3854,"children":3856},{"className":3855},[],[3857],{"type":49,"value":3680},{"type":49,"value":3859}," must both read from the same ",{"type":43,"tag":146,"props":3861,"children":3863},{"className":3862},[],[3864],{"type":49,"value":3813},{"type":49,"value":3866},"-derived list. Forgetting one leaves the LLM seeing the new tools but the executor still running the old ones, or vice versa.",{"type":43,"tag":70,"props":3868,"children":3869},{},[3870,3874,3876,3882],{"type":43,"tag":58,"props":3871,"children":3872},{},[3873],{"type":49,"value":3484},{"type":49,"value":3875}," Run the app; confirm the tool flows still execute correctly. ",{"type":43,"tag":146,"props":3877,"children":3879},{"className":3878},[],[3880],{"type":49,"value":3881},"get-ai-config",{"type":49,"value":3883}," (via the delegate) confirms the tools are attached server-side.",{"type":43,"tag":52,"props":3885,"children":3886},{},[3887,3888,3896],{"type":49,"value":3506},{"type":43,"tag":58,"props":3889,"children":3890},{},[3891],{"type":43,"tag":146,"props":3892,"children":3894},{"className":3893},[],[3895],{"type":49,"value":953},{"type":49,"value":3897}," (sub-step 2).",{"type":43,"tag":1100,"props":3899,"children":3901},{"id":3900},"step-4-instrument-the-tracker-stage-4",[3902],{"type":49,"value":3903},"Step 4: Instrument the tracker (Stage 4)",{"type":43,"tag":52,"props":3905,"children":3906},{},[3907,3908,3916,3918,3924,3926,3935,3937,3943,3945,3951,3953,3959],{"type":49,"value":3506},{"type":43,"tag":58,"props":3909,"children":3910},{},[3911],{"type":43,"tag":146,"props":3912,"children":3914},{"className":3913},[],[3915],{"type":49,"value":626},{"type":49,"value":3917}," wires the per-request ",{"type":43,"tag":146,"props":3919,"children":3921},{"className":3920},[],[3922],{"type":49,"value":3923},"tracker.track_*",{"type":49,"value":3925}," calls (duration, tokens, success\u002Ferror, feedback) around the provider call. Use ",{"type":43,"tag":58,"props":3927,"children":3928},{},[3929],{"type":43,"tag":146,"props":3930,"children":3932},{"className":3931},[],[3933],{"type":49,"value":3934},"custom-metrics",{"type":49,"value":3936}," alongside it if the app needs business metrics beyond the built-in agent ones. Note: do not confuse this with ",{"type":43,"tag":146,"props":3938,"children":3940},{"className":3939},[],[3941],{"type":49,"value":3942},"launchdarkly-metric-instrument",{"type":49,"value":3944},", which is for ",{"type":43,"tag":146,"props":3946,"children":3948},{"className":3947},[],[3949],{"type":49,"value":3950},"ldClient.track()",{"type":49,"value":3952}," feature metrics — a different API. See ",{"type":43,"tag":179,"props":3954,"children":3956},{"href":3955},"references\u002Fsdk-ai-tracker-patterns.md",[3957],{"type":49,"value":3958},"sdk-ai-tracker-patterns.md",{"type":49,"value":3960}," for the full per-method Python + Node matrix that the delegate skill draws on.",{"type":43,"tag":52,"props":3962,"children":3963},{},[3964,3966,3977],{"type":49,"value":3965},"Hand off: print the config key, variation key, provider, and whether the call is streaming, then tell the user: ",{"type":43,"tag":1008,"props":3967,"children":3968},{},[3969,3970,3976],{"type":49,"value":2543},{"type":43,"tag":146,"props":3971,"children":3973},{"className":3972},[],[3974],{"type":49,"value":3975},"\u002Fbuilt-in-metrics",{"type":49,"value":2550},{"type":49,"value":3978}," Do not auto-invoke. Return here for sub-step 5 (verify) once they're done.",{"type":43,"tag":66,"props":3980,"children":3981},{},[3982,4190,4207,4842,5070],{"type":43,"tag":70,"props":3983,"children":3984},{},[3985,3990,3992,3998,4000,4006,4008,4013,4015,4021,4023,4028,4030,4035,4037,4040,4045],{"type":43,"tag":58,"props":3986,"children":3987},{},[3988],{"type":49,"value":3989},"Create the tracker.",{"type":49,"value":3991}," Obtain a per-execution tracker via the factory on the config returned in Stage 2: ",{"type":43,"tag":146,"props":3993,"children":3995},{"className":3994},[],[3996],{"type":49,"value":3997},"tracker = config.create_tracker()",{"type":49,"value":3999}," (Python) or ",{"type":43,"tag":146,"props":4001,"children":4003},{"className":4002},[],[4004],{"type":49,"value":4005},"const tracker = aiConfig.createTracker();",{"type":49,"value":4007}," (Node). Call the factory ",{"type":43,"tag":58,"props":4009,"children":4010},{},[4011],{"type":49,"value":4012},"once per user turn",{"type":49,"value":4014}," and reuse the returned ",{"type":43,"tag":146,"props":4016,"children":4018},{"className":4017},[],[4019],{"type":49,"value":4020},"tracker",{"type":49,"value":4022}," for every tracking call in that turn — each call mints a fresh ",{"type":43,"tag":146,"props":4024,"children":4026},{"className":4025},[],[4027],{"type":49,"value":175},{"type":49,"value":4029}," that tags every event emitted from the turn so they can be correlated via exported events or downstream queries. (The Monitoring tab aggregates today; run-level grouping is a downstream concern — but the ",{"type":43,"tag":146,"props":4031,"children":4033},{"className":4032},[],[4034],{"type":49,"value":175},{"type":49,"value":4036}," is also what the SDK's at-most-once guards are keyed on, so minting a new one mid-turn breaks the guard semantics regardless of where the events end up.)",{"type":43,"tag":1703,"props":4038,"children":4039},{},[],{"type":43,"tag":58,"props":4041,"children":4042},{},[4043],{"type":49,"value":4044},"Where to call the factory depends on the call shape:",{"type":43,"tag":798,"props":4046,"children":4047},{},[4048,4065,4158],{"type":43,"tag":70,"props":4049,"children":4050},{},[4051,4056,4058,4063],{"type":43,"tag":58,"props":4052,"children":4053},{},[4054],{"type":49,"value":4055},"Completion mode \u002F one-shot provider call:",{"type":49,"value":4057}," mint the tracker right after ",{"type":43,"tag":146,"props":4059,"children":4061},{"className":4060},[],[4062],{"type":49,"value":2677},{"type":49,"value":4064}," returns, in the same function that handles the request.",{"type":43,"tag":70,"props":4066,"children":4067},{},[4068,4073,4075,4080,4082,4087,4089,4094,4096,4102,4104,4110,4111,4117,4118,4124,4126,4137,4139,4144,4145,4150,4151,4156],{"type":43,"tag":58,"props":4069,"children":4070},{},[4071],{"type":49,"value":4072},"Agent mode with a ReAct loop (LangGraph, LangChain, custom):",{"type":49,"value":4074}," mint the tracker in a dedicated ",{"type":43,"tag":146,"props":4076,"children":4078},{"className":4077},[],[4079],{"type":49,"value":159},{"type":49,"value":4081}," entry node that executes ",{"type":43,"tag":58,"props":4083,"children":4084},{},[4085],{"type":49,"value":4086},"once",{"type":49,"value":4088}," before the loop, stash it on graph state, and read it from state in ",{"type":43,"tag":146,"props":4090,"children":4092},{"className":4091},[],[4093],{"type":49,"value":167},{"type":49,"value":4095}," \u002F tool handlers \u002F a terminal ",{"type":43,"tag":146,"props":4097,"children":4099},{"className":4098},[],[4100],{"type":49,"value":4101},"finalize",{"type":49,"value":4103}," node. Emitting ",{"type":43,"tag":146,"props":4105,"children":4107},{"className":4106},[],[4108],{"type":49,"value":4109},"track_duration",{"type":49,"value":600},{"type":43,"tag":146,"props":4112,"children":4114},{"className":4113},[],[4115],{"type":49,"value":4116},"track_tokens",{"type":49,"value":600},{"type":43,"tag":146,"props":4119,"children":4121},{"className":4120},[],[4122],{"type":49,"value":4123},"track_success",{"type":49,"value":4125}," inside the loop body will trip the at-most-once guards. See ",{"type":43,"tag":179,"props":4127,"children":4128},{"href":181},[4129,4130,4135],{"type":49,"value":184},{"type":43,"tag":146,"props":4131,"children":4133},{"className":4132},[],[4134],{"type":49,"value":190},{"type":49,"value":4136}," (run-scoped architecture)",{"type":49,"value":4138}," for the full ",{"type":43,"tag":146,"props":4140,"children":4142},{"className":4141},[],[4143],{"type":49,"value":159},{"type":49,"value":1732},{"type":43,"tag":146,"props":4146,"children":4148},{"className":4147},[],[4149],{"type":49,"value":167},{"type":49,"value":1732},{"type":43,"tag":146,"props":4152,"children":4154},{"className":4153},[],[4155],{"type":49,"value":4101},{"type":49,"value":4157}," pattern.",{"type":43,"tag":70,"props":4159,"children":4160},{},[4161,4166,4168,4173,4175,4181,4182,4188],{"type":43,"tag":58,"props":4162,"children":4163},{},[4164],{"type":49,"value":4165},"Managed runner (Tier 1):",{"type":49,"value":4167}," skip this step entirely. ",{"type":43,"tag":146,"props":4169,"children":4171},{"className":4170},[],[4172],{"type":49,"value":377},{"type":49,"value":4174}," mints the tracker internally per ",{"type":43,"tag":146,"props":4176,"children":4178},{"className":4177},[],[4179],{"type":49,"value":4180},"run()",{"type":49,"value":600},{"type":43,"tag":146,"props":4183,"children":4185},{"className":4184},[],[4186],{"type":49,"value":4187},"invoke()",{"type":49,"value":4189},". Move to sub-step 4 if that's what the app uses.",{"type":43,"tag":70,"props":4191,"children":4192},{},[4193,4198,4200,4205],{"type":43,"tag":58,"props":4194,"children":4195},{},[4196],{"type":49,"value":4197},"Pick a tier from the four-tier ladder.",{"type":49,"value":4199}," See ",{"type":43,"tag":179,"props":4201,"children":4202},{"href":3955},[4203],{"type":49,"value":4204},"sdk-ai-tracker-patterns.md § Tier decision table",{"type":49,"value":4206}," for the full table (chat loop → Tier 1; provider-package call → Tier 2; custom extractor → Tier 3; streaming\u002Fmanual → Tier 4).",{"type":43,"tag":70,"props":4208,"children":4209},{},[4210,4215,4217,4220,4224,4370,4373,4378,4756,4759,4761,4767,4769,4775,4776,4782,4784,4790,4792,4798,4799,4805,4807,4813,4814,4820,4822,4828,4829,4835,4837,4841],{"type":43,"tag":58,"props":4211,"children":4212},{},[4213],{"type":49,"value":4214},"Wire the chosen tier.",{"type":49,"value":4216}," The delegate skill has full Python + Node examples for each tier plus per-provider files. A condensed Tier 2\u002F3 example for reference — OpenAI via the provider package:",{"type":43,"tag":1703,"props":4218,"children":4219},{},[],{"type":43,"tag":58,"props":4221,"children":4222},{},[4223],{"type":49,"value":1877},{"type":43,"tag":1451,"props":4225,"children":4227},{"className":1880,"code":4226,"language":1882,"meta":1459,"style":1459},"from ldai_openai import get_ai_metrics_from_response\nimport openai\n\nclient = openai.OpenAI()\n\ntracker = config.create_tracker()\n\ndef call_openai():\n    return client.chat.completions.create(\n        model=config.model.name,\n        messages=[{\"role\": \"system\", \"content\": config.messages[0].content},\n                  {\"role\": \"user\", \"content\": user_prompt}],\n    )\n\n# Exceptions are tracked automatically — track_metrics_of catches\n# exceptions, records tracker.track_error(), and re-raises. Wrap your\n# own try\u002Fexcept only for local handling (logging, fallback).\nresponse = tracker.track_metrics_of(get_ai_metrics_from_response, call_openai)\n",[4228],{"type":43,"tag":146,"props":4229,"children":4230},{"__ignoreMap":1459},[4231,4239,4247,4254,4262,4269,4277,4284,4292,4300,4308,4316,4324,4331,4338,4346,4354,4362],{"type":43,"tag":1888,"props":4232,"children":4233},{"class":1890,"line":1891},[4234],{"type":43,"tag":1888,"props":4235,"children":4236},{},[4237],{"type":49,"value":4238},"from ldai_openai import get_ai_metrics_from_response\n",{"type":43,"tag":1888,"props":4240,"children":4241},{"class":1890,"line":1900},[4242],{"type":43,"tag":1888,"props":4243,"children":4244},{},[4245],{"type":49,"value":4246},"import openai\n",{"type":43,"tag":1888,"props":4248,"children":4249},{"class":1890,"line":1909},[4250],{"type":43,"tag":1888,"props":4251,"children":4252},{"emptyLinePlaceholder":1931},[4253],{"type":49,"value":1934},{"type":43,"tag":1888,"props":4255,"children":4256},{"class":1890,"line":1918},[4257],{"type":43,"tag":1888,"props":4258,"children":4259},{},[4260],{"type":49,"value":4261},"client = openai.OpenAI()\n",{"type":43,"tag":1888,"props":4263,"children":4264},{"class":1890,"line":1927},[4265],{"type":43,"tag":1888,"props":4266,"children":4267},{"emptyLinePlaceholder":1931},[4268],{"type":49,"value":1934},{"type":43,"tag":1888,"props":4270,"children":4271},{"class":1890,"line":24},[4272],{"type":43,"tag":1888,"props":4273,"children":4274},{},[4275],{"type":49,"value":4276},"tracker = config.create_tracker()\n",{"type":43,"tag":1888,"props":4278,"children":4279},{"class":1890,"line":1945},[4280],{"type":43,"tag":1888,"props":4281,"children":4282},{"emptyLinePlaceholder":1931},[4283],{"type":49,"value":1934},{"type":43,"tag":1888,"props":4285,"children":4286},{"class":1890,"line":1954},[4287],{"type":43,"tag":1888,"props":4288,"children":4289},{},[4290],{"type":49,"value":4291},"def call_openai():\n",{"type":43,"tag":1888,"props":4293,"children":4294},{"class":1890,"line":1963},[4295],{"type":43,"tag":1888,"props":4296,"children":4297},{},[4298],{"type":49,"value":4299},"    return client.chat.completions.create(\n",{"type":43,"tag":1888,"props":4301,"children":4302},{"class":1890,"line":1972},[4303],{"type":43,"tag":1888,"props":4304,"children":4305},{},[4306],{"type":49,"value":4307},"        model=config.model.name,\n",{"type":43,"tag":1888,"props":4309,"children":4310},{"class":1890,"line":1981},[4311],{"type":43,"tag":1888,"props":4312,"children":4313},{},[4314],{"type":49,"value":4315},"        messages=[{\"role\": \"system\", \"content\": config.messages[0].content},\n",{"type":43,"tag":1888,"props":4317,"children":4318},{"class":1890,"line":1990},[4319],{"type":43,"tag":1888,"props":4320,"children":4321},{},[4322],{"type":49,"value":4323},"                  {\"role\": \"user\", \"content\": user_prompt}],\n",{"type":43,"tag":1888,"props":4325,"children":4326},{"class":1890,"line":1999},[4327],{"type":43,"tag":1888,"props":4328,"children":4329},{},[4330],{"type":49,"value":2050},{"type":43,"tag":1888,"props":4332,"children":4333},{"class":1890,"line":2008},[4334],{"type":43,"tag":1888,"props":4335,"children":4336},{"emptyLinePlaceholder":1931},[4337],{"type":49,"value":1934},{"type":43,"tag":1888,"props":4339,"children":4340},{"class":1890,"line":2017},[4341],{"type":43,"tag":1888,"props":4342,"children":4343},{},[4344],{"type":49,"value":4345},"# Exceptions are tracked automatically — track_metrics_of catches\n",{"type":43,"tag":1888,"props":4347,"children":4348},{"class":1890,"line":2026},[4349],{"type":43,"tag":1888,"props":4350,"children":4351},{},[4352],{"type":49,"value":4353},"# exceptions, records tracker.track_error(), and re-raises. Wrap your\n",{"type":43,"tag":1888,"props":4355,"children":4356},{"class":1890,"line":2035},[4357],{"type":43,"tag":1888,"props":4358,"children":4359},{},[4360],{"type":49,"value":4361},"# own try\u002Fexcept only for local handling (logging, fallback).\n",{"type":43,"tag":1888,"props":4363,"children":4364},{"class":1890,"line":2044},[4365],{"type":43,"tag":1888,"props":4366,"children":4367},{},[4368],{"type":49,"value":4369},"response = tracker.track_metrics_of(get_ai_metrics_from_response, call_openai)\n",{"type":43,"tag":1703,"props":4371,"children":4372},{},[],{"type":43,"tag":58,"props":4374,"children":4375},{},[4376],{"type":49,"value":4377},"Node:",{"type":43,"tag":1451,"props":4379,"children":4381},{"className":2086,"code":4380,"language":2088,"meta":1459,"style":1459},"import { getAIMetricsFromResponse } from '@launchdarkly\u002Fserver-sdk-ai-openai';\n\nconst tracker = aiConfig.createTracker();\n\u002F\u002F Exceptions are tracked automatically — trackMetricsOf catches\n\u002F\u002F exceptions, records tracker.trackError(), and re-throws.\nconst response = await tracker.trackMetricsOf(\n  getAIMetricsFromResponse,\n  () => openaiClient.chat.completions.create({\n    model: aiConfig.model!.name,\n    messages: [...aiConfig.messages, { role: 'user', content: userPrompt }],\n  }),\n);\n",[4382],{"type":43,"tag":146,"props":4383,"children":4384},{"__ignoreMap":1459},[4385,4426,4433,4471,4479,4487,4527,4540,4592,4631,4729,4745],{"type":43,"tag":1888,"props":4386,"children":4387},{"class":1890,"line":1891},[4388,4392,4396,4401,4405,4409,4413,4418,4422],{"type":43,"tag":1888,"props":4389,"children":4390},{"style":2098},[4391],{"type":49,"value":2101},{"type":43,"tag":1888,"props":4393,"children":4394},{"style":2104},[4395],{"type":49,"value":2107},{"type":43,"tag":1888,"props":4397,"children":4398},{"style":2110},[4399],{"type":49,"value":4400}," getAIMetricsFromResponse",{"type":43,"tag":1888,"props":4402,"children":4403},{"style":2104},[4404],{"type":49,"value":2118},{"type":43,"tag":1888,"props":4406,"children":4407},{"style":2098},[4408],{"type":49,"value":2123},{"type":43,"tag":1888,"props":4410,"children":4411},{"style":2104},[4412],{"type":49,"value":2128},{"type":43,"tag":1888,"props":4414,"children":4415},{"style":2131},[4416],{"type":49,"value":4417},"@launchdarkly\u002Fserver-sdk-ai-openai",{"type":43,"tag":1888,"props":4419,"children":4420},{"style":2104},[4421],{"type":49,"value":2138},{"type":43,"tag":1888,"props":4423,"children":4424},{"style":2104},[4425],{"type":49,"value":2143},{"type":43,"tag":1888,"props":4427,"children":4428},{"class":1890,"line":1900},[4429],{"type":43,"tag":1888,"props":4430,"children":4431},{"emptyLinePlaceholder":1931},[4432],{"type":49,"value":1934},{"type":43,"tag":1888,"props":4434,"children":4435},{"class":1890,"line":1909},[4436,4440,4445,4449,4454,4458,4463,4467],{"type":43,"tag":1888,"props":4437,"children":4438},{"style":2321},[4439],{"type":49,"value":2324},{"type":43,"tag":1888,"props":4441,"children":4442},{"style":2110},[4443],{"type":49,"value":4444}," tracker ",{"type":43,"tag":1888,"props":4446,"children":4447},{"style":2104},[4448],{"type":49,"value":2334},{"type":43,"tag":1888,"props":4450,"children":4451},{"style":2110},[4452],{"type":49,"value":4453}," aiConfig",{"type":43,"tag":1888,"props":4455,"children":4456},{"style":2104},[4457],{"type":49,"value":192},{"type":43,"tag":1888,"props":4459,"children":4460},{"style":2277},[4461],{"type":49,"value":4462},"createTracker",{"type":43,"tag":1888,"props":4464,"children":4465},{"style":2110},[4466],{"type":49,"value":2459},{"type":43,"tag":1888,"props":4468,"children":4469},{"style":2104},[4470],{"type":49,"value":2143},{"type":43,"tag":1888,"props":4472,"children":4473},{"class":1890,"line":1918},[4474],{"type":43,"tag":1888,"props":4475,"children":4476},{"style":2197},[4477],{"type":49,"value":4478},"\u002F\u002F Exceptions are tracked automatically — trackMetricsOf catches\n",{"type":43,"tag":1888,"props":4480,"children":4481},{"class":1890,"line":1927},[4482],{"type":43,"tag":1888,"props":4483,"children":4484},{"style":2197},[4485],{"type":49,"value":4486},"\u002F\u002F exceptions, records tracker.trackError(), and re-throws.\n",{"type":43,"tag":1888,"props":4488,"children":4489},{"class":1890,"line":24},[4490,4494,4499,4503,4508,4513,4517,4522],{"type":43,"tag":1888,"props":4491,"children":4492},{"style":2321},[4493],{"type":49,"value":2324},{"type":43,"tag":1888,"props":4495,"children":4496},{"style":2110},[4497],{"type":49,"value":4498}," response ",{"type":43,"tag":1888,"props":4500,"children":4501},{"style":2104},[4502],{"type":49,"value":2334},{"type":43,"tag":1888,"props":4504,"children":4505},{"style":2098},[4506],{"type":49,"value":4507}," await",{"type":43,"tag":1888,"props":4509,"children":4510},{"style":2110},[4511],{"type":49,"value":4512}," tracker",{"type":43,"tag":1888,"props":4514,"children":4515},{"style":2104},[4516],{"type":49,"value":192},{"type":43,"tag":1888,"props":4518,"children":4519},{"style":2277},[4520],{"type":49,"value":4521},"trackMetricsOf",{"type":43,"tag":1888,"props":4523,"children":4524},{"style":2110},[4525],{"type":49,"value":4526},"(\n",{"type":43,"tag":1888,"props":4528,"children":4529},{"class":1890,"line":1945},[4530,4535],{"type":43,"tag":1888,"props":4531,"children":4532},{"style":2110},[4533],{"type":49,"value":4534},"  getAIMetricsFromResponse",{"type":43,"tag":1888,"props":4536,"children":4537},{"style":2104},[4538],{"type":49,"value":4539},",\n",{"type":43,"tag":1888,"props":4541,"children":4542},{"class":1890,"line":1954},[4543,4548,4552,4557,4561,4566,4570,4575,4579,4584,4588],{"type":43,"tag":1888,"props":4544,"children":4545},{"style":2104},[4546],{"type":49,"value":4547},"  ()",{"type":43,"tag":1888,"props":4549,"children":4550},{"style":2321},[4551],{"type":49,"value":2464},{"type":43,"tag":1888,"props":4553,"children":4554},{"style":2110},[4555],{"type":49,"value":4556}," openaiClient",{"type":43,"tag":1888,"props":4558,"children":4559},{"style":2104},[4560],{"type":49,"value":192},{"type":43,"tag":1888,"props":4562,"children":4563},{"style":2110},[4564],{"type":49,"value":4565},"chat",{"type":43,"tag":1888,"props":4567,"children":4568},{"style":2104},[4569],{"type":49,"value":192},{"type":43,"tag":1888,"props":4571,"children":4572},{"style":2110},[4573],{"type":49,"value":4574},"completions",{"type":43,"tag":1888,"props":4576,"children":4577},{"style":2104},[4578],{"type":49,"value":192},{"type":43,"tag":1888,"props":4580,"children":4581},{"style":2277},[4582],{"type":49,"value":4583},"create",{"type":43,"tag":1888,"props":4585,"children":4586},{"style":2110},[4587],{"type":49,"value":2286},{"type":43,"tag":1888,"props":4589,"children":4590},{"style":2104},[4591],{"type":49,"value":2262},{"type":43,"tag":1888,"props":4593,"children":4594},{"class":1890,"line":1963},[4595,4600,4604,4608,4612,4617,4622,4627],{"type":43,"tag":1888,"props":4596,"children":4597},{"style":2283},[4598],{"type":49,"value":4599},"    model",{"type":43,"tag":1888,"props":4601,"children":4602},{"style":2104},[4603],{"type":49,"value":2427},{"type":43,"tag":1888,"props":4605,"children":4606},{"style":2110},[4607],{"type":49,"value":4453},{"type":43,"tag":1888,"props":4609,"children":4610},{"style":2104},[4611],{"type":49,"value":192},{"type":43,"tag":1888,"props":4613,"children":4614},{"style":2110},[4615],{"type":49,"value":4616},"model",{"type":43,"tag":1888,"props":4618,"children":4619},{"style":2104},[4620],{"type":49,"value":4621},"!.",{"type":43,"tag":1888,"props":4623,"children":4624},{"style":2110},[4625],{"type":49,"value":4626},"name",{"type":43,"tag":1888,"props":4628,"children":4629},{"style":2104},[4630],{"type":49,"value":4539},{"type":43,"tag":1888,"props":4632,"children":4633},{"class":1890,"line":1972},[4634,4639,4643,4648,4653,4658,4662,4667,4672,4676,4681,4685,4689,4694,4698,4702,4707,4711,4716,4720,4725],{"type":43,"tag":1888,"props":4635,"children":4636},{"style":2283},[4637],{"type":49,"value":4638},"    messages",{"type":43,"tag":1888,"props":4640,"children":4641},{"style":2104},[4642],{"type":49,"value":2427},{"type":43,"tag":1888,"props":4644,"children":4645},{"style":2110},[4646],{"type":49,"value":4647}," [",{"type":43,"tag":1888,"props":4649,"children":4650},{"style":2104},[4651],{"type":49,"value":4652},"...",{"type":43,"tag":1888,"props":4654,"children":4655},{"style":2110},[4656],{"type":49,"value":4657},"aiConfig",{"type":43,"tag":1888,"props":4659,"children":4660},{"style":2104},[4661],{"type":49,"value":192},{"type":43,"tag":1888,"props":4663,"children":4664},{"style":2110},[4665],{"type":49,"value":4666},"messages",{"type":43,"tag":1888,"props":4668,"children":4669},{"style":2104},[4670],{"type":49,"value":4671},",",{"type":43,"tag":1888,"props":4673,"children":4674},{"style":2104},[4675],{"type":49,"value":2107},{"type":43,"tag":1888,"props":4677,"children":4678},{"style":2283},[4679],{"type":49,"value":4680}," role",{"type":43,"tag":1888,"props":4682,"children":4683},{"style":2104},[4684],{"type":49,"value":2427},{"type":43,"tag":1888,"props":4686,"children":4687},{"style":2104},[4688],{"type":49,"value":2128},{"type":43,"tag":1888,"props":4690,"children":4691},{"style":2131},[4692],{"type":49,"value":4693},"user",{"type":43,"tag":1888,"props":4695,"children":4696},{"style":2104},[4697],{"type":49,"value":2138},{"type":43,"tag":1888,"props":4699,"children":4700},{"style":2104},[4701],{"type":49,"value":4671},{"type":43,"tag":1888,"props":4703,"children":4704},{"style":2283},[4705],{"type":49,"value":4706}," content",{"type":43,"tag":1888,"props":4708,"children":4709},{"style":2104},[4710],{"type":49,"value":2427},{"type":43,"tag":1888,"props":4712,"children":4713},{"style":2110},[4714],{"type":49,"value":4715}," userPrompt ",{"type":43,"tag":1888,"props":4717,"children":4718},{"style":2104},[4719],{"type":49,"value":2485},{"type":43,"tag":1888,"props":4721,"children":4722},{"style":2110},[4723],{"type":49,"value":4724},"]",{"type":43,"tag":1888,"props":4726,"children":4727},{"style":2104},[4728],{"type":49,"value":4539},{"type":43,"tag":1888,"props":4730,"children":4731},{"class":1890,"line":1981},[4732,4737,4741],{"type":43,"tag":1888,"props":4733,"children":4734},{"style":2104},[4735],{"type":49,"value":4736},"  }",{"type":43,"tag":1888,"props":4738,"children":4739},{"style":2110},[4740],{"type":49,"value":379},{"type":43,"tag":1888,"props":4742,"children":4743},{"style":2104},[4744],{"type":49,"value":4539},{"type":43,"tag":1888,"props":4746,"children":4747},{"class":1890,"line":1990},[4748,4752],{"type":43,"tag":1888,"props":4749,"children":4750},{"style":2110},[4751],{"type":49,"value":379},{"type":43,"tag":1888,"props":4753,"children":4754},{"style":2104},[4755],{"type":49,"value":2143},{"type":43,"tag":1703,"props":4757,"children":4758},{},[],{"type":49,"value":4760},"For Anthropic direct, Bedrock (no provider package), Gemini, and custom HTTP, write a small extractor returning ",{"type":43,"tag":146,"props":4762,"children":4764},{"className":4763},[],[4765],{"type":49,"value":4766},"LDAIMetrics",{"type":49,"value":4768}," — see the delegate skill's ",{"type":43,"tag":179,"props":4770,"children":4772},{"href":4771},"..\u002Fbuilt-in-metrics\u002Freferences\u002Fanthropic-tracking.md",[4773],{"type":49,"value":4774},"anthropic-tracking.md",{"type":49,"value":1065},{"type":43,"tag":179,"props":4777,"children":4779},{"href":4778},"..\u002Fbuilt-in-metrics\u002Freferences\u002Fbedrock-tracking.md",[4780],{"type":49,"value":4781},"bedrock-tracking.md",{"type":49,"value":4783},", and ",{"type":43,"tag":179,"props":4785,"children":4787},{"href":4786},"..\u002Fbuilt-in-metrics\u002Freferences\u002Fgemini-tracking.md",[4788],{"type":49,"value":4789},"gemini-tracking.md",{"type":49,"value":4791},". LangChain single-node and LangGraph go through the ",{"type":43,"tag":146,"props":4793,"children":4795},{"className":4794},[],[4796],{"type":49,"value":4797},"launchdarkly-server-sdk-ai-langchain",{"type":49,"value":600},{"type":43,"tag":146,"props":4800,"children":4802},{"className":4801},[],[4803],{"type":49,"value":4804},"@launchdarkly\u002Fserver-sdk-ai-langchain",{"type":49,"value":4806}," provider package. Build the model with ",{"type":43,"tag":146,"props":4808,"children":4810},{"className":4809},[],[4811],{"type":49,"value":4812},"create_langchain_model(config)",{"type":49,"value":1795},{"type":43,"tag":146,"props":4815,"children":4817},{"className":4816},[],[4818],{"type":49,"value":4819},"createLangChainModel(config)",{"type":49,"value":4821}," (Node) — both forward all variation parameters — and track with ",{"type":43,"tag":146,"props":4823,"children":4825},{"className":4824},[],[4826],{"type":49,"value":4827},"get_ai_metrics_from_response",{"type":49,"value":600},{"type":43,"tag":146,"props":4830,"children":4832},{"className":4831},[],[4833],{"type":49,"value":4834},"getAIMetricsFromResponse",{"type":49,"value":4836},". See ",{"type":43,"tag":179,"props":4838,"children":4839},{"href":419},[4840],{"type":49,"value":422},{"type":49,"value":192},{"type":43,"tag":70,"props":4843,"children":4844},{},[4845,4850,4852,4858,4860,4866,4868,4871,4875,4898,4901,4905,5009,5012,5017,5019,5023,5025,5030,5032,5037,5039,5045,5047,5053,5055,5061,5062,5068],{"type":43,"tag":58,"props":4846,"children":4847},{},[4848],{"type":49,"value":4849},"Wire feedback tracking if the app has thumbs-up\u002Fdown UI.",{"type":49,"value":4851}," Both SDKs expose ",{"type":43,"tag":146,"props":4853,"children":4855},{"className":4854},[],[4856],{"type":49,"value":4857},"trackFeedback",{"type":49,"value":4859}," with a ",{"type":43,"tag":146,"props":4861,"children":4863},{"className":4862},[],[4864],{"type":49,"value":4865},"{kind}",{"type":49,"value":4867}," argument.",{"type":43,"tag":1703,"props":4869,"children":4870},{},[],{"type":43,"tag":58,"props":4872,"children":4873},{},[4874],{"type":49,"value":1877},{"type":43,"tag":1451,"props":4876,"children":4878},{"className":1880,"code":4877,"language":1882,"meta":1459,"style":1459},"from ldai.tracker import FeedbackKind\ntracker.track_feedback({\"kind\": FeedbackKind.Positive})\n",[4879],{"type":43,"tag":146,"props":4880,"children":4881},{"__ignoreMap":1459},[4882,4890],{"type":43,"tag":1888,"props":4883,"children":4884},{"class":1890,"line":1891},[4885],{"type":43,"tag":1888,"props":4886,"children":4887},{},[4888],{"type":49,"value":4889},"from ldai.tracker import FeedbackKind\n",{"type":43,"tag":1888,"props":4891,"children":4892},{"class":1890,"line":1900},[4893],{"type":43,"tag":1888,"props":4894,"children":4895},{},[4896],{"type":49,"value":4897},"tracker.track_feedback({\"kind\": FeedbackKind.Positive})\n",{"type":43,"tag":1703,"props":4899,"children":4900},{},[],{"type":43,"tag":58,"props":4902,"children":4903},{},[4904],{"type":49,"value":4377},{"type":43,"tag":1451,"props":4906,"children":4908},{"className":2086,"code":4907,"language":2088,"meta":1459,"style":1459},"import { LDFeedbackKind } from '@launchdarkly\u002Fserver-sdk-ai';\ntracker.trackFeedback({ kind: LDFeedbackKind.Positive });\n",[4909],{"type":43,"tag":146,"props":4910,"children":4911},{"__ignoreMap":1459},[4912,4952],{"type":43,"tag":1888,"props":4913,"children":4914},{"class":1890,"line":1891},[4915,4919,4923,4928,4932,4936,4940,4944,4948],{"type":43,"tag":1888,"props":4916,"children":4917},{"style":2098},[4918],{"type":49,"value":2101},{"type":43,"tag":1888,"props":4920,"children":4921},{"style":2104},[4922],{"type":49,"value":2107},{"type":43,"tag":1888,"props":4924,"children":4925},{"style":2110},[4926],{"type":49,"value":4927}," LDFeedbackKind",{"type":43,"tag":1888,"props":4929,"children":4930},{"style":2104},[4931],{"type":49,"value":2118},{"type":43,"tag":1888,"props":4933,"children":4934},{"style":2098},[4935],{"type":49,"value":2123},{"type":43,"tag":1888,"props":4937,"children":4938},{"style":2104},[4939],{"type":49,"value":2128},{"type":43,"tag":1888,"props":4941,"children":4942},{"style":2131},[4943],{"type":49,"value":2176},{"type":43,"tag":1888,"props":4945,"children":4946},{"style":2104},[4947],{"type":49,"value":2138},{"type":43,"tag":1888,"props":4949,"children":4950},{"style":2104},[4951],{"type":49,"value":2143},{"type":43,"tag":1888,"props":4953,"children":4954},{"class":1890,"line":1900},[4955,4959,4963,4967,4971,4975,4980,4984,4988,4992,4997,5001,5005],{"type":43,"tag":1888,"props":4956,"children":4957},{"style":2110},[4958],{"type":49,"value":4020},{"type":43,"tag":1888,"props":4960,"children":4961},{"style":2104},[4962],{"type":49,"value":192},{"type":43,"tag":1888,"props":4964,"children":4965},{"style":2277},[4966],{"type":49,"value":4857},{"type":43,"tag":1888,"props":4968,"children":4969},{"style":2110},[4970],{"type":49,"value":2286},{"type":43,"tag":1888,"props":4972,"children":4973},{"style":2104},[4974],{"type":49,"value":2417},{"type":43,"tag":1888,"props":4976,"children":4977},{"style":2283},[4978],{"type":49,"value":4979}," kind",{"type":43,"tag":1888,"props":4981,"children":4982},{"style":2104},[4983],{"type":49,"value":2427},{"type":43,"tag":1888,"props":4985,"children":4986},{"style":2110},[4987],{"type":49,"value":4927},{"type":43,"tag":1888,"props":4989,"children":4990},{"style":2104},[4991],{"type":49,"value":192},{"type":43,"tag":1888,"props":4993,"children":4994},{"style":2110},[4995],{"type":49,"value":4996},"Positive ",{"type":43,"tag":1888,"props":4998,"children":4999},{"style":2104},[5000],{"type":49,"value":2485},{"type":43,"tag":1888,"props":5002,"children":5003},{"style":2110},[5004],{"type":49,"value":379},{"type":43,"tag":1888,"props":5006,"children":5007},{"style":2104},[5008],{"type":49,"value":2143},{"type":43,"tag":1703,"props":5010,"children":5011},{},[],{"type":43,"tag":58,"props":5013,"children":5014},{},[5015],{"type":49,"value":5016},"Deferred feedback across processes.",{"type":49,"value":5018}," If the thumbs-up UI fires in a different process than the one that produced the response, do ",{"type":43,"tag":58,"props":5020,"children":5021},{},[5022],{"type":49,"value":902},{"type":49,"value":5024}," call ",{"type":43,"tag":146,"props":5026,"children":5028},{"className":5027},[],[5029],{"type":49,"value":151},{"type":49,"value":5031}," again in the consumer — that mints a new ",{"type":43,"tag":146,"props":5033,"children":5035},{"className":5034},[],[5036],{"type":49,"value":175},{"type":49,"value":5038},". Persist the tracker's resumption token (",{"type":43,"tag":146,"props":5040,"children":5042},{"className":5041},[],[5043],{"type":49,"value":5044},"tracker.resumption_token",{"type":49,"value":5046}," in Python, ",{"type":43,"tag":146,"props":5048,"children":5050},{"className":5049},[],[5051],{"type":49,"value":5052},"tracker.resumptionToken",{"type":49,"value":5054}," in Node) alongside the message, then rehydrate the tracker with ",{"type":43,"tag":146,"props":5056,"children":5058},{"className":5057},[],[5059],{"type":49,"value":5060},"LDAIConfigTracker.from_resumption_token(...)",{"type":49,"value":3999},{"type":43,"tag":146,"props":5063,"children":5065},{"className":5064},[],[5066],{"type":49,"value":5067},"aiClient.createTracker(token, context)",{"type":49,"value":5069}," (Node) in the feedback handler.",{"type":43,"tag":70,"props":5071,"children":5072},{},[5073,5077,5079,5083],{"type":43,"tag":58,"props":5074,"children":5075},{},[5076],{"type":49,"value":3484},{"type":49,"value":5078}," Hit the wrapped endpoint in staging, then open the config in LaunchDarkly → Monitoring tab. Duration, token, and generation counts should appear within 1–2 minutes. If nothing shows up, walk the checklist in ",{"type":43,"tag":179,"props":5080,"children":5081},{"href":3955},[5082],{"type":49,"value":3958},{"type":49,"value":5084}," under \"Troubleshooting.\"",{"type":43,"tag":1100,"props":5086,"children":5088},{"id":5087},"step-5-attach-evaluations-stage-5",[5089],{"type":49,"value":5090},"Step 5: Attach evaluations (Stage 5)",{"type":43,"tag":66,"props":5092,"children":5093},{},[5094,5229,5365,5420,5829],{"type":43,"tag":70,"props":5095,"children":5096},{},[5097,5102,5104,5109,5111,5219,5222,5227],{"type":43,"tag":58,"props":5098,"children":5099},{},[5100],{"type":49,"value":5101},"Decide between three evaluation paths.",{"type":49,"value":5103}," This is the most commonly misunderstood stage — there are ",{"type":43,"tag":58,"props":5105,"children":5106},{},[5107],{"type":49,"value":5108},"three",{"type":49,"value":5110}," paths, not two, and the right default for a migration context is often the one people skip.",{"type":43,"tag":293,"props":5112,"children":5113},{},[5114,5135],{"type":43,"tag":297,"props":5115,"children":5116},{},[5117],{"type":43,"tag":301,"props":5118,"children":5119},{},[5120,5125,5130],{"type":43,"tag":305,"props":5121,"children":5122},{},[5123],{"type":49,"value":5124},"Path",{"type":43,"tag":305,"props":5126,"children":5127},{},[5128],{"type":49,"value":5129},"When to use",{"type":43,"tag":305,"props":5131,"children":5132},{},[5133],{"type":49,"value":5134},"Supports agent mode?",{"type":43,"tag":326,"props":5136,"children":5137},{},[5138,5161,5182],{"type":43,"tag":301,"props":5139,"children":5140},{},[5141,5151,5156],{"type":43,"tag":333,"props":5142,"children":5143},{},[5144,5149],{"type":43,"tag":58,"props":5145,"children":5146},{},[5147],{"type":49,"value":5148},"Offline eval",{"type":49,"value":5150}," (recommended default for migration)",{"type":43,"tag":333,"props":5152,"children":5153},{},[5154],{"type":49,"value":5155},"Pre-ship regression: run a fixed dataset through the new variation in the LD Playground and score against baseline. Best fit for migration because you want to prove the new config behaves at least as well as the hardcoded version before shipping.",{"type":43,"tag":333,"props":5157,"children":5158},{},[5159],{"type":49,"value":5160},"Yes — all modes",{"type":43,"tag":301,"props":5162,"children":5163},{},[5164,5172,5177],{"type":43,"tag":333,"props":5165,"children":5166},{},[5167],{"type":43,"tag":58,"props":5168,"children":5169},{},[5170],{"type":49,"value":5171},"UI-attached auto judges",{"type":43,"tag":333,"props":5173,"children":5174},{},[5175],{"type":49,"value":5176},"Attach one or more judges to a variation in the LD UI; judges run on sampled live requests automatically. Zero code changes.",{"type":43,"tag":333,"props":5178,"children":5179},{},[5180],{"type":49,"value":5181},"Completion mode only (the UI widget is completion-only today)",{"type":43,"tag":301,"props":5183,"children":5184},{},[5185,5193,5214],{"type":43,"tag":333,"props":5186,"children":5187},{},[5188],{"type":43,"tag":58,"props":5189,"children":5190},{},[5191],{"type":49,"value":5192},"Programmatic direct-judge",{"type":43,"tag":333,"props":5194,"children":5195},{},[5196,5198,5204,5206,5212],{"type":49,"value":5197},"Call ",{"type":43,"tag":146,"props":5199,"children":5201},{"className":5200},[],[5202],{"type":49,"value":5203},"ai_client.create_judge(...)",{"type":49,"value":5205}," inside the request handler and ",{"type":43,"tag":146,"props":5207,"children":5209},{"className":5208},[],[5210],{"type":49,"value":5211},"judge.evaluate(input, output)",{"type":49,"value":5213}," on each call. Adds per-request cost and code complexity. Best for continuous live scoring of workflows where sampled auto-judges aren't enough.",{"type":43,"tag":333,"props":5215,"children":5216},{},[5217],{"type":49,"value":5218},"Yes — all modes (the SDK handles both identically)",{"type":43,"tag":1703,"props":5220,"children":5221},{},[],{"type":43,"tag":58,"props":5223,"children":5224},{},[5225],{"type":49,"value":5226},"Most migration users should start with offline eval",{"type":49,"value":5228},", then add programmatic direct-judge only if they need continuous live scoring after the rollout is stable.",{"type":43,"tag":70,"props":5230,"children":5231},{},[5232,5237,5239,5244,5246,5251,5253,5260,5262,5265,5270,5352,5355,5357,5363],{"type":43,"tag":58,"props":5233,"children":5234},{},[5235],{"type":49,"value":5236},"For agent-mode migrations, default to offline eval.",{"type":49,"value":5238}," UI-attached auto judges are completion-mode only today. The documented path for agent mode is either (a) ",{"type":43,"tag":58,"props":5240,"children":5241},{},[5242],{"type":49,"value":5243},"offline regression",{"type":49,"value":5245}," via the LD Playground + Datasets (works for all modes), or (b) ",{"type":43,"tag":58,"props":5247,"children":5248},{},[5249],{"type":49,"value":5250},"programmatic direct-judge",{"type":49,"value":5252}," wired into the call site. Generate a starter dataset CSV from the audit manifest (one representative input per row) and point the user at the ",{"type":43,"tag":179,"props":5254,"children":5257},{"href":5255,"rel":5256},"https:\u002F\u002Fdocs.launchdarkly.com\u002Fguides\u002Fai-configs\u002Foffline-evaluations",[852],[5258],{"type":49,"value":5259},"Offline Evals guide",{"type":49,"value":5261}," for the Playground walkthrough. Only wire programmatic direct-judge into production code if the user explicitly asks for continuous live scoring.",{"type":43,"tag":1703,"props":5263,"children":5264},{},[],{"type":43,"tag":58,"props":5266,"children":5267},{},[5268],{"type":49,"value":5269},"Recommended offline-eval shape for a migration:",{"type":43,"tag":798,"props":5271,"children":5272},{},[5273,5286,5321,5340],{"type":43,"tag":70,"props":5274,"children":5275},{},[5276,5278,5284],{"type":49,"value":5277},"Run the ",{"type":43,"tag":146,"props":5279,"children":5281},{"className":5280},[],[5282],{"type":49,"value":5283},"default",{"type":49,"value":5285}," variation (or whichever variation mirrors the pre-migration hardcoded behavior) against the dataset first — this is the baseline.",{"type":43,"tag":70,"props":5287,"children":5288},{},[5289,5291,5296,5298,5304,5306,5312,5313,5319],{"type":49,"value":5290},"Clone it into a second variation pointing at a ",{"type":43,"tag":58,"props":5292,"children":5293},{},[5294],{"type":49,"value":5295},"different model family",{"type":49,"value":5297}," (e.g., if the baseline is ",{"type":43,"tag":146,"props":5299,"children":5301},{"className":5300},[],[5302],{"type":49,"value":5303},"anthropic\u002Fclaude-sonnet-4-5",{"type":49,"value":5305},", clone to ",{"type":43,"tag":146,"props":5307,"children":5309},{"className":5308},[],[5310],{"type":49,"value":5311},"openai\u002Fgpt-4o",{"type":49,"value":1200},{"type":43,"tag":146,"props":5314,"children":5316},{"className":5315},[],[5317],{"type":49,"value":5318},"openai\u002Fgpt-4o-mini",{"type":49,"value":5320},"). The comparison is most informative across families, not across siblings.",{"type":43,"tag":70,"props":5322,"children":5323},{},[5324,5326,5331,5333,5338],{"type":49,"value":5325},"Attach the built-in ",{"type":43,"tag":58,"props":5327,"children":5328},{},[5329],{"type":49,"value":5330},"Accuracy",{"type":49,"value":5332}," judge with a pass threshold of ",{"type":43,"tag":58,"props":5334,"children":5335},{},[5336],{"type":49,"value":5337},"0.85",{"type":49,"value":5339},", and run both variations against the same dataset.",{"type":43,"tag":70,"props":5341,"children":5342},{},[5343,5345,5350],{"type":49,"value":5344},"Promote the winner to fallthrough via ",{"type":43,"tag":146,"props":5346,"children":5348},{"className":5347},[],[5349],{"type":49,"value":277},{"type":49,"value":5351}," only if it beats the baseline on Accuracy and does not regress on Relevance or Toxicity.",{"type":43,"tag":1703,"props":5353,"children":5354},{},[],{"type":49,"value":5356},"Write this shape into the project's ",{"type":43,"tag":146,"props":5358,"children":5360},{"className":5359},[],[5361],{"type":49,"value":5362},"datasets\u002FREADME.md",{"type":49,"value":5364}," (or equivalent) so the comparison pattern is reproducible after the migration ships.",{"type":43,"tag":70,"props":5366,"children":5367},{},[5368,5377,5379,5389,5391,5409,5412,5414,5418],{"type":43,"tag":58,"props":5369,"children":5370},{},[5371,5372],{"type":49,"value":2530},{"type":43,"tag":146,"props":5373,"children":5375},{"className":5374},[],[5376],{"type":49,"value":975},{"type":49,"value":5378}," — only for UI-attached judges (completion mode) or to create custom judge configs that will be referenced by the programmatic path. Tell the user: ",{"type":43,"tag":1008,"props":5380,"children":5381},{},[5382,5383,5388],{"type":49,"value":2543},{"type":43,"tag":146,"props":5384,"children":5386},{"className":5385},[],[5387],{"type":49,"value":1084},{"type":49,"value":2550},{"type":49,"value":5390}," Do not auto-invoke. Pass:",{"type":43,"tag":798,"props":5392,"children":5393},{},[5394,5399,5404],{"type":43,"tag":70,"props":5395,"children":5396},{},[5397],{"type":49,"value":5398},"The parent config key and variation key",{"type":43,"tag":70,"props":5400,"children":5401},{},[5402],{"type":49,"value":5403},"A list of built-in judges (Accuracy, Relevance, Toxicity) or custom judge keys to create\u002Fattach",{"type":43,"tag":70,"props":5405,"children":5406},{},[5407],{"type":49,"value":5408},"Target environment",{"type":43,"tag":1703,"props":5410,"children":5411},{},[],{"type":49,"value":5413},"The delegate handles creating custom judge configs, attaching them via the variation PATCH endpoint, and setting fallthrough on each judge config. Offline eval does ",{"type":43,"tag":58,"props":5415,"children":5416},{},[5417],{"type":49,"value":902},{"type":49,"value":5419}," go through this delegate — it's a Playground workflow, not an API write.",{"type":43,"tag":70,"props":5421,"children":5422},{},[5423,5449,5451,5582,5585,5587,5798,5801,5806,5808,5813,5815,5820,5822,5827],{"type":43,"tag":58,"props":5424,"children":5425},{},[5426,5428,5434,5435,5441,5442,5448],{"type":49,"value":5427},"For programmatic direct-judge: wire ",{"type":43,"tag":146,"props":5429,"children":5431},{"className":5430},[],[5432],{"type":49,"value":5433},"create_judge",{"type":49,"value":1732},{"type":43,"tag":146,"props":5436,"children":5438},{"className":5437},[],[5439],{"type":49,"value":5440},"evaluate",{"type":49,"value":1732},{"type":43,"tag":146,"props":5443,"children":5445},{"className":5444},[],[5446],{"type":49,"value":5447},"track_judge_result",{"type":49,"value":192},{"type":49,"value":5450}," This is the only path at Stage 5 that writes code. The Python shape:",{"type":43,"tag":1451,"props":5452,"children":5454},{"className":1880,"code":5453,"language":1882,"meta":1459,"style":1459},"from ldai.client import AIJudgeConfigDefault\n\njudge = ai_client.create_judge(\n    judge_key,                               # judge config key in LD\n    ld_context,\n    AIJudgeConfigDefault(enabled=False),     # fallback: skip eval on SDK miss\n)\n\nif judge and judge.enabled:\n    result = await judge.evaluate(\n        input_text,\n        output_text,\n        sampling_rate=0.25,                  # optional; default 1.0 (always eval)\n    )\n    if result.sampled:\n        tracker.track_judge_result(result)\n",[5455],{"type":43,"tag":146,"props":5456,"children":5457},{"__ignoreMap":1459},[5458,5466,5473,5481,5489,5497,5505,5512,5519,5527,5535,5543,5551,5559,5566,5574],{"type":43,"tag":1888,"props":5459,"children":5460},{"class":1890,"line":1891},[5461],{"type":43,"tag":1888,"props":5462,"children":5463},{},[5464],{"type":49,"value":5465},"from ldai.client import AIJudgeConfigDefault\n",{"type":43,"tag":1888,"props":5467,"children":5468},{"class":1890,"line":1900},[5469],{"type":43,"tag":1888,"props":5470,"children":5471},{"emptyLinePlaceholder":1931},[5472],{"type":49,"value":1934},{"type":43,"tag":1888,"props":5474,"children":5475},{"class":1890,"line":1909},[5476],{"type":43,"tag":1888,"props":5477,"children":5478},{},[5479],{"type":49,"value":5480},"judge = ai_client.create_judge(\n",{"type":43,"tag":1888,"props":5482,"children":5483},{"class":1890,"line":1918},[5484],{"type":43,"tag":1888,"props":5485,"children":5486},{},[5487],{"type":49,"value":5488},"    judge_key,                               # judge config key in LD\n",{"type":43,"tag":1888,"props":5490,"children":5491},{"class":1890,"line":1927},[5492],{"type":43,"tag":1888,"props":5493,"children":5494},{},[5495],{"type":49,"value":5496},"    ld_context,\n",{"type":43,"tag":1888,"props":5498,"children":5499},{"class":1890,"line":24},[5500],{"type":43,"tag":1888,"props":5501,"children":5502},{},[5503],{"type":49,"value":5504},"    AIJudgeConfigDefault(enabled=False),     # fallback: skip eval on SDK miss\n",{"type":43,"tag":1888,"props":5506,"children":5507},{"class":1890,"line":1945},[5508],{"type":43,"tag":1888,"props":5509,"children":5510},{},[5511],{"type":49,"value":2810},{"type":43,"tag":1888,"props":5513,"children":5514},{"class":1890,"line":1954},[5515],{"type":43,"tag":1888,"props":5516,"children":5517},{"emptyLinePlaceholder":1931},[5518],{"type":49,"value":1934},{"type":43,"tag":1888,"props":5520,"children":5521},{"class":1890,"line":1963},[5522],{"type":43,"tag":1888,"props":5523,"children":5524},{},[5525],{"type":49,"value":5526},"if judge and judge.enabled:\n",{"type":43,"tag":1888,"props":5528,"children":5529},{"class":1890,"line":1972},[5530],{"type":43,"tag":1888,"props":5531,"children":5532},{},[5533],{"type":49,"value":5534},"    result = await judge.evaluate(\n",{"type":43,"tag":1888,"props":5536,"children":5537},{"class":1890,"line":1981},[5538],{"type":43,"tag":1888,"props":5539,"children":5540},{},[5541],{"type":49,"value":5542},"        input_text,\n",{"type":43,"tag":1888,"props":5544,"children":5545},{"class":1890,"line":1990},[5546],{"type":43,"tag":1888,"props":5547,"children":5548},{},[5549],{"type":49,"value":5550},"        output_text,\n",{"type":43,"tag":1888,"props":5552,"children":5553},{"class":1890,"line":1999},[5554],{"type":43,"tag":1888,"props":5555,"children":5556},{},[5557],{"type":49,"value":5558},"        sampling_rate=0.25,                  # optional; default 1.0 (always eval)\n",{"type":43,"tag":1888,"props":5560,"children":5561},{"class":1890,"line":2008},[5562],{"type":43,"tag":1888,"props":5563,"children":5564},{},[5565],{"type":49,"value":2050},{"type":43,"tag":1888,"props":5567,"children":5568},{"class":1890,"line":2017},[5569],{"type":43,"tag":1888,"props":5570,"children":5571},{},[5572],{"type":49,"value":5573},"    if result.sampled:\n",{"type":43,"tag":1888,"props":5575,"children":5576},{"class":1890,"line":2026},[5577],{"type":43,"tag":1888,"props":5578,"children":5579},{},[5580],{"type":49,"value":5581},"        tracker.track_judge_result(result)\n",{"type":43,"tag":1703,"props":5583,"children":5584},{},[],{"type":49,"value":5586},"Four rules:",{"type":43,"tag":798,"props":5588,"children":5589},{},[5590,5650,5694,5731],{"type":43,"tag":70,"props":5591,"children":5592},{},[5593,5610,5612,5618,5620,5626,5628,5634,5636,5641,5643,5649],{"type":43,"tag":58,"props":5594,"children":5595},{},[5596,5601,5603,5609],{"type":43,"tag":146,"props":5597,"children":5599},{"className":5598},[],[5600],{"type":49,"value":5433},{"type":49,"value":5602}," returns ",{"type":43,"tag":146,"props":5604,"children":5606},{"className":5605},[],[5607],{"type":49,"value":5608},"Optional[Judge]",{"type":49,"value":192},{"type":49,"value":5611}," Always guard with ",{"type":43,"tag":146,"props":5613,"children":5615},{"className":5614},[],[5616],{"type":49,"value":5617},"if judge and judge.enabled:",{"type":49,"value":5619}," — it returns ",{"type":43,"tag":146,"props":5621,"children":5623},{"className":5622},[],[5624],{"type":49,"value":5625},"None",{"type":49,"value":5627}," if the judge config is disabled for the context or the provider is missing. A direct ",{"type":43,"tag":146,"props":5629,"children":5631},{"className":5630},[],[5632],{"type":49,"value":5633},".evaluate()",{"type":49,"value":5635}," on a ",{"type":43,"tag":146,"props":5637,"children":5639},{"className":5638},[],[5640],{"type":49,"value":5625},{"type":49,"value":5642}," return will raise ",{"type":43,"tag":146,"props":5644,"children":5646},{"className":5645},[],[5647],{"type":49,"value":5648},"AttributeError",{"type":49,"value":192},{"type":43,"tag":70,"props":5651,"children":5652},{},[5653,5664,5666,5671,5673,5678,5679,5684,5686,5692],{"type":43,"tag":58,"props":5654,"children":5655},{},[5656,5658],{"type":49,"value":5657},"Pass ",{"type":43,"tag":146,"props":5659,"children":5661},{"className":5660},[],[5662],{"type":49,"value":5663},"AIJudgeConfigDefault",{"type":49,"value":5665},", not ",{"type":43,"tag":146,"props":5667,"children":5669},{"className":5668},[],[5670],{"type":49,"value":2916},{"type":49,"value":5672},". The ",{"type":43,"tag":146,"props":5674,"children":5676},{"className":5675},[],[5677],{"type":49,"value":5433},{"type":49,"value":2651},{"type":43,"tag":146,"props":5680,"children":5682},{"className":5681},[],[5683],{"type":49,"value":5283},{"type":49,"value":5685}," parameter is typed ",{"type":43,"tag":146,"props":5687,"children":5689},{"className":5688},[],[5690],{"type":49,"value":5691},"Optional[AIJudgeConfigDefault]",{"type":49,"value":5693},"; passing the completion type will not type-check and is a doc-level bug in some older examples.",{"type":43,"tag":70,"props":5695,"children":5696},{},[5697,5714,5716,5721,5723,5729],{"type":43,"tag":58,"props":5698,"children":5699},{},[5700,5706,5708],{"type":43,"tag":146,"props":5701,"children":5703},{"className":5702},[],[5704],{"type":49,"value":5705},"sampling_rate",{"type":49,"value":5707}," is a parameter on ",{"type":43,"tag":146,"props":5709,"children":5711},{"className":5710},[],[5712],{"type":49,"value":5713},"evaluate()",{"type":49,"value":5715},", not on ",{"type":43,"tag":146,"props":5717,"children":5719},{"className":5718},[],[5720],{"type":49,"value":5433},{"type":49,"value":5722},". It defaults to ",{"type":43,"tag":146,"props":5724,"children":5726},{"className":5725},[],[5727],{"type":49,"value":5728},"1.0",{"type":49,"value":5730}," (evaluate every call). For live paths, pass something lower (0.1–0.25) to control cost.",{"type":43,"tag":70,"props":5732,"children":5733},{},[5734,5750,5752,5757,5759,5765,5767,5773,5775,5781,5782,5788,5790,5796],{"type":43,"tag":58,"props":5735,"children":5736},{},[5737,5742,5744],{"type":43,"tag":146,"props":5738,"children":5740},{"className":5739},[],[5741],{"type":49,"value":5713},{"type":49,"value":5743}," returns a ",{"type":43,"tag":146,"props":5745,"children":5747},{"className":5746},[],[5748],{"type":49,"value":5749},"JudgeResult",{"type":49,"value":5751}," (never ",{"type":43,"tag":146,"props":5753,"children":5755},{"className":5754},[],[5756],{"type":49,"value":5625},{"type":49,"value":5758},"). Check ",{"type":43,"tag":146,"props":5760,"children":5762},{"className":5761},[],[5763],{"type":49,"value":5764},"result.sampled",{"type":49,"value":5766}," to know whether the evaluation actually ran, and call ",{"type":43,"tag":146,"props":5768,"children":5770},{"className":5769},[],[5771],{"type":49,"value":5772},"track_judge_result(result)",{"type":49,"value":5774},". Node uses ",{"type":43,"tag":146,"props":5776,"children":5778},{"className":5777},[],[5779],{"type":49,"value":5780},"trackJudgeResult(result)",{"type":49,"value":3674},{"type":43,"tag":146,"props":5783,"children":5785},{"className":5784},[],[5786],{"type":49,"value":5787},"LDJudgeResult",{"type":49,"value":5789}," with the same ",{"type":43,"tag":146,"props":5791,"children":5793},{"className":5792},[],[5794],{"type":49,"value":5795},"sampled",{"type":49,"value":5797}," field.",{"type":43,"tag":1703,"props":5799,"children":5800},{},[],{"type":43,"tag":58,"props":5802,"children":5803},{},[5804],{"type":49,"value":5805},"Ask the user which judge config key to use.",{"type":49,"value":5807}," LaunchDarkly ships three built-in judges — Accuracy, Relevance, Toxicity — but the actual config ",{"type":43,"tag":58,"props":5809,"children":5810},{},[5811],{"type":49,"value":5812},"keys",{"type":49,"value":5814}," for the built-ins are not canonical SDK constants and aren't documented. Have the user open ",{"type":43,"tag":58,"props":5816,"children":5817},{},[5818],{"type":49,"value":5819},"AgentControl > Library",{"type":49,"value":5821}," in the LD UI and copy the key of the judge they want to reference, or create a custom judge config via ",{"type":43,"tag":146,"props":5823,"children":5825},{"className":5824},[],[5826],{"type":49,"value":942},{"type":49,"value":5828}," first.",{"type":43,"tag":70,"props":5830,"children":5831},{},[5832,5836],{"type":43,"tag":58,"props":5833,"children":5834},{},[5835],{"type":49,"value":3484},{"type":43,"tag":798,"props":5837,"children":5838},{},[5839,5849,5866],{"type":43,"tag":70,"props":5840,"children":5841},{},[5842,5847],{"type":43,"tag":58,"props":5843,"children":5844},{},[5845],{"type":49,"value":5846},"UI-attached auto judges:",{"type":49,"value":5848}," trigger a request in staging, open the Monitoring tab → \"Evaluator metrics\" dropdown. Scores appear within 1–2 minutes at the configured sampling rate.",{"type":43,"tag":70,"props":5850,"children":5851},{},[5852,5857,5859,5864],{"type":43,"tag":58,"props":5853,"children":5854},{},[5855],{"type":49,"value":5856},"Programmatic direct-judge:",{"type":49,"value":5858}," hit the wrapped endpoint and confirm ",{"type":43,"tag":146,"props":5860,"children":5862},{"className":5861},[],[5863],{"type":49,"value":5447},{"type":49,"value":5865}," lands on the parent config's Monitoring tab.",{"type":43,"tag":70,"props":5867,"children":5868},{},[5869,5874],{"type":43,"tag":58,"props":5870,"children":5871},{},[5872],{"type":49,"value":5873},"Offline eval:",{"type":49,"value":5875}," run the dataset through the LD Playground, compare baseline vs new-variation scores side by side. No runtime wiring required.",{"type":43,"tag":52,"props":5877,"children":5878},{},[5879,5880,5888],{"type":49,"value":3506},{"type":43,"tag":58,"props":5881,"children":5882},{},[5883],{"type":43,"tag":146,"props":5884,"children":5886},{"className":5885},[],[5887],{"type":49,"value":975},{"type":49,"value":5889}," (sub-step 3, optional — only for UI-attached judges or custom-judge creation; offline eval doesn't delegate).",{"type":43,"tag":281,"props":5891,"children":5893},{"id":5892},"edge-cases",[5894],{"type":49,"value":5895},"Edge Cases",{"type":43,"tag":293,"props":5897,"children":5898},{},[5899,5915],{"type":43,"tag":297,"props":5900,"children":5901},{},[5902],{"type":43,"tag":301,"props":5903,"children":5904},{},[5905,5910],{"type":43,"tag":305,"props":5906,"children":5907},{},[5908],{"type":49,"value":5909},"Situation",{"type":43,"tag":305,"props":5911,"children":5912},{},[5913],{"type":49,"value":5914},"Action",{"type":43,"tag":326,"props":5916,"children":5917},{},[5918,5953,6000,6053,6066,6107,6201,6229,6291,6311,6350,6368,6402,6492],{"type":43,"tag":301,"props":5919,"children":5920},{},[5921,5933],{"type":43,"tag":333,"props":5922,"children":5923},{},[5924,5926,5931],{"type":49,"value":5925},"App already initializes ",{"type":43,"tag":146,"props":5927,"children":5929},{"className":5928},[],[5930],{"type":49,"value":1198},{"type":49,"value":5932}," for feature flags",{"type":43,"tag":333,"props":5934,"children":5935},{},[5936,5938,5944,5945,5951],{"type":49,"value":5937},"Reuse it — pass the existing client to ",{"type":43,"tag":146,"props":5939,"children":5941},{"className":5940},[],[5942],{"type":49,"value":5943},"LDAIClient()",{"type":49,"value":600},{"type":43,"tag":146,"props":5946,"children":5948},{"className":5947},[],[5949],{"type":49,"value":5950},"initAi()",{"type":49,"value":5952},", do not create a second client",{"type":43,"tag":301,"props":5954,"children":5955},{},[5956,5967],{"type":43,"tag":333,"props":5957,"children":5958},{},[5959,5961],{"type":49,"value":5960},"App uses LangChain ",{"type":43,"tag":146,"props":5962,"children":5964},{"className":5963},[],[5965],{"type":49,"value":5966},"ChatOpenAI(model=...)",{"type":43,"tag":333,"props":5968,"children":5969},{},[5970,5972,5977,5978,5983,5985,5991,5993,5998],{"type":49,"value":5971},"Replace the hand-rolled model construction with ",{"type":43,"tag":146,"props":5973,"children":5975},{"className":5974},[],[5976],{"type":49,"value":4812},{"type":49,"value":3999},{"type":43,"tag":146,"props":5979,"children":5981},{"className":5980},[],[5982],{"type":49,"value":4819},{"type":49,"value":5984}," (Node). Do not read ",{"type":43,"tag":146,"props":5986,"children":5988},{"className":5987},[],[5989],{"type":49,"value":5990},"config.model.name",{"type":49,"value":5992}," and pass it to ",{"type":43,"tag":146,"props":5994,"children":5996},{"className":5995},[],[5997],{"type":49,"value":5966},{"type":49,"value":5999}," by hand — that pattern drops every variation parameter except the ones you explicitly name",{"type":43,"tag":301,"props":6001,"children":6002},{},[6003,6008],{"type":43,"tag":333,"props":6004,"children":6005},{},[6006],{"type":49,"value":6007},"Retry wrapper around the provider call",{"type":43,"tag":333,"props":6009,"children":6010},{},[6011,6013,6018,6020,6025,6026,6031,6032,6037,6038,6044,6046,6051],{"type":49,"value":6012},"The tracker is minted once at the top of the user turn; the retry loop is inside that scope. Every retry attempt shares the same ",{"type":43,"tag":146,"props":6014,"children":6016},{"className":6015},[],[6017],{"type":49,"value":175},{"type":49,"value":6019},". Tracker calls (",{"type":43,"tag":146,"props":6021,"children":6023},{"className":6022},[],[6024],{"type":49,"value":4109},{"type":49,"value":600},{"type":43,"tag":146,"props":6027,"children":6029},{"className":6028},[],[6030],{"type":49,"value":4116},{"type":49,"value":600},{"type":43,"tag":146,"props":6033,"children":6035},{"className":6034},[],[6036],{"type":49,"value":4123},{"type":49,"value":600},{"type":43,"tag":146,"props":6039,"children":6041},{"className":6040},[],[6042],{"type":49,"value":6043},"track_error",{"type":49,"value":6045},") live ",{"type":43,"tag":1008,"props":6047,"children":6048},{},[6049],{"type":49,"value":6050},"outside",{"type":49,"value":6052}," the retry body — one call at the end of the turn, on the success path or the final-failure path",{"type":43,"tag":301,"props":6054,"children":6055},{},[6056,6061],{"type":43,"tag":333,"props":6057,"children":6058},{},[6059],{"type":49,"value":6060},"App has no tools — Stage 3 skipped",{"type":43,"tag":333,"props":6062,"children":6063},{},[6064],{"type":49,"value":6065},"Move directly from Stage 2 verification to Stage 4 (tracking)",{"type":43,"tag":301,"props":6067,"children":6068},{},[6069,6074],{"type":43,"tag":333,"props":6070,"children":6071},{},[6072],{"type":49,"value":6073},"Mode mismatch: user said agent, audit shows one-shot chat",{"type":43,"tag":333,"props":6075,"children":6076},{},[6077,6079,6084,6086,6091,6093,6098,6100,6105],{"type":49,"value":6078},"Choose completion mode unless the app uses a LangGraph prebuilt agent (",{"type":43,"tag":146,"props":6080,"children":6082},{"className":6081},[],[6083],{"type":49,"value":436},{"type":49,"value":6085}," in Python or ",{"type":43,"tag":146,"props":6087,"children":6089},{"className":6088},[],[6090],{"type":49,"value":444},{"type":49,"value":6092}," in Node), CrewAI ",{"type":43,"tag":146,"props":6094,"children":6096},{"className":6095},[],[6097],{"type":49,"value":512},{"type":49,"value":6099},", Strands ",{"type":43,"tag":146,"props":6101,"children":6103},{"className":6102},[],[6104],{"type":49,"value":512},{"type":49,"value":6106},", or a similar goal-driven framework",{"type":43,"tag":301,"props":6108,"children":6109},{},[6110,6115],{"type":43,"tag":333,"props":6111,"children":6112},{},[6113],{"type":49,"value":6114},"App uses Strands Agents (Python)",{"type":43,"tag":333,"props":6116,"children":6117},{},[6118,6120,6126,6128,6134,6136,6142,6143,6149,6151,6157,6159,6164,6166,6172,6174,6180,6182,6188,6189,6194,6195],{"type":49,"value":6119},"Agent mode. Build a ",{"type":43,"tag":146,"props":6121,"children":6123},{"className":6122},[],[6124],{"type":49,"value":6125},"create_strands_model",{"type":49,"value":6127}," dispatcher keyed on ",{"type":43,"tag":146,"props":6129,"children":6131},{"className":6130},[],[6132],{"type":49,"value":6133},"agent_config.provider.name",{"type":49,"value":6135}," that returns ",{"type":43,"tag":146,"props":6137,"children":6139},{"className":6138},[],[6140],{"type":49,"value":6141},"AnthropicModel(model_id=..., max_tokens=...)",{"type":49,"value":1200},{"type":43,"tag":146,"props":6144,"children":6146},{"className":6145},[],[6147],{"type":49,"value":6148},"OpenAIModel(model_id=..., params=...)",{"type":49,"value":6150},". Drop ",{"type":43,"tag":146,"props":6152,"children":6154},{"className":6153},[],[6155],{"type":49,"value":6156},"parameters.tools",{"type":49,"value":6158}," before passing params to the model class — Strands receives tools via ",{"type":43,"tag":146,"props":6160,"children":6162},{"className":6161},[],[6163],{"type":49,"value":3618},{"type":49,"value":6165},". Tracking is Tier 3: wrap ",{"type":43,"tag":146,"props":6167,"children":6169},{"className":6168},[],[6170],{"type":49,"value":6171},"invoke_async",{"type":49,"value":6173}," with ",{"type":43,"tag":146,"props":6175,"children":6177},{"className":6176},[],[6178],{"type":49,"value":6179},"tracker.track_duration_of(...)",{"type":49,"value":6181}," and record tokens from ",{"type":43,"tag":146,"props":6183,"children":6185},{"className":6184},[],[6186],{"type":49,"value":6187},"result.metrics.accumulated_usage",{"type":49,"value":4836},{"type":43,"tag":179,"props":6190,"children":6191},{"href":181},[6192],{"type":49,"value":6193},"agent-mode-frameworks.md § Strands Agent",{"type":49,"value":3674},{"type":43,"tag":179,"props":6196,"children":6198},{"href":6197},"..\u002Fbuilt-in-metrics\u002Freferences\u002Fstrands-tracking.md",[6199],{"type":49,"value":6200},"strands-tracking.md",{"type":43,"tag":301,"props":6202,"children":6203},{},[6204,6209],{"type":43,"tag":333,"props":6205,"children":6206},{},[6207],{"type":49,"value":6208},"Strands app on TypeScript",{"type":43,"tag":333,"props":6210,"children":6211},{},[6212,6214,6220,6221,6227],{"type":49,"value":6213},"TS SDK ships ",{"type":43,"tag":146,"props":6215,"children":6217},{"className":6216},[],[6218],{"type":49,"value":6219},"BedrockModel",{"type":49,"value":3674},{"type":43,"tag":146,"props":6222,"children":6224},{"className":6223},[],[6225],{"type":49,"value":6226},"OpenAIModel",{"type":49,"value":6228}," only — cannot serve Anthropic-backed variations. Use the Python SDK if multi-provider variations are required",{"type":43,"tag":301,"props":6230,"children":6231},{},[6232,6237],{"type":43,"tag":333,"props":6233,"children":6234},{},[6235],{"type":49,"value":6236},"TypeScript app using Anthropic SDK",{"type":43,"tag":333,"props":6238,"children":6239},{},[6240,6242,6248,6250,6255,6257,6263,6264,6270,6272,6277,6278,6282,6284,6289],{"type":49,"value":6241},"No ",{"type":43,"tag":146,"props":6243,"children":6245},{"className":6244},[],[6246],{"type":49,"value":6247},"trackAnthropicMetrics",{"type":49,"value":6249}," helper exists. Use Tier 3: ",{"type":43,"tag":146,"props":6251,"children":6253},{"className":6252},[],[6254],{"type":49,"value":4521},{"type":49,"value":6256}," with a small custom extractor that reads ",{"type":43,"tag":146,"props":6258,"children":6260},{"className":6259},[],[6261],{"type":49,"value":6262},"response.usage.input_tokens",{"type":49,"value":600},{"type":43,"tag":146,"props":6265,"children":6267},{"className":6266},[],[6268],{"type":49,"value":6269},"response.usage.output_tokens",{"type":49,"value":6271}," and returns ",{"type":43,"tag":146,"props":6273,"children":6275},{"className":6274},[],[6276],{"type":49,"value":4766},{"type":49,"value":4836},{"type":43,"tag":179,"props":6279,"children":6280},{"href":4771},[6281],{"type":49,"value":4774},{"type":49,"value":6283}," in the ",{"type":43,"tag":146,"props":6285,"children":6287},{"className":6286},[],[6288],{"type":49,"value":626},{"type":49,"value":6290}," skill for the exact extractor",{"type":43,"tag":301,"props":6292,"children":6293},{},[6294,6306],{"type":43,"tag":333,"props":6295,"children":6296},{},[6297,6299,6304],{"type":49,"value":6298},"Fallback would silently crash because ",{"type":43,"tag":146,"props":6300,"children":6302},{"className":6301},[],[6303],{"type":49,"value":809},{"type":49,"value":6305}," is missing",{"type":43,"tag":333,"props":6307,"children":6308},{},[6309],{"type":49,"value":6310},"Log a startup warning; proceed with the fallback. Never raise at import time",{"type":43,"tag":301,"props":6312,"children":6313},{},[6314,6318],{"type":43,"tag":333,"props":6315,"children":6316},{},[6317],{"type":49,"value":678},{"type":43,"tag":333,"props":6319,"children":6320},{},[6321,6323,6327,6329,6335,6337,6342,6344,6348],{"type":49,"value":6322},"Stop after migrating a single agent. Agent Graph Definitions are available in ",{"type":43,"tag":58,"props":6324,"children":6325},{},[6326],{"type":49,"value":2642},{"type":49,"value":6328}," SDKs — Python via ",{"type":43,"tag":146,"props":6330,"children":6332},{"className":6331},[],[6333],{"type":49,"value":6334},"launchdarkly-server-sdk-ai.agent_graph",{"type":49,"value":6336}," and Node via the graph API in ",{"type":43,"tag":146,"props":6338,"children":6340},{"className":6339},[],[6341],{"type":49,"value":2176},{"type":49,"value":6343},". Read ",{"type":43,"tag":179,"props":6345,"children":6346},{"href":693},[6347],{"type":49,"value":696},{"type":49,"value":6349}," for the graph-level migration path — it is deliberately out of this skill's main scope",{"type":43,"tag":301,"props":6351,"children":6352},{},[6353,6358],{"type":43,"tag":333,"props":6354,"children":6355},{},[6356],{"type":49,"value":6357},"Single-agent (ReAct, tool loop) + agent mode",{"type":43,"tag":333,"props":6359,"children":6360},{},[6361,6363],{"type":49,"value":6362},"Default to offline eval via the LD Playground + Datasets for Stage 5. UI-attached judges are completion-only today, and programmatic direct-judge adds per-call cost that is usually not worth it until after the migration is live and stable. Point at the ",{"type":43,"tag":179,"props":6364,"children":6366},{"href":5255,"rel":6365},[852],[6367],{"type":49,"value":5259},{"type":43,"tag":301,"props":6369,"children":6370},{},[6371,6390],{"type":43,"tag":333,"props":6372,"children":6373},{},[6374,6376,6382,6384,6389],{"type":49,"value":6375},"Tool with a Pydantic ",{"type":43,"tag":146,"props":6377,"children":6379},{"className":6378},[],[6380],{"type":49,"value":6381},"args_schema",{"type":49,"value":6383}," (LangChain ",{"type":43,"tag":146,"props":6385,"children":6387},{"className":6386},[],[6388],{"type":49,"value":3636},{"type":49,"value":379},{"type":43,"tag":333,"props":6391,"children":6392},{},[6393,6395,6400],{"type":49,"value":6394},"Extract the schema via ",{"type":43,"tag":146,"props":6396,"children":6398},{"className":6397},[],[6399],{"type":49,"value":3735},{"type":49,"value":6401},"; do not hand-write the JSON schema for the delegate",{"type":43,"tag":301,"props":6403,"children":6404},{},[6405,6445],{"type":43,"tag":333,"props":6406,"children":6407},{},[6408,6409,6414,6416,6422,6424,6429,6431,6436,6438,6443],{"type":49,"value":3646},{"type":43,"tag":146,"props":6410,"children":6412},{"className":6411},[],[6413],{"type":49,"value":190},{"type":49,"value":6415}," with module-level ",{"type":43,"tag":146,"props":6417,"children":6419},{"className":6418},[],[6420],{"type":49,"value":6421},"TOOLS",{"type":49,"value":6423}," list bound via ",{"type":43,"tag":146,"props":6425,"children":6427},{"className":6426},[],[6428],{"type":49,"value":3851},{"type":49,"value":6430}," and run through ",{"type":43,"tag":146,"props":6432,"children":6434},{"className":6433},[],[6435],{"type":49,"value":3680},{"type":49,"value":6437}," (e.g. the ",{"type":43,"tag":146,"props":6439,"children":6441},{"className":6440},[],[6442],{"type":49,"value":214},{"type":49,"value":6444}," template)",{"type":43,"tag":333,"props":6446,"children":6447},{},[6448,6450,6455,6457,6462,6464,6468,6470,6476,6477,6483,6485,6490],{"type":49,"value":6449},"Find the ",{"type":43,"tag":146,"props":6451,"children":6453},{"className":6452},[],[6454],{"type":49,"value":6421},{"type":49,"value":6456}," list (usually in a separate ",{"type":43,"tag":146,"props":6458,"children":6460},{"className":6459},[],[6461],{"type":49,"value":3695},{"type":49,"value":6463}," module). Extract schemas the same way. Swap ",{"type":43,"tag":58,"props":6465,"children":6466},{},[6467],{"type":49,"value":2642},{"type":49,"value":6469}," call sites — ",{"type":43,"tag":146,"props":6471,"children":6473},{"className":6472},[],[6474],{"type":49,"value":6475},".bind_tools(...)",{"type":49,"value":3674},{"type":43,"tag":146,"props":6478,"children":6480},{"className":6479},[],[6481],{"type":49,"value":6482},"ToolNode(...)",{"type":49,"value":6484}," — to read from the same ",{"type":43,"tag":146,"props":6486,"children":6488},{"className":6487},[],[6489],{"type":49,"value":3813},{"type":49,"value":6491},"-derived list",{"type":43,"tag":301,"props":6493,"children":6494},{},[6495,6523],{"type":43,"tag":333,"props":6496,"children":6497},{},[6498,6500,6506,6508,6514,6516,6522],{"type":49,"value":6499},"App has already externalized config into a ",{"type":43,"tag":146,"props":6501,"children":6503},{"className":6502},[],[6504],{"type":49,"value":6505},"Context",{"type":49,"value":6507}," dataclass with env-var fallback (e.g. ",{"type":43,"tag":146,"props":6509,"children":6511},{"className":6510},[],[6512],{"type":49,"value":6513},"react-agent",{"type":49,"value":6515}," template's ",{"type":43,"tag":146,"props":6517,"children":6519},{"className":6518},[],[6520],{"type":49,"value":6521},"context.py",{"type":49,"value":379},{"type":43,"tag":333,"props":6524,"children":6525},{},[6526,6528,6534,6535,6541,6542,6548,6550,6556,6558,6563,6565,6571,6573,6578,6580,6585,6587,6593,6595,6601,6603,6609,6611,6620],{"type":49,"value":6527},"Replace the consumers of ",{"type":43,"tag":146,"props":6529,"children":6531},{"className":6530},[],[6532],{"type":49,"value":6533},"runtime.context.model",{"type":49,"value":600},{"type":43,"tag":146,"props":6536,"children":6538},{"className":6537},[],[6539],{"type":49,"value":6540},"runtime.context.system_prompt",{"type":49,"value":6173},{"type":43,"tag":146,"props":6543,"children":6545},{"className":6544},[],[6546],{"type":49,"value":6547},"ai_client.agent_config(...)",{"type":49,"value":6549}," and read from the returned ",{"type":43,"tag":146,"props":6551,"children":6553},{"className":6552},[],[6554],{"type":49,"value":6555},"AIAgentConfig",{"type":49,"value":6557},". ",{"type":43,"tag":58,"props":6559,"children":6560},{},[6561],{"type":49,"value":6562},"Empty the dataclass",{"type":49,"value":6564}," rather than keeping it as the fallback shape — the canonical fallback is ",{"type":43,"tag":146,"props":6566,"children":6568},{"className":6567},[],[6569],{"type":49,"value":6570},"FALLBACK = AIAgentConfigDefault(...)",{"type":49,"value":6572}," in Python (a top-level constant near the ",{"type":43,"tag":146,"props":6574,"children":6576},{"className":6575},[],[6577],{"type":49,"value":775},{"type":49,"value":6579}," call), not a parallel Python dataclass. Two sources of truth for fallback values drift. An empty ",{"type":43,"tag":146,"props":6581,"children":6583},{"className":6582},[],[6584],{"type":49,"value":6505},{"type":49,"value":6586}," is a placeholder satisfying LangGraph's ",{"type":43,"tag":146,"props":6588,"children":6590},{"className":6589},[],[6591],{"type":49,"value":6592},"context_schema",{"type":49,"value":6594}," requirement only; ",{"type":43,"tag":146,"props":6596,"children":6598},{"className":6597},[],[6599],{"type":49,"value":6600},"thread_id",{"type":49,"value":6602}," and any other per-request plumbing comes through ",{"type":43,"tag":146,"props":6604,"children":6606},{"className":6605},[],[6607],{"type":49,"value":6608},"config: RunnableConfig",{"type":49,"value":6610}," instead (see ",{"type":43,"tag":179,"props":6612,"children":6613},{"href":181},[6614,6615],{"type":49,"value":184},{"type":43,"tag":146,"props":6616,"children":6618},{"className":6617},[],[6619],{"type":49,"value":190},{"type":49,"value":379},{"type":43,"tag":281,"props":6622,"children":6624},{"id":6623},"what-not-to-do",[6625],{"type":49,"value":6626},"What NOT to Do",{"type":43,"tag":52,"props":6628,"children":6629},{},[6630],{"type":49,"value":6631},"These are ordered by how likely they are to show up as a first-run failure. The first three rules — about tracker and config lifetime — account for most of the \"migration looks done but the Monitoring tab is fragmented \u002F wrong\" reports.",{"type":43,"tag":1100,"props":6633,"children":6635},{"id":6634},"tracker-and-config-lifetime-most-common-failure-mode",[6636],{"type":49,"value":6637},"Tracker and config lifetime (most common failure mode)",{"type":43,"tag":798,"props":6639,"children":6640},{},[6641,6665,6740,6785,6797,6809],{"type":43,"tag":70,"props":6642,"children":6643},{},[6644,6663],{"type":43,"tag":58,"props":6645,"children":6646},{},[6647,6649,6654,6655,6661],{"type":49,"value":6648},"Don't call ",{"type":43,"tag":146,"props":6650,"children":6652},{"className":6651},[],[6653],{"type":49,"value":151},{"type":49,"value":600},{"type":43,"tag":146,"props":6656,"children":6658},{"className":6657},[],[6659],{"type":49,"value":6660},"createTracker()",{"type":49,"value":6662}," more than once per user turn.",{"type":49,"value":6664}," One turn = the full request\u002Fresponse cycle including every ReAct iteration, tool call, and retry. See Stage 4 Step 1 for the canonical placement in each app shape (completion \u002F agent loop \u002F managed runner).",{"type":43,"tag":70,"props":6666,"children":6667},{},[6668,6704,6706,6712,6713,6719,6720,6726,6727,6732,6734,6739],{"type":43,"tag":58,"props":6669,"children":6670},{},[6671,6672,6677,6678,6683,6684,6689,6690,6695,6696,6702],{"type":49,"value":6648},{"type":43,"tag":146,"props":6673,"children":6675},{"className":6674},[],[6676],{"type":49,"value":4109},{"type":49,"value":600},{"type":43,"tag":146,"props":6679,"children":6681},{"className":6680},[],[6682],{"type":49,"value":4116},{"type":49,"value":600},{"type":43,"tag":146,"props":6685,"children":6687},{"className":6686},[],[6688],{"type":49,"value":4123},{"type":49,"value":600},{"type":43,"tag":146,"props":6691,"children":6693},{"className":6692},[],[6694],{"type":49,"value":6043},{"type":49,"value":600},{"type":43,"tag":146,"props":6697,"children":6699},{"className":6698},[],[6700],{"type":49,"value":6701},"track_time_to_first_token",{"type":49,"value":6703}," inside a loop body.",{"type":49,"value":6705}," These are at-most-once per tracker; second calls are dropped. Accumulate inside the loop, emit once in a terminal\u002Ffinalize node. Per-event methods (",{"type":43,"tag":146,"props":6707,"children":6709},{"className":6708},[],[6710],{"type":49,"value":6711},"track_tool_call",{"type":49,"value":1065},{"type":43,"tag":146,"props":6714,"children":6716},{"className":6715},[],[6717],{"type":49,"value":6718},"track_tool_calls",{"type":49,"value":1065},{"type":43,"tag":146,"props":6721,"children":6723},{"className":6722},[],[6724],{"type":49,"value":6725},"track_feedback",{"type":49,"value":1065},{"type":43,"tag":146,"props":6728,"children":6730},{"className":6729},[],[6731],{"type":49,"value":5447},{"type":49,"value":6733},") are safe to call repeatedly. Full matrix: ",{"type":43,"tag":179,"props":6735,"children":6736},{"href":3955},[6737],{"type":49,"value":6738},"sdk-ai-tracker-patterns.md § At-most-once guards",{"type":49,"value":192},{"type":43,"tag":70,"props":6741,"children":6742},{},[6743,6761,6763,6769,6771,6777,6779,6784],{"type":43,"tag":58,"props":6744,"children":6745},{},[6746,6747,6753,6754,6760],{"type":49,"value":6648},{"type":43,"tag":146,"props":6748,"children":6750},{"className":6749},[],[6751],{"type":49,"value":6752},"agent_config()",{"type":49,"value":600},{"type":43,"tag":146,"props":6755,"children":6757},{"className":6756},[],[6758],{"type":49,"value":6759},"completion_config()",{"type":49,"value":6662},{"type":49,"value":6762}," Each call is a flag evaluation and emits a ",{"type":43,"tag":146,"props":6764,"children":6766},{"className":6765},[],[6767],{"type":49,"value":6768},"$ld:ai:agent:config",{"type":49,"value":6770}," event. Re-fetching inside a loop step or a tool body inflates agent-config counts on the Monitoring tab and lets a mid-turn targeting change swap the variation between LLM calls in a single turn. Resolve once at the top, stash on state, and have every subsequent consumer read from state. Tools that need variation-scoped knobs should use the tool-factory pattern (",{"type":43,"tag":146,"props":6772,"children":6774},{"className":6773},[],[6775],{"type":49,"value":6776},"make_search(ai_config)",{"type":49,"value":6778}," that closes over the knob at setup time) — see ",{"type":43,"tag":179,"props":6780,"children":6781},{"href":181},[6782],{"type":49,"value":6783},"agent-mode-frameworks.md § Getting knobs into tools",{"type":49,"value":192},{"type":43,"tag":70,"props":6786,"children":6787},{},[6788,6790,6795],{"type":49,"value":6789},"Don't cache the config object ",{"type":43,"tag":1008,"props":6791,"children":6792},{},[6793],{"type":49,"value":6794},"across",{"type":49,"value":6796}," requests — resolve once per turn, yes, but still resolve once per turn. Caching at module scope defeats the targeting-change mechanism entirely.",{"type":43,"tag":70,"props":6798,"children":6799},{},[6800,6802,6807],{"type":49,"value":6801},"Don't delete the fallback once LaunchDarkly is wired up. It is required for the ",{"type":43,"tag":146,"props":6803,"children":6805},{"className":6804},[],[6806],{"type":49,"value":269},{"type":49,"value":6808}," and SDK-unreachable paths.",{"type":43,"tag":70,"props":6810,"children":6811},{},[6812,6814,6819,6820,6825,6826,6831,6832,6837,6839,6844,6846,6851,6852,6858,6860,6866,6868,6874,6875,6881,6883,6889],{"type":49,"value":6813},"Don't tuple-unpack the return of ",{"type":43,"tag":146,"props":6815,"children":6817},{"className":6816},[],[6818],{"type":49,"value":1030},{"type":49,"value":600},{"type":43,"tag":146,"props":6821,"children":6823},{"className":6822},[],[6824],{"type":49,"value":775},{"type":49,"value":600},{"type":43,"tag":146,"props":6827,"children":6829},{"className":6828},[],[6830],{"type":49,"value":3031},{"type":49,"value":600},{"type":43,"tag":146,"props":6833,"children":6835},{"className":6834},[],[6836],{"type":49,"value":3045},{"type":49,"value":6838},". They return a ",{"type":43,"tag":58,"props":6840,"children":6841},{},[6842],{"type":49,"value":6843},"single",{"type":49,"value":6845}," config object (e.g. ",{"type":43,"tag":146,"props":6847,"children":6849},{"className":6848},[],[6850],{"type":49,"value":6555},{"type":49,"value":1065},{"type":43,"tag":146,"props":6853,"children":6855},{"className":6854},[],[6856],{"type":49,"value":6857},"AICompletionConfig",{"type":49,"value":6859},"), not ",{"type":43,"tag":146,"props":6861,"children":6863},{"className":6862},[],[6864],{"type":49,"value":6865},"(config, tracker)",{"type":49,"value":6867},". Obtain the tracker by calling ",{"type":43,"tag":146,"props":6869,"children":6871},{"className":6870},[],[6872],{"type":49,"value":6873},"config.create_tracker()",{"type":49,"value":600},{"type":43,"tag":146,"props":6876,"children":6878},{"className":6877},[],[6879],{"type":49,"value":6880},"aiConfig.createTracker()",{"type":49,"value":6882},". LLMs hallucinate both the tuple shape and a ",{"type":43,"tag":146,"props":6884,"children":6886},{"className":6885},[],[6887],{"type":49,"value":6888},"config.tracker",{"type":49,"value":6890}," property — the actual API is a factory.",{"type":43,"tag":1100,"props":6892,"children":6894},{"id":6893},"langchain-langgraph-patterns-second-most-common-failure-mode",[6895],{"type":49,"value":6896},"LangChain \u002F LangGraph patterns (second most common failure mode)",{"type":43,"tag":798,"props":6898,"children":6899},{},[6900,6976,7076,7183,7196],{"type":43,"tag":70,"props":6901,"children":6902},{},[6903,6915,6917,6922,6924,6930,6931,6937,6939,6945,6947,6953,6955,6960,6962,6967,6969,6974],{"type":43,"tag":58,"props":6904,"children":6905},{},[6906,6908,6913],{"type":49,"value":6907},"If the repo already contains a ",{"type":43,"tag":146,"props":6909,"children":6911},{"className":6910},[],[6912],{"type":49,"value":222},{"type":49,"value":6914}," helper, delete it — don't just avoid using it.",{"type":49,"value":6916}," This exact shape ships with ",{"type":43,"tag":146,"props":6918,"children":6920},{"className":6919},[],[6921],{"type":49,"value":214},{"type":49,"value":6923}," and is copied into dozens of derivative repos; look for ",{"type":43,"tag":146,"props":6925,"children":6927},{"className":6926},[],[6928],{"type":49,"value":6929},"utils.load_chat_model",{"type":49,"value":1065},{"type":43,"tag":146,"props":6932,"children":6934},{"className":6933},[],[6935],{"type":49,"value":6936},"utils.build_model",{"type":49,"value":6938},", or any one-arg ",{"type":43,"tag":146,"props":6940,"children":6942},{"className":6941},[],[6943],{"type":49,"value":6944},"init_chat_model",{"type":49,"value":6946}," wrapper that splits a ",{"type":43,"tag":146,"props":6948,"children":6950},{"className":6949},[],[6951],{"type":49,"value":6952},"\"provider\u002Fmodel\"",{"type":49,"value":6954}," string. Re-using it is the first-run failure mode: every variation parameter (temperature, max_tokens, top_p, stop sequences) silently drops on the floor because ",{"type":43,"tag":146,"props":6956,"children":6958},{"className":6957},[],[6959],{"type":49,"value":6944},{"type":49,"value":6961}," only receives the name and provider. ",{"type":43,"tag":146,"props":6963,"children":6965},{"className":6964},[],[6966],{"type":49,"value":245},{"type":49,"value":6968}," is a one-for-one replacement that forwards the whole ",{"type":43,"tag":146,"props":6970,"children":6972},{"className":6971},[],[6973],{"type":49,"value":1361},{"type":49,"value":6975}," dict. Replace every call site, then delete the wrapper file-side so the next reader can't reach for it.",{"type":43,"tag":70,"props":6977,"children":6978},{},[6979,7003,7005,7011,7013,7018,7020,7026,7028,7034,7036,7042,7044,7050,7052,7058,7060,7066,7068,7074],{"type":43,"tag":58,"props":6980,"children":6981},{},[6982,6984,6989,6990,6995,6996,7001],{"type":49,"value":6983},"Same rule applies to hand-rolled ",{"type":43,"tag":146,"props":6985,"children":6987},{"className":6986},[],[6988],{"type":49,"value":1675},{"type":49,"value":600},{"type":43,"tag":146,"props":6991,"children":6993},{"className":6992},[],[6994],{"type":49,"value":1682},{"type":49,"value":600},{"type":43,"tag":146,"props":6997,"children":6999},{"className":6998},[],[7000],{"type":49,"value":1689},{"type":49,"value":7002}," helpers.",{"type":49,"value":7004}," If the template already has a ",{"type":43,"tag":146,"props":7006,"children":7008},{"className":7007},[],[7009],{"type":49,"value":7010},"resolve_tools(tool_keys)",{"type":49,"value":7012}," or an ",{"type":43,"tag":146,"props":7014,"children":7016},{"className":7015},[],[7017],{"type":49,"value":1689},{"type":49,"value":7019}," module-level list, import ",{"type":43,"tag":146,"props":7021,"children":7023},{"className":7022},[],[7024],{"type":49,"value":7025},"build_structured_tools",{"type":49,"value":7027}," from ",{"type":43,"tag":146,"props":7029,"children":7031},{"className":7030},[],[7032],{"type":49,"value":7033},"ldai_langchain.langchain_helper",{"type":49,"value":7035}," and delete the hand-rolled version. ",{"type":43,"tag":146,"props":7037,"children":7039},{"className":7038},[],[7040],{"type":49,"value":7041},"build_structured_tools(ai_config, TOOL_REGISTRY_DICT)",{"type":49,"value":7043}," reads ",{"type":43,"tag":146,"props":7045,"children":7047},{"className":7046},[],[7048],{"type":49,"value":7049},"ai_config.model.parameters.tools",{"type":49,"value":7051}," and wraps the matching callables as LangChain ",{"type":43,"tag":146,"props":7053,"children":7055},{"className":7054},[],[7056],{"type":49,"value":7057},"StructuredTool",{"type":49,"value":7059},"s with the LD tool key as the ",{"type":43,"tag":146,"props":7061,"children":7063},{"className":7062},[],[7064],{"type":49,"value":7065},"StructuredTool.name",{"type":49,"value":7067}," — so ",{"type":43,"tag":146,"props":7069,"children":7071},{"className":7070},[],[7072],{"type":49,"value":7073},"ToolNode",{"type":49,"value":7075}," lookup works without a second mapping. Don't leave both in the repo.",{"type":43,"tag":70,"props":7077,"children":7078},{},[7079,7081,7086,7087,7093,7095,7101,7103,7108,7110,7116,7117,7123,7124,7130,7132,7137,7139,7145,7147,7152,7154,7160,7162,7167,7169,7175,7177,7182],{"type":49,"value":7080},"Don't put app-scoped knobs directly in ",{"type":43,"tag":146,"props":7082,"children":7084},{"className":7083},[],[7085],{"type":49,"value":1361},{"type":49,"value":6557},{"type":43,"tag":146,"props":7088,"children":7090},{"className":7089},[],[7091],{"type":49,"value":7092},"create_langchain_model",{"type":49,"value":7094}," forwards every key in ",{"type":43,"tag":146,"props":7096,"children":7098},{"className":7097},[],[7099],{"type":49,"value":7100},"parameters",{"type":49,"value":7102}," to the provider SDK via ",{"type":43,"tag":146,"props":7104,"children":7106},{"className":7105},[],[7107],{"type":49,"value":6944},{"type":49,"value":7109},", so a ",{"type":43,"tag":146,"props":7111,"children":7113},{"className":7112},[],[7114],{"type":49,"value":7115},"max_search_results",{"type":49,"value":600},{"type":43,"tag":146,"props":7118,"children":7120},{"className":7119},[],[7121],{"type":49,"value":7122},"retry_budget",{"type":49,"value":600},{"type":43,"tag":146,"props":7125,"children":7127},{"className":7126},[],[7128],{"type":49,"value":7129},"feature_toggle",{"type":49,"value":7131}," entry will crash the provider with an unexpected-keyword-argument error. The correct home is ",{"type":43,"tag":146,"props":7133,"children":7135},{"className":7134},[],[7136],{"type":49,"value":1353},{"type":49,"value":7138},", which the provider helpers ignore and the app reads via ",{"type":43,"tag":146,"props":7140,"children":7142},{"className":7141},[],[7143],{"type":49,"value":7144},"ai_config.model.get_custom(\"key\")",{"type":49,"value":7146},". The MCP ",{"type":43,"tag":146,"props":7148,"children":7150},{"className":7149},[],[7151],{"type":49,"value":3795},{"type":49,"value":7153}," tool does not currently expose top-level ",{"type":43,"tag":146,"props":7155,"children":7157},{"className":7156},[],[7158],{"type":49,"value":7159},"custom",{"type":49,"value":7161},", so pick one of two paths: (a) PATCH the variation via the REST API to set ",{"type":43,"tag":146,"props":7163,"children":7165},{"className":7164},[],[7166],{"type":49,"value":1353},{"type":49,"value":7168}," directly, or (b) set it via MCP inside ",{"type":43,"tag":146,"props":7170,"children":7172},{"className":7171},[],[7173],{"type":49,"value":7174},"parameters.custom",{"type":49,"value":7176}," (as a nested dict) and use a defensive accessor that reads both locations. Full walk-through with code samples in ",{"type":43,"tag":179,"props":7178,"children":7179},{"href":419},[7180],{"type":49,"value":7181},"langchain-tracking.md § MCP caveat",{"type":49,"value":192},{"type":43,"tag":70,"props":7184,"children":7185},{},[7186,7188,7194],{"type":49,"value":7187},"Don't re-encode tool schemas inside the fallback. When LaunchDarkly is unreachable the fallback should run without tools (or with whatever minimal provider-bound parameters the app needs to keep operating). Building a ",{"type":43,"tag":146,"props":7189,"children":7191},{"className":7190},[],[7192],{"type":49,"value":7193},"_FALLBACK_TOOLS",{"type":49,"value":7195}," array that duplicates the config's tool schema re-introduces the hardcoded config the migration was supposed to move out of code.",{"type":43,"tag":70,"props":7197,"children":7198},{},[7199,7201,7207,7208,7214,7216,7222,7224,7229,7230,7236],{"type":49,"value":7200},"Don't import ",{"type":43,"tag":146,"props":7202,"children":7204},{"className":7203},[],[7205],{"type":49,"value":7206},"LaunchDarklyCallbackHandler",{"type":49,"value":7027},{"type":43,"tag":146,"props":7209,"children":7211},{"className":7210},[],[7212],{"type":49,"value":7213},"ldai.langchain",{"type":49,"value":7215}," — neither the class nor the dotted module path exists. The Python LangChain helper package is ",{"type":43,"tag":146,"props":7217,"children":7219},{"className":7218},[],[7220],{"type":49,"value":7221},"ldai_langchain",{"type":49,"value":7223}," (top-level module, underscore). Use ",{"type":43,"tag":146,"props":7225,"children":7227},{"className":7226},[],[7228],{"type":49,"value":4812},{"type":49,"value":1732},{"type":43,"tag":146,"props":7231,"children":7233},{"className":7232},[],[7234],{"type":49,"value":7235},"track_metrics_of_async(get_ai_metrics_from_response, lambda: llm.ainvoke(messages))",{"type":49,"value":7237}," as the canonical pattern.",{"type":43,"tag":1100,"props":7239,"children":7241},{"id":7240},"stage-handoff-discipline",[7242],{"type":49,"value":7243},"Stage \u002F handoff discipline",{"type":43,"tag":798,"props":7245,"children":7246},{},[7247,7252,7264,7283,7295,7314],{"type":43,"tag":70,"props":7248,"children":7249},{},[7250],{"type":49,"value":7251},"Don't skip Step 1 even when the user says \"just wrap it.\" Without the audit, the fallback will drift from the hardcoded behavior.",{"type":43,"tag":70,"props":7253,"children":7254},{},[7255,7257,7262],{"type":49,"value":7256},"Don't delegate to ",{"type":43,"tag":146,"props":7258,"children":7260},{"className":7259},[],[7261],{"type":49,"value":942},{"type":49,"value":7263}," before extracting the prompt and model — the delegate needs them as inputs.",{"type":43,"tag":70,"props":7265,"children":7266},{},[7267,7269,7275,7277,7282],{"type":49,"value":7268},"Don't try to attach tools during initial ",{"type":43,"tag":146,"props":7270,"children":7272},{"className":7271},[],[7273],{"type":49,"value":7274},"setup-ai-config",{"type":49,"value":7276},". Tool attachment is a separate step owned by ",{"type":43,"tag":146,"props":7278,"children":7280},{"className":7279},[],[7281],{"type":49,"value":953},{"type":49,"value":192},{"type":43,"tag":70,"props":7284,"children":7285},{},[7286,7288,7293],{"type":49,"value":7287},"Don't claim you \"delegated to ",{"type":43,"tag":146,"props":7289,"children":7291},{"className":7290},[],[7292],{"type":49,"value":942},{"type":49,"value":7294},"\" or any other sibling skill. This skill does not auto-invoke. At each handoff, print the inputs and tell the user to run the sibling slash-command, then wait. Anything else misleads the user about what just happened.",{"type":43,"tag":70,"props":7296,"children":7297},{},[7298,7300,7305,7307,7312],{"type":49,"value":7299},"Don't skip the ",{"type":43,"tag":146,"props":7301,"children":7303},{"className":7302},[],[7304],{"type":49,"value":277},{"type":49,"value":7306}," step between Stage 2 and Stage 4. A freshly created variation returns ",{"type":43,"tag":146,"props":7308,"children":7310},{"className":7309},[],[7311],{"type":49,"value":269},{"type":49,"value":7313}," until targeting promotes it to fallthrough — Stage 2 verification will silently take the fallback path on every request.",{"type":43,"tag":70,"props":7315,"children":7316},{},[7317,7319,7323],{"type":49,"value":7318},"Don't attempt a multi-agent graph migration in one pass. Migrate a single agent first; use ",{"type":43,"tag":179,"props":7320,"children":7321},{"href":693},[7322],{"type":49,"value":696},{"type":49,"value":7324}," as the next-step read.",{"type":43,"tag":1100,"props":7326,"children":7328},{"id":7327},"stage-5-evaluations",[7329],{"type":49,"value":7330},"Stage 5 evaluations",{"type":43,"tag":798,"props":7332,"children":7333},{},[7334,7339,7350,7383,7409],{"type":43,"tag":70,"props":7335,"children":7336},{},[7337],{"type":49,"value":7338},"Don't wire evals before the tracker is in place. Judges score traffic; without Stage 4 traffic, there is nothing to judge.",{"type":43,"tag":70,"props":7340,"children":7341},{},[7342,7344,7348],{"type":49,"value":7343},"Don't frame Stage 5 as \"either UI or programmatic.\" There are ",{"type":43,"tag":58,"props":7345,"children":7346},{},[7347],{"type":49,"value":5108},{"type":49,"value":7349}," paths: offline eval (recommended default for migration), UI-attached auto judges (completion-mode only), and programmatic direct-judge. Offline eval is the one most people skip and usually the right starting point.",{"type":43,"tag":70,"props":7351,"children":7352},{},[7353,7355,7360,7362,7367,7369,7375,7376,7382],{"type":49,"value":7354},"Don't pass ",{"type":43,"tag":146,"props":7356,"children":7358},{"className":7357},[],[7359],{"type":49,"value":5705},{"type":49,"value":7361}," to ",{"type":43,"tag":146,"props":7363,"children":7365},{"className":7364},[],[7366],{"type":49,"value":5433},{"type":49,"value":7368}," — it's a parameter on ",{"type":43,"tag":146,"props":7370,"children":7372},{"className":7371},[],[7373],{"type":49,"value":7374},"Judge.evaluate()",{"type":49,"value":5665},{"type":43,"tag":146,"props":7377,"children":7379},{"className":7378},[],[7380],{"type":49,"value":7381},"create_judge()",{"type":49,"value":192},{"type":43,"tag":70,"props":7384,"children":7385},{},[7386,7388,7394,7395,7401,7403,7407],{"type":49,"value":7387},"Don't hardcode judge config keys (",{"type":43,"tag":146,"props":7389,"children":7391},{"className":7390},[],[7392],{"type":49,"value":7393},"\"accuracy-judge\"",{"type":49,"value":1065},{"type":43,"tag":146,"props":7396,"children":7398},{"className":7397},[],[7399],{"type":49,"value":7400},"\"relevance-judge\"",{"type":49,"value":7402},", etc). The built-in keys are not canonical SDK constants; ask the user to look them up in ",{"type":43,"tag":58,"props":7404,"children":7405},{},[7406],{"type":49,"value":5819},{"type":49,"value":7408}," in the LD UI.",{"type":43,"tag":70,"props":7410,"children":7411},{},[7412,7414,7419,7421,7426,7428,7433,7434,7439],{"type":49,"value":7413},"Don't forget the ",{"type":43,"tag":146,"props":7415,"children":7417},{"className":7416},[],[7418],{"type":49,"value":5617},{"type":49,"value":7420}," guard after ",{"type":43,"tag":146,"props":7422,"children":7424},{"className":7423},[],[7425],{"type":49,"value":5433},{"type":49,"value":7427},". It returns ",{"type":43,"tag":146,"props":7429,"children":7431},{"className":7430},[],[7432],{"type":49,"value":5608},{"type":49,"value":6271},{"type":43,"tag":146,"props":7435,"children":7437},{"className":7436},[],[7438],{"type":49,"value":5625},{"type":49,"value":7440}," when the judge config is disabled for the context.",{"type":43,"tag":1100,"props":7442,"children":7444},{"id":7443},"api-surface-gotchas",[7445],{"type":49,"value":7446},"API surface gotchas",{"type":43,"tag":798,"props":7448,"children":7449},{},[7450,7476,7529],{"type":43,"tag":70,"props":7451,"children":7452},{},[7453,7455,7460,7462,7467,7469,7474],{"type":49,"value":7454},"Don't use ",{"type":43,"tag":146,"props":7456,"children":7458},{"className":7457},[],[7459],{"type":49,"value":3942},{"type":49,"value":7461}," for Stage 4 (tracking). That skill is for ",{"type":43,"tag":146,"props":7463,"children":7465},{"className":7464},[],[7466],{"type":49,"value":3950},{"type":49,"value":7468}," feature metrics, not agent ",{"type":43,"tag":146,"props":7470,"children":7472},{"className":7471},[],[7473],{"type":49,"value":3923},{"type":49,"value":7475}," calls — they are different APIs.",{"type":43,"tag":70,"props":7477,"children":7478},{},[7479,7480,7486,7488,7494,7496,7502,7504,7509,7510,7515,7516,7521,7522,7527],{"type":49,"value":7454},{"type":43,"tag":146,"props":7481,"children":7483},{"className":7482},[],[7484],{"type":49,"value":7485},"track_request()",{"type":49,"value":7487}," in Python — it does not exist in ",{"type":43,"tag":146,"props":7489,"children":7491},{"className":7490},[],[7492],{"type":49,"value":7493},"launchdarkly-server-sdk-ai",{"type":49,"value":7495},". Use ",{"type":43,"tag":146,"props":7497,"children":7499},{"className":7498},[],[7500],{"type":49,"value":7501},"track_metrics_of",{"type":49,"value":7503}," with a provider-package or custom extractor, or drop to explicit ",{"type":43,"tag":146,"props":7505,"children":7507},{"className":7506},[],[7508],{"type":49,"value":4109},{"type":49,"value":1732},{"type":43,"tag":146,"props":7511,"children":7513},{"className":7512},[],[7514],{"type":49,"value":4116},{"type":49,"value":1732},{"type":43,"tag":146,"props":7517,"children":7519},{"className":7518},[],[7520],{"type":49,"value":4123},{"type":49,"value":600},{"type":43,"tag":146,"props":7523,"children":7525},{"className":7524},[],[7526],{"type":49,"value":6043},{"type":49,"value":7528}," if you're on the streaming path.",{"type":43,"tag":70,"props":7530,"children":7531},{},[7532,7533,7539,7540,7546],{"type":49,"value":7354},{"type":43,"tag":146,"props":7534,"children":7536},{"className":7535},[],[7537],{"type":49,"value":7538},"graph_key=...",{"type":49,"value":7361},{"type":43,"tag":146,"props":7541,"children":7543},{"className":7542},[],[7544],{"type":49,"value":7545},"tracker.track_*()",{"type":49,"value":7547}," methods in Python — it is not an accepted argument. Trackers obtained inside a graph traversal are automatically configured with the correct graph key.",{"type":43,"tag":281,"props":7549,"children":7551},{"id":7550},"related-skills",[7552],{"type":49,"value":7553},"Related Skills",{"type":43,"tag":798,"props":7555,"children":7556},{},[7557,7567,7577,7587,7598,7608,7619],{"type":43,"tag":70,"props":7558,"children":7559},{},[7560,7565],{"type":43,"tag":146,"props":7561,"children":7563},{"className":7562},[],[7564],{"type":49,"value":942},{"type":49,"value":7566}," — called by Stage 2 to create the config",{"type":43,"tag":70,"props":7568,"children":7569},{},[7570,7575],{"type":43,"tag":146,"props":7571,"children":7573},{"className":7572},[],[7574],{"type":49,"value":953},{"type":49,"value":7576}," — called by Stage 3 to create and attach tool definitions",{"type":43,"tag":70,"props":7578,"children":7579},{},[7580,7585],{"type":43,"tag":146,"props":7581,"children":7583},{"className":7582},[],[7584],{"type":49,"value":975},{"type":49,"value":7586}," — called by Stage 5 to attach judges",{"type":43,"tag":70,"props":7588,"children":7589},{},[7590,7596],{"type":43,"tag":146,"props":7591,"children":7593},{"className":7592},[],[7594],{"type":49,"value":7595},"configs-variations",{"type":49,"value":7597}," — add variations for A\u002FB testing after migration is complete",{"type":43,"tag":70,"props":7599,"children":7600},{},[7601,7606],{"type":43,"tag":146,"props":7602,"children":7604},{"className":7603},[],[7605],{"type":49,"value":964},{"type":49,"value":7607}," — roll out new variations to users after migration is complete",{"type":43,"tag":70,"props":7609,"children":7610},{},[7611,7617],{"type":43,"tag":146,"props":7612,"children":7614},{"className":7613},[],[7615],{"type":49,"value":7616},"configs-update",{"type":49,"value":7618}," — modify config properties as your app evolves",{"type":43,"tag":70,"props":7620,"children":7621},{},[7622,7627,7629,7634],{"type":43,"tag":146,"props":7623,"children":7625},{"className":7624},[],[7626],{"type":49,"value":3942},{"type":49,"value":7628}," — for ",{"type":43,"tag":146,"props":7630,"children":7632},{"className":7631},[],[7633],{"type":49,"value":3950},{"type":49,"value":7635}," feature metrics (NOT for agent tracker calls)",{"type":43,"tag":281,"props":7637,"children":7639},{"id":7638},"references",[7640],{"type":49,"value":7641},"References",{"type":43,"tag":798,"props":7643,"children":7644},{},[7645,7654,7663,7679,7695,7704],{"type":43,"tag":70,"props":7646,"children":7647},{},[7648,7652],{"type":43,"tag":179,"props":7649,"children":7650},{"href":745},[7651],{"type":49,"value":1157},{"type":49,"value":7653}," — Step 1 audit checklist, grep patterns, SDK routing table, mode decision tree",{"type":43,"tag":70,"props":7655,"children":7656},{},[7657,7661],{"type":43,"tag":179,"props":7658,"children":7659},{"href":352},[7660],{"type":49,"value":355},{"type":49,"value":7662}," — Paired hardcoded-to-wrapped snippets for Python OpenAI, Node Anthropic, Python LangGraph",{"type":43,"tag":70,"props":7664,"children":7665},{},[7666,7670,7672,7677],{"type":43,"tag":179,"props":7667,"children":7668},{"href":3955},[7669],{"type":49,"value":3958},{"type":49,"value":7671}," — Every ",{"type":43,"tag":146,"props":7673,"children":7675},{"className":7674},[],[7676],{"type":49,"value":3923},{"type":49,"value":7678}," method in Python and Node side by side, auto-helper matrix, and common gotchas",{"type":43,"tag":70,"props":7680,"children":7681},{},[7682,7686,7688,7693],{"type":43,"tag":179,"props":7683,"children":7684},{"href":181},[7685],{"type":49,"value":3820},{"type":49,"value":7687}," — How to wire ",{"type":43,"tag":146,"props":7689,"children":7691},{"className":7690},[],[7692],{"type":49,"value":775},{"type":49,"value":7694}," into LangGraph, CrewAI, and custom react loops; dynamic tool loading pattern",{"type":43,"tag":70,"props":7696,"children":7697},{},[7698,7702],{"type":43,"tag":179,"props":7699,"children":7700},{"href":2895},[7701],{"type":49,"value":2930},{"type":49,"value":7703}," — Three fallback patterns (inline, file-backed, bootstrap-generated) and when to use each",{"type":43,"tag":70,"props":7705,"children":7706},{},[7707,7711],{"type":43,"tag":179,"props":7708,"children":7709},{"href":693},[7710],{"type":49,"value":696},{"type":49,"value":7712}," — Out-of-scope pointer doc for multi-agent migrations",{"type":43,"tag":7714,"props":7715,"children":7716},"style",{},[7717],{"type":49,"value":7718},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":7720,"total":7851},[7721,7736,7745,7759,7769,7781,7789,7803,7814,7823,7833,7842],{"slug":7722,"name":7722,"fn":7723,"description":7724,"org":7725,"tags":7726,"stars":20,"repoUrl":21,"updatedAt":7735},"agent-graphs","create and manage agent graphs","Create and manage agent graphs — directed graphs of configs connected by edges with handoff logic. Use when building multi-agent workflows where configs route to each other.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7727,7728,7731,7732],{"name":17,"slug":18,"type":15},{"name":7729,"slug":7730,"type":15},"Architecture","architecture",{"name":9,"slug":8,"type":15},{"name":7733,"slug":7734,"type":15},"Multi-Agent","multi-agent","2026-07-28T05:33:33.709407",{"slug":7737,"name":7737,"fn":7738,"description":7739,"org":7740,"tags":7741,"stars":20,"repoUrl":21,"updatedAt":7744},"aiconfig-agent-graphs","manage agent graphs","DEPRECATED redirect — this skill was renamed to agent-graphs. Do not use this skill; invoke agent-graphs instead. Kept only so old references to aiconfig-agent-graphs still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7742,7743],{"name":17,"slug":18,"type":15},{"name":7729,"slug":7730,"type":15},"2026-05-22T06:55:56.527064",{"slug":7746,"name":7746,"fn":7747,"description":7748,"org":7749,"tags":7750,"stars":20,"repoUrl":21,"updatedAt":7758},"aiconfig-ai-metrics","manage built-in AI metrics","DEPRECATED redirect — this skill was renamed to built-in-metrics. Do not use this skill; invoke built-in-metrics instead. Kept only so old references to aiconfig-ai-metrics still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7751,7754,7755],{"name":7752,"slug":7753,"type":15},"Analytics","analytics",{"name":9,"slug":8,"type":15},{"name":7756,"slug":7757,"type":15},"Metrics","metrics","2026-05-22T06:55:53.858749",{"slug":7760,"name":7760,"fn":7761,"description":7762,"org":7763,"tags":7764,"stars":20,"repoUrl":21,"updatedAt":7768},"aiconfig-create","redirect to configs-create skill","DEPRECATED redirect — this skill was renamed to configs-create. Do not use this skill; invoke configs-create instead. Kept only so old references to aiconfig-create still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7765,7766],{"name":9,"slug":8,"type":15},{"name":324,"slug":7767,"type":15},"reference","2026-05-22T06:55:41.790591",{"slug":7770,"name":7770,"fn":7771,"description":7772,"org":7773,"tags":7774,"stars":20,"repoUrl":21,"updatedAt":7780},"aiconfig-custom-metrics","configure custom metrics in LaunchDarkly","DEPRECATED redirect — this skill was renamed to custom-metrics. Do not use this skill; invoke custom-metrics instead. Kept only so old references to aiconfig-custom-metrics still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7775,7776,7779],{"name":7752,"slug":7753,"type":15},{"name":7777,"slug":7778,"type":15},"Feature Flags","feature-flags",{"name":9,"slug":8,"type":15},"2026-05-22T06:55:57.84851",{"slug":7782,"name":7782,"fn":7783,"description":7784,"org":7785,"tags":7786,"stars":20,"repoUrl":21,"updatedAt":7788},"aiconfig-migrate","redirect to migrate skill","DEPRECATED redirect — this skill was renamed to migrate. Do not use this skill; invoke migrate instead. Kept only so old references to aiconfig-migrate still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7787],{"name":324,"slug":7767,"type":15},"2026-05-22T06:55:44.464733",{"slug":7790,"name":7790,"fn":7791,"description":7792,"org":7793,"tags":7794,"stars":20,"repoUrl":21,"updatedAt":7802},"aiconfig-online-evals","run online evaluations","DEPRECATED redirect — this skill was renamed to online-evals. Do not use this skill; invoke online-evals instead. Kept only so old references to aiconfig-online-evals still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7795,7798,7799],{"name":7796,"slug":7797,"type":15},"Evals","evals",{"name":9,"slug":8,"type":15},{"name":7800,"slug":7801,"type":15},"Testing","testing","2026-05-22T06:55:55.179617",{"slug":7804,"name":7804,"fn":7805,"description":7806,"org":7807,"tags":7808,"stars":20,"repoUrl":21,"updatedAt":7813},"aiconfig-projects","manage AI configuration projects","DEPRECATED redirect — this skill was renamed to projects. Do not use this skill; invoke projects instead. Kept only so old references to aiconfig-projects still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7809,7812],{"name":7810,"slug":7811,"type":15},"Configuration","configuration",{"name":9,"slug":8,"type":15},"2026-05-22T06:55:48.522229",{"slug":7815,"name":7815,"fn":7816,"description":7817,"org":7818,"tags":7819,"stars":20,"repoUrl":21,"updatedAt":7822},"aiconfig-snippets","manage AI configuration snippets","DEPRECATED redirect — this skill was renamed to snippets. Do not use this skill; invoke snippets instead. Kept only so old references to aiconfig-snippets still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7820,7821],{"name":7810,"slug":7811,"type":15},{"name":9,"slug":8,"type":15},"2026-05-22T06:55:47.16557",{"slug":7824,"name":7824,"fn":7825,"description":7826,"org":7827,"tags":7828,"stars":20,"repoUrl":21,"updatedAt":7832},"aiconfig-targeting","configure LaunchDarkly targeting rules","DEPRECATED redirect — this skill was renamed to configs-targeting. Do not use this skill; invoke configs-targeting instead. Kept only so old references to aiconfig-targeting still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7829,7830,7831],{"name":7810,"slug":7811,"type":15},{"name":7777,"slug":7778,"type":15},{"name":9,"slug":8,"type":15},"2026-05-22T06:55:49.845445",{"slug":7834,"name":7834,"fn":7835,"description":7836,"org":7837,"tags":7838,"stars":20,"repoUrl":21,"updatedAt":7841},"aiconfig-tools","redirect to tools skill","DEPRECATED redirect — this skill was renamed to tools. Do not use this skill; invoke tools instead. Kept only so old references to aiconfig-tools still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7839,7840],{"name":9,"slug":8,"type":15},{"name":324,"slug":7767,"type":15},"2026-05-22T06:55:39.13373",{"slug":7843,"name":7843,"fn":7844,"description":7845,"org":7846,"tags":7847,"stars":20,"repoUrl":21,"updatedAt":7850},"aiconfig-update","redirect to configs-update skill","DEPRECATED redirect — this skill was renamed to configs-update. Do not use this skill; invoke configs-update instead. Kept only so old references to aiconfig-update still point users to the new name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7848,7849],{"name":9,"slug":8,"type":15},{"name":324,"slug":7767,"type":15},"2026-05-22T06:55:40.464884",49,{"items":7853,"total":7851},[7854,7861,7866,7872,7877,7883,7887],{"slug":7722,"name":7722,"fn":7723,"description":7724,"org":7855,"tags":7856,"stars":20,"repoUrl":21,"updatedAt":7735},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7857,7858,7859,7860],{"name":17,"slug":18,"type":15},{"name":7729,"slug":7730,"type":15},{"name":9,"slug":8,"type":15},{"name":7733,"slug":7734,"type":15},{"slug":7737,"name":7737,"fn":7738,"description":7739,"org":7862,"tags":7863,"stars":20,"repoUrl":21,"updatedAt":7744},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7864,7865],{"name":17,"slug":18,"type":15},{"name":7729,"slug":7730,"type":15},{"slug":7746,"name":7746,"fn":7747,"description":7748,"org":7867,"tags":7868,"stars":20,"repoUrl":21,"updatedAt":7758},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7869,7870,7871],{"name":7752,"slug":7753,"type":15},{"name":9,"slug":8,"type":15},{"name":7756,"slug":7757,"type":15},{"slug":7760,"name":7760,"fn":7761,"description":7762,"org":7873,"tags":7874,"stars":20,"repoUrl":21,"updatedAt":7768},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7875,7876],{"name":9,"slug":8,"type":15},{"name":324,"slug":7767,"type":15},{"slug":7770,"name":7770,"fn":7771,"description":7772,"org":7878,"tags":7879,"stars":20,"repoUrl":21,"updatedAt":7780},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7880,7881,7882],{"name":7752,"slug":7753,"type":15},{"name":7777,"slug":7778,"type":15},{"name":9,"slug":8,"type":15},{"slug":7782,"name":7782,"fn":7783,"description":7784,"org":7884,"tags":7885,"stars":20,"repoUrl":21,"updatedAt":7788},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7886],{"name":324,"slug":7767,"type":15},{"slug":7790,"name":7790,"fn":7791,"description":7792,"org":7888,"tags":7889,"stars":20,"repoUrl":21,"updatedAt":7802},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[7890,7891,7892],{"name":7796,"slug":7797,"type":15},{"name":9,"slug":8,"type":15},{"name":7800,"slug":7801,"type":15}]