diff --git a/.github/scripts/generate_sample_catalog.mjs b/.github/scripts/generate_sample_catalog.mjs index 60f0931..300ceca 100644 --- a/.github/scripts/generate_sample_catalog.mjs +++ b/.github/scripts/generate_sample_catalog.mjs @@ -592,6 +592,32 @@ const AZURE_OPENAI_MAX_COMPLETION_TOKENS = Number(process.env.AZURE_OPENAI_MAX_C // o-series use `low`. const AZURE_OPENAI_REASONING_EFFORT = process.env.AZURE_OPENAI_REASONING_EFFORT || ''; +// When true (workflow `refine_with_ai` input), the LLM reviews EVERY template's +// existing displayName/description and rewrites them only when they no longer +// fit the sample's README. When false (default), the LLM only fills BLANK +// fields and never touches values that are already present. +const AI_REFINE = /^(true|1|yes)$/i.test(process.env.AI_REFINE || ''); + +// When true (workflow `ignore_existing_catalog` input), the previous catalog's +// displayName/description values are NOT carried over. Every field starts empty, +// so a fresh run — typically the first AI refine — regenerates all values instead +// of being anchored by the "keep it if it already fits" logic. Off by default, +// so normal runs keep preserving PM-curated values. +const IGNORE_EXISTING = /^(true|1|yes)$/i.test(process.env.IGNORE_EXISTING || ''); + + +// Retry knobs for the Azure OpenAI calls. The refine path fires one request +// per template (~90 in a full run); even with ample TPM/RPM quota a burst can +// momentarily trip the rate limiter (HTTP 429) at the sliding-window edge. +// Retry those (and transient 5xx / network errors) with jittered exponential +// backoff, honoring a server `Retry-After` (reusing the shared `delay` and +// `parseRetryAfterMs` helpers), so an occasional throttle self-heals instead of +// dropping the field. Overridable via env for local debugging. +const LLM_MAX_ATTEMPTS = Number(process.env.LLM_MAX_ATTEMPTS) || 5; +const LLM_BASE_DELAY_MS = Number(process.env.LLM_BASE_DELAY_MS) || 1000; +const LLM_MAX_DELAY_MS = Number(process.env.LLM_MAX_DELAY_MS) || 30_000; + + /** * Fetch README.md content for a sample directory. * @param {string} samplePath @@ -608,46 +634,24 @@ async function fetchReadme(samplePath, ref) { } /** - * Call Azure OpenAI to generate a description from README content. We - * intentionally do NOT ask the LLM for displayName — the folder name (with - * numeric-prefix stripped, dashes turned into spaces, and Title Case) - * produces more consistent results across the catalog and is easier for PMs - * to predict at review time. + * Low-level Azure OpenAI chat call that expects a single JSON object back. + * Centralizes the request shape, error handling, and JSON extraction so the + * description-only and displayName+description prompts share one code path. + * Returns the parsed object, or `null` when the LLM is not configured or the + * call/parse fails (each failure mode is surfaced via `warn`). * - * @param {string} readmeContent - * @param {string} samplePath - * @returns {Promise<{ description: string } | null>} + * @param {string} systemPrompt + * @param {string} userPrompt + * @param {string} samplePath Used only for diagnostic messages. + * @returns {Promise | null>} */ -async function generateWithLLM(readmeContent, samplePath) { +async function callLLMForJson(systemPrompt, userPrompt, samplePath) { if (!AZURE_OPENAI_ENDPOINT || !AZURE_OPENAI_API_KEY) { return null; } const apiUrl = `${AZURE_OPENAI_ENDPOINT}/openai/deployments/${AZURE_OPENAI_DEPLOYMENT}/chat/completions?api-version=${AZURE_OPENAI_API_VERSION}`; - const systemPrompt = `You generate one-sentence descriptions for a VS Code template picker. -The user has already selected language, framework, and protocol before seeing these items, so the description must NOT repeat those choices. - -Rules: -- One sentence, max 100 characters -- Plain text, no markdown -- Describe what the sample does, not how it is implemented -- Do NOT include language names, protocol names, framework names, or words like "Sample" / "Demo" - -Examples: - {"description": "Minimal agent that echoes a response from a Foundry model."} - {"description": "Conversational agent with multi-turn session history."} - {"description": "Agent with local function tools for hotel search."} - {"description": "Agent that discovers and invokes tools from a remote MCP server."} - {"description": "Agent that saves and retrieves notes using function calling."} - -Respond ONLY with a JSON object: {"description": "..."}`; - - const userPrompt = `Path: ${samplePath} - -README.md: -${readmeContent.substring(0, 2000)}`; - /** @type {{ messages: Array<{role: string, content: string}>, max_completion_tokens: number, reasoning_effort?: string }} */ const body = { messages: [ @@ -656,7 +660,7 @@ ${readmeContent.substring(0, 2000)}`; ], // `max_completion_tokens` (not the legacy `max_tokens`) so newer models // accept the request. Kept generous because reasoning models spend part - // of the budget on hidden reasoning tokens before emitting the sentence. + // of the budget on hidden reasoning tokens before emitting the answer. // `temperature` is intentionally omitted: several newer models only // support the default value and 400 on anything else. max_completion_tokens: AZURE_OPENAI_MAX_COMPLETION_TOKENS, @@ -665,60 +669,182 @@ ${readmeContent.substring(0, 2000)}`; body.reasoning_effort = AZURE_OPENAI_REASONING_EFFORT; } - try { - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'api-key': AZURE_OPENAI_API_KEY, - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - // Include a truncated response body so the exact reason (e.g. an - // unsupported param, a missing deployment, or a wrong endpoint) is - // visible in the CI log instead of a bare status code. - let detail = ''; - try { - detail = (await response.text()).replace(/\s+/g, ' ').trim().slice(0, 400); - } catch { - // ignore body read failures + for (let attempt = 1; attempt <= LLM_MAX_ATTEMPTS; attempt++) { + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'api-key': AZURE_OPENAI_API_KEY, + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + // Retry rate-limit (429) and transient 5xx, honoring a server + // `Retry-After`; give up on other 4xx (bad request, auth, etc.). + const retryable = response.status === 429 || response.status >= 500; + if (retryable && attempt < LLM_MAX_ATTEMPTS) { + const retryAfter = parseRetryAfterMs(response); + const backoff = retryAfter !== undefined + ? Math.min(retryAfter, LLM_MAX_DELAY_MS) + : Math.min(Math.random() * LLM_BASE_DELAY_MS * 2 ** (attempt - 1), LLM_MAX_DELAY_MS); + const source = retryAfter !== undefined ? 'server Retry-After' : 'jittered backoff'; + console.warn(`LLM API returned ${response.status} for ${samplePath} (attempt ${attempt}/${LLM_MAX_ATTEMPTS}); retrying in ${Math.round(backoff)}ms (${source}).`); + await delay(backoff); + continue; + } + // Include a truncated response body so the exact reason (e.g. an + // unsupported param, a missing deployment, or a wrong endpoint) is + // visible in the CI log instead of a bare status code. + let detail = ''; + try { + detail = (await response.text()).replace(/\s+/g, ' ').trim().slice(0, 400); + } catch { + // ignore body read failures + } + warn(`LLM API returned ${response.status} for ${samplePath}.${detail ? ` Response: ${detail}` : ''}`); + return null; + } + + const data = await response.json(); + const choice = data.choices?.[0]; + const content = choice?.message?.content?.trim(); + if (!content) { + // A successful (200) call with empty content is almost always a + // reasoning model exhausting `max_completion_tokens` on hidden + // reasoning (finish_reason: "length"). Surface it instead of + // silently returning nothing, and hint at the knobs. + const finishReason = choice?.finish_reason ?? 'unknown'; + const reasoningTokens = data.usage?.completion_tokens_details?.reasoning_tokens; + const usageHint = reasoningTokens !== undefined ? ` (reasoning_tokens=${reasoningTokens})` : ''; + warn(`LLM returned empty content for ${samplePath} (finish_reason=${finishReason}${usageHint}). If finish_reason is "length", raise AZURE_OPENAI_MAX_COMPLETION_TOKENS or set AZURE_OPENAI_REASONING_EFFORT.`); + return null; } - warn(`LLM API returned ${response.status} for ${samplePath}; description will be left empty.${detail ? ` Response: ${detail}` : ''}`); - return null; - } - const data = await response.json(); - const choice = data.choices?.[0]; - const content = choice?.message?.content?.trim(); - if (!content) { - // A successful (200) call with empty content is almost always a - // reasoning model exhausting `max_completion_tokens` on hidden - // reasoning (finish_reason: "length"). Surface it instead of - // silently leaving the description empty, and hint at the knobs. - const finishReason = choice?.finish_reason ?? 'unknown'; - const reasoningTokens = data.usage?.completion_tokens_details?.reasoning_tokens; - const usageHint = reasoningTokens !== undefined ? ` (reasoning_tokens=${reasoningTokens})` : ''; - warn(`LLM returned empty content for ${samplePath} (finish_reason=${finishReason}${usageHint}); description left empty. If finish_reason is "length", raise AZURE_OPENAI_MAX_COMPLETION_TOKENS or set AZURE_OPENAI_REASONING_EFFORT.`); + // Parse JSON response — strip markdown code fences if present. + const jsonStr = content.replace(/^```json\s*/, '').replace(/\s*```$/, ''); + const parsed = JSON.parse(jsonStr); + if (!parsed || typeof parsed !== 'object') { + warn(`LLM response for ${samplePath} was not a JSON object.`); + return null; + } + return /** @type {Record} */ (parsed); + } catch (/** @type {any} */ err) { + // Network-level errors (ECONNRESET, socket timeout) are transient. + if (attempt < LLM_MAX_ATTEMPTS) { + const backoff = Math.min(Math.random() * LLM_BASE_DELAY_MS * 2 ** (attempt - 1), LLM_MAX_DELAY_MS); + console.warn(`LLM call failed for ${samplePath} (attempt ${attempt}/${LLM_MAX_ATTEMPTS}): ${err.message}; retrying in ${Math.round(backoff)}ms.`); + await delay(backoff); + continue; + } + warn(`LLM call failed for ${samplePath}: ${err.message}`); return null; } + } + return null; +} - // Parse JSON response — strip markdown code fences if present - const jsonStr = content.replace(/^```json\s*/, '').replace(/\s*```$/, ''); - const parsed = JSON.parse(jsonStr); +// Shared guidance describing what a good picker description looks like, reused +// by both the description-only prompt and the combined refine prompt so the +// two paths stay consistent. +const DESCRIPTION_GUIDANCE = `A good description: +- Is one sentence, max 100 characters, plain text (no markdown) +- Describes what the sample does, not how it is implemented +- Does NOT include language, protocol, or framework names, or words like "Sample" / "Demo" +Examples: + "Minimal agent that echoes a response from a Foundry model." + "Conversational agent with multi-turn session history." + "Agent with local function tools for hotel search." + "Agent that discovers and invokes tools from a remote MCP server."`; - const description = typeof parsed.description === 'string' ? parsed.description.trim() : ''; - if (!description) { - warn(`LLM response for ${samplePath} had no usable "description" field; description left empty.`); - return null; - } +/** + * Generate a one-sentence description from README content (used when only + * BLANK descriptions are being filled — the default, non-refine path). + * displayName is intentionally NOT requested here; when not refining it is + * derived deterministically from the folder name. + * + * @param {string} readmeContent + * @param {string} samplePath + * @returns {Promise<{ description: string } | null>} + */ +async function generateWithLLM(readmeContent, samplePath) { + const systemPrompt = `You generate one-sentence descriptions for a VS Code template picker. +The user has already selected language, framework, and protocol before seeing these items, so the description must NOT repeat those choices. - return { description }; - } catch (/** @type {any} */ err) { - warn(`LLM call failed for ${samplePath}: ${err.message}`); +${DESCRIPTION_GUIDANCE} + +Respond ONLY with a JSON object: {"description": "..."}`; + + const userPrompt = `Path: ${samplePath} + +README.md: +${readmeContent.substring(0, 2000)}`; + + const parsed = await callLLMForJson(systemPrompt, userPrompt, samplePath); + if (!parsed) { + return null; + } + const description = typeof parsed.description === 'string' ? parsed.description.trim() : ''; + if (!description) { + warn(`LLM response for ${samplePath} had no usable "description" field; description left empty.`); + return null; + } + return { description }; +} + +/** + * Review and (only when needed) rewrite a template's displayName AND + * description against its README (used by the opt-in `refine_with_ai` path). + * + * The current displayName and description are passed to the model so it can + * KEEP them verbatim when they already fit the sample's scenario — refinement + * is not a forced rewrite. displayName follows the Foundry Sample Finder + * convention: a short, human-friendly Title-Case name for the scenario + * (e.g. "Basic Agent", "Foundry Toolbox", "Azure Search RAG") rather than the + * raw folder name. + * + * @param {string} readmeContent + * @param {string} samplePath + * @param {string} currentDisplayName + * @param {string} currentDescription + * @returns {Promise<{ displayName: string, description: string } | null>} + */ +async function refineDisplayFieldsWithLLM(readmeContent, samplePath, currentDisplayName, currentDescription) { + const systemPrompt = `You curate the displayName and description of a hosted-agent sample shown in a VS Code template picker. +The user has already selected language, framework, and protocol before seeing these items, so neither field should repeat those choices. + +You are given the CURRENT displayName and description. If they already fit the sample's README scenario, KEEP them exactly as-is. Only rewrite a field when it is empty, inaccurate, or unclear. + +displayName rules: +- A short, human-friendly Title Case name for the scenario (2-4 words) +- Name what the sample demonstrates, not the folder (e.g. "Basic Agent", "Foundry Toolbox", "Azure Search RAG", "Human-in-the-Loop") +- No leading numbers, no raw folder tokens, no words like "Sample" / "Demo" +- Keep known acronyms uppercased (MCP, RAG, SDK, API, UI) + +description rules: +${DESCRIPTION_GUIDANCE} + +Respond ONLY with a JSON object: {"displayName": "...", "description": "..."}`; + + const userPrompt = `Path: ${samplePath} +Current displayName: ${currentDisplayName || '(empty)'} +Current description: ${currentDescription || '(empty)'} + +README.md: +${readmeContent.substring(0, 2000)}`; + + const parsed = await callLLMForJson(systemPrompt, userPrompt, samplePath); + if (!parsed) { + return null; + } + const displayName = typeof parsed.displayName === 'string' ? parsed.displayName.trim() : ''; + const description = typeof parsed.description === 'string' ? parsed.description.trim() : ''; + if (!displayName && !description) { + warn(`LLM refine response for ${samplePath} had no usable "displayName"/"description" fields; leaving values unchanged.`); return null; } + return { displayName, description }; } /** @@ -971,22 +1097,50 @@ function applyOverrides(templates, overrides) { } /** - * Auto-fill empty displayName and description fields. + * Fill and (optionally) refine displayName and description fields. * - * displayName is ALWAYS derived from the template's directory name when empty - * — the LLM is not consulted, because folder-name derivation is deterministic - * and PM-predictable. Existing PM-curated displayName values are preserved by - * the prior `mergeExistingDisplayFields` step, so this only affects newly - * scanned templates. + * Default path (AI_REFINE off): empty displayName is derived deterministically + * from the folder name; empty description is filled by the LLM when configured. + * Values that already exist (PM-curated or preserved from the prior catalog) + * are left untouched. * - * description is filled by the LLM (when configured) using the sample's - * README as context. Without LLM credentials the description stays empty and - * gets surfaced as an anomaly in the step summary. + * Refine path (AI_REFINE on, opt-in via the `refine_with_ai` workflow input): + * the LLM reviews BOTH fields of every template against its README and rewrites + * them only when they no longer fit — existing values that already match the + * scenario are kept verbatim. Without LLM credentials this path degrades to the + * default behavior. * * @param {Array<{displayName: string, description: string, path: string}>} templates * @param {string} commitSha */ async function autoFillDisplayFields(templates, commitSha) { + const hasLLM = Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY); + + if (AI_REFINE && hasLLM) { + await refineAllWithLLM(templates, commitSha); + } else { + if (AI_REFINE && !hasLLM) { + warn('refine_with_ai was requested but no Azure OpenAI credentials are configured; falling back to filling blanks only.'); + } + await fillBlankDisplayFields(templates, commitSha, hasLLM); + } + + for (const template of templates) { + if (!template.description) { + warn(`Template "${template.path}" is missing: description. PM should fill it before merge.`); + } + } +} + +/** + * Default path: derive empty displayName from the folder name and fill empty + * descriptions via the LLM. Never touches values that already exist. + * + * @param {Array<{displayName: string, description: string, path: string}>} templates + * @param {string} commitSha + * @param {boolean} hasLLM + */ +async function fillBlankDisplayFields(templates, commitSha, hasLLM) { // Fill displayName first — deterministic, no API calls. for (const template of templates) { if (!template.displayName) { @@ -997,34 +1151,63 @@ async function autoFillDisplayFields(templates, commitSha) { const needsDescription = templates.filter((t) => !t.description); if (needsDescription.length === 0) { console.log('All templates already have a description.'); - } else { - const hasLLM = Boolean(AZURE_OPENAI_ENDPOINT && AZURE_OPENAI_API_KEY); - if (hasLLM) { - console.log(`Generating descriptions for ${needsDescription.length} templates using LLM...`); - for (const template of needsDescription) { - const readme = await fetchReadme(template.path, commitSha); - if (!readme) { - continue; - } - const result = await generateWithLLM(readme, template.path); - if (result?.description) { - template.description = result.description; - } - } - } else { - console.log(`${needsDescription.length} templates need a description but no LLM is configured; leaving empty (will be flagged as anomalies).`); + return; + } + if (!hasLLM) { + console.log(`${needsDescription.length} templates need a description but no LLM is configured; leaving empty (will be flagged as anomalies).`); + return; + } + + console.log(`Generating descriptions for ${needsDescription.length} templates using LLM...`); + for (const template of needsDescription) { + const readme = await fetchReadme(template.path, commitSha); + if (!readme) { + continue; + } + const result = await generateWithLLM(readme, template.path); + if (result?.description) { + template.description = result.description; } } +} - // displayName always gets filled by the folder-name fallback above, so - // anomaly detection here is description-only. +/** + * Refine path: ask the LLM to review displayName + description for every + * template against its README, keeping values that already fit and rewriting + * only those that do not. Templates whose README cannot be fetched fall back + * to folder-name derivation for an empty displayName so nothing is left blank. + * + * @param {Array<{displayName: string, description: string, path: string}>} templates + * @param {string} commitSha + */ +async function refineAllWithLLM(templates, commitSha) { + console.log(`Refining displayName/description for ${templates.length} templates using LLM...`); for (const template of templates) { - if (!template.description) { - warn(`Template "${template.path}" is missing: description. PM should fill it before merge.`); + const readme = await fetchReadme(template.path, commitSha); + if (!readme) { + if (!template.displayName) { + template.displayName = displayNameFromPath(template.path); + } + continue; + } + const result = await refineDisplayFieldsWithLLM( + readme, + template.path, + template.displayName, + template.description + ); + if (result?.displayName) { + template.displayName = result.displayName; + } else if (!template.displayName) { + template.displayName = displayNameFromPath(template.path); + } + if (result?.description) { + template.description = result.description; } } } + /** * Reorder templates so the curated pins come first, in the declared * PINNED_TEMPLATE_PATHS order, followed by every other template in its existing @@ -1056,6 +1239,44 @@ function reorderPinnedFirst(templates) { return [...pinned, ...rest]; } +/** + * Warn about templates that share the same displayName WITHIN a single + * language+framework+protocol group — i.e. the set a user actually sees at once + * in the picker after choosing those dimensions. Duplicate names across + * DIFFERENT groups are fine (the user never sees them side by side) and are not + * reported. This is detection-only: the catalog is left unchanged so a PM can + * disambiguate the flagged names when reviewing the generated PR. + * + * @param {Array<{displayName: string, language: string, framework: string, protocol: string, path: string}>} templates + */ +function warnDuplicateDisplayNames(templates) { + /** @type {Map>} */ + const groups = new Map(); + for (const t of templates) { + const groupKey = `${t.language} / ${t.framework} / ${t.protocol}`; + const nameKey = t.displayName.trim().toLowerCase(); + if (!nameKey) { + continue; + } + let byName = groups.get(groupKey); + if (!byName) { + byName = new Map(); + groups.set(groupKey, byName); + } + const paths = byName.get(nameKey) ?? []; + paths.push(t.path); + byName.set(nameKey, paths); + } + + for (const [groupKey, byName] of groups) { + for (const [, paths] of byName) { + if (paths.length > 1) { + warn(`Duplicate displayName within "${groupKey}" (users see these together): ${paths.join(', ')}. A PM should disambiguate these names before merge.`); + } + } + } +} + async function main() { const commitSha = parseCommitShaArg(); console.log(`Using commit: ${commitSha}`); @@ -1064,8 +1285,14 @@ async function main() { const templates = await scanTemplates(commitSha); console.log(`Found ${templates.length} templates`); - // Step 1: Preserve existing PM-curated displayName/description values. - mergeExistingDisplayFields(templates); + // Step 1: Preserve existing PM-curated displayName/description values, + // unless a fresh run was requested (ignore_existing_catalog) — then every + // field starts empty so nothing anchors the regeneration. + if (IGNORE_EXISTING) { + console.log('IGNORE_EXISTING is set; not carrying over displayName/description from the previous catalog.'); + } else { + mergeExistingDisplayFields(templates); + } // Step 2: Apply source-controlled per-path overrides (structural fields like // `framework` that the upstream tree layout cannot express on its own). @@ -1075,6 +1302,9 @@ async function main() { // description from the LLM when configured. await autoFillDisplayFields(templates, commitSha); + // Flag same-name collisions a user would see together (detection only). + warnDuplicateDisplayNames(templates); + const dimensions = buildDimensions(templates); const orderedTemplates = reorderPinnedFirst(templates); diff --git a/.github/workflows/sync-sample-catalog.yml b/.github/workflows/sync-sample-catalog.yml index 0055a41..687a53a 100644 --- a/.github/workflows/sync-sample-catalog.yml +++ b/.github/workflows/sync-sample-catalog.yml @@ -8,6 +8,16 @@ on: required: false type: string default: "" + refine_with_ai: + description: "Let AI review and refine existing displayName/description (not just fill empty ones). Leave off to only fill blanks." + required: false + type: boolean + default: false + ignore_existing_catalog: + description: "Ignore the previous catalog's displayName/description (regenerate from scratch). Use for the first AI refine so existing values don't anchor the result." + required: false + type: boolean + default: false # Least-privilege for the default GITHUB_TOKEN: only what `actions/checkout` # needs. All writes (push branch + create PR) are performed via the GitHub @@ -80,6 +90,8 @@ jobs: AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} + AI_REFINE: ${{ inputs.refine_with_ai }} + IGNORE_EXISTING: ${{ inputs.ignore_existing_catalog }} run: node .github/scripts/generate_sample_catalog.mjs "${{ steps.resolve-sha.outputs.sha }}" - name: Detect catalog changes diff --git a/samples/hosted-agent/sample-catalog.json b/samples/hosted-agent/sample-catalog.json index 5e5da1d..239a57e 100644 --- a/samples/hosted-agent/sample-catalog.json +++ b/samples/hosted-agent/sample-catalog.json @@ -1,7 +1,7 @@ { - "commitSha": "84cb8c9a15d7a8bd40e1d80556e00557ea917049", + "commitSha": "3b98218db7e9a92367dc3e4b6638fd97ac7c9502", "repo": "https://github.com/microsoft-foundry/foundry-samples/", - "generatedAt": "2026-07-23T07:56:05Z", + "generatedAt": "2026-07-23T07:59:46Z", "dimensions": { "language": { "title": "Select a Language", @@ -67,8 +67,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Basic", - "description": "Agent that supports multi-turn conversations with response tracking.", + "displayName": "Basic Hosted Agent", + "description": "Minimal hosted agent that supports basic request/response and multi-turn conversations.", "path": "samples/python/hosted-agents/agent-framework/responses/01-basic", "requiresModel": true }, @@ -76,8 +76,8 @@ "language": "python", "framework": "copilot-sdk", "protocol": "invocations", - "displayName": "GitHub Copilot", - "description": "Agent streams Copilot session events with support for multi-turn conversations.", + "displayName": "Event Stream Agent", + "description": "Streams raw session events and supports multi-turn session resume across requests.", "path": "samples/python/hosted-agents/bring-your-own/invocations/github-copilot", "requiresModel": false }, @@ -85,8 +85,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Langgraph Chat", - "description": "Chat agent with tools for current time and math calculations.", + "displayName": "Tool-Enabled Chat Agent", + "description": "Multi-turn chat agent with local time and calculator tools.", "path": "samples/python/hosted-agents/langgraph/responses/01-langgraph-chat", "requiresModel": true }, @@ -94,8 +94,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Langgraph Toolbox", - "description": "Agent that uses a toolbox for web search and GitHub Copilot MCP tools with managed credentials.", + "displayName": "MCP Toolbox ReAct Agent", + "description": "Agent that uses a toolbox to call web_search and the Microsoft Learn MCP.", "path": "samples/python/hosted-agents/langgraph/responses/02-langgraph-toolbox", "requiresModel": true }, @@ -103,8 +103,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Toolbox", - "description": "Agent that discovers and invokes centrally managed tools from a Foundry Toolbox.", + "displayName": "MCP Toolbox Integration", + "description": "Agent that discovers and invokes tools from a Foundry Toolbox via MCP.", "path": "samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox", "requiresModel": true }, @@ -112,8 +112,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Workflows", - "description": "Processes requests through slogan writing, legal review, and formatting agents in sequence.", + "displayName": "Multi-Agent Workflow (Agent Framework)", + "description": "Chained agents that write a slogan, perform legal review, and format the final output.", "path": "samples/python/hosted-agents/agent-framework/responses/05-workflows", "requiresModel": true }, @@ -121,8 +121,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Workflows", - "description": "Processes a request through slogan writing, legal review, and formatting steps.", + "displayName": "Multi-Agent Workflow (LangGraph)", + "description": "Sequential agents that write, legally review, and format a final message.", "path": "samples/python/hosted-agents/langgraph/responses/05-workflows", "requiresModel": true }, @@ -130,8 +130,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Azure Search RAG", - "description": "Answers questions using product documentation and cites sources from a search index.", + "displayName": "Azure AI Search RAG", + "description": "Agent that searches Azure AI Search and cites source documents in responses.", "path": "samples/python/hosted-agents/agent-framework/responses/11-azure-search-rag", "requiresModel": true }, @@ -139,8 +139,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Claude Agent SDK", - "description": "Agent that streams responses from a Claude model for plain text input.", + "displayName": "Claude Streaming Agent", + "description": "Agent that streams assistant text responses in real time.", "path": "samples/python/hosted-agents/bring-your-own/invocations/claude-agent-sdk", "requiresModel": false }, @@ -148,8 +148,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "OpenAI Agents SDK", - "description": "Minimal agent that streams text responses from a Foundry model.", + "displayName": "Foundry Streaming Agent", + "description": "Minimal hosted agent that streams model text deltas from a Foundry deployment.", "path": "samples/python/hosted-agents/bring-your-own/responses/openai-agents-sdk", "requiresModel": true }, @@ -157,8 +157,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Caller", - "description": "Concierge agent that delegates questions to a remote specialist and summarizes answers.", + "displayName": "Delegation Caller", + "description": "Caller agent that delegates questions to a remote specialist and summarizes the reply.", "path": "samples/csharp/hosted-agents/agent-framework/a2a/01-delegation/caller", "requiresModel": true }, @@ -166,8 +166,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Executor", - "description": "Answers arithmetic and math questions as an expert agent.", + "displayName": "Math Executor Agent", + "description": "Agent that answers arithmetic and math questions.", "path": "samples/csharp/hosted-agents/agent-framework/a2a/01-delegation/executor", "requiresModel": true }, @@ -175,8 +175,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Agent Skills", - "description": "Agent that loads behavioral guidelines from Foundry Skills at startup for dynamic updates.", + "displayName": "Foundry Skills Agent", + "description": "Agent that loads behavioral guidelines from Foundry Skills at startup.", "path": "samples/csharp/hosted-agents/agent-framework/agent-skills", "requiresModel": true }, @@ -185,7 +185,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Azure Search RAG", - "description": "Support agent answers questions using a keyword-indexed knowledge base for grounding.", + "description": "Support agent that grounds answers using Azure AI Search retrieval.", "path": "samples/csharp/hosted-agents/agent-framework/azure-search-rag", "requiresModel": true }, @@ -194,7 +194,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Browser Automation", - "description": "Automates web browsing, scraping, and form filling in a remote Chromium browser.", + "description": "Automates browsing, web scraping, and form filling using a remote Chromium session with live view.", "path": "samples/csharp/hosted-agents/agent-framework/browser-automation", "requiresModel": true }, @@ -202,8 +202,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "File Tools", - "description": "Agent answers questions using bundled and user-uploaded files as knowledge sources.", + "displayName": "Agent with File Tools", + "description": "Agent that answers questions over bundled and session file sources using scoped file tools.", "path": "samples/csharp/hosted-agents/agent-framework/file-tools", "requiresModel": true }, @@ -212,7 +212,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Foundry Memory RAG", - "description": "Personal coach agent that remembers user preferences and goals across sessions.", + "description": "Personal coach agent with persistent per-user memory and RAG-grounded responses.", "path": "samples/csharp/hosted-agents/agent-framework/foundry-memory-rag", "requiresModel": true }, @@ -220,8 +220,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Toolbox MCP Skills", - "description": "Agent that dynamically discovers and uses skills from a remote toolbox endpoint.", + "displayName": "MCP Toolbox Skills", + "description": "Agent that discovers and invokes skills from a Foundry Toolbox via MCP.", "path": "samples/csharp/hosted-agents/agent-framework/foundry-toolbox-mcp-skills", "requiresModel": true }, @@ -229,8 +229,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Toolbox Server Side", - "description": "Agent that uses tools from a Foundry Toolbox, invoked server-side by the platform.", + "displayName": "Foundry MCP Toolbox (Platform-Managed)", + "description": "Agent that uses a Foundry Toolbox's server-side tools through the MCP proxy.", "path": "samples/csharp/hosted-agents/agent-framework/foundry-toolbox-server-side", "requiresModel": true }, @@ -238,8 +238,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Hello World", - "description": "Minimal hosted agent that responds to user questions and supports multi-turn conversation.", + "displayName": "Hello World Agent", + "description": "Minimal hosted agent that responds to simple text queries.", "path": "samples/csharp/hosted-agents/agent-framework/hello-world", "requiresModel": true }, @@ -247,8 +247,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "invocations", - "displayName": "Invocations Echo Agent", - "description": "Echoes back input messages with a simple prefix for testing agent hosting.", + "displayName": "Echo Agent", + "description": "Agent that echoes the request message back prefixed with Echo:", "path": "samples/csharp/hosted-agents/agent-framework/invocations-echo-agent", "requiresModel": false }, @@ -256,8 +256,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Local Tools", - "description": "Hotel search assistant that uses local function tools for queries.", + "displayName": "Agent with Local Tools", + "description": "Agent with local function tools for hotel search.", "path": "samples/csharp/hosted-agents/agent-framework/local-tools", "requiresModel": true }, @@ -265,8 +265,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "MCP Tools", - "description": "Agent answers questions by searching documentation using integrated tool providers.", + "displayName": "MCP Documentation Tools", + "description": "Agent that integrates client- and server-side tools for documentation search.", "path": "samples/csharp/hosted-agents/agent-framework/mcp-tools", "requiresModel": true }, @@ -274,8 +274,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Observability", - "description": "Agent with built-in observability for traces, metrics, and logs via Azure Monitor.", + "displayName": "Observability Agent", + "description": "Agent with built-in tracing, metrics, and logs for end-to-end observability.", "path": "samples/csharp/hosted-agents/agent-framework/observability", "requiresModel": true }, @@ -284,7 +284,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Simple Agent", - "description": "General-purpose AI assistant that replies to user input with model-generated responses.", + "description": "Minimal hosted agent that routes input to a configured model and returns its reply.", "path": "samples/csharp/hosted-agents/agent-framework/simple-agent", "requiresModel": true }, @@ -292,8 +292,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Teams Activity", - "description": "Agent that answers questions about Teams and calendar, and handles file attachments.", + "displayName": "Teams Activity Agent", + "description": "Agent that answers Teams and calendar questions and accepts file attachments.", "path": "samples/csharp/hosted-agents/agent-framework/teams-activity", "requiresModel": true }, @@ -302,7 +302,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Text Search RAG", - "description": "Agent answers support questions using retrieved passages from external documents.", + "description": "Support agent that grounds answers using text-search retrieval from a knowledge base.", "path": "samples/csharp/hosted-agents/agent-framework/text-search-rag", "requiresModel": true }, @@ -311,7 +311,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Toolbox Auth Paths", - "description": "Agent toolbox demonstrates different authentication methods for accessing upstream tools.", + "description": "Demonstrates how toolbox tools authenticate to upstream servers using different paths.", "path": "samples/csharp/hosted-agents/agent-framework/toolbox-auth-paths", "requiresModel": true }, @@ -319,8 +319,8 @@ "language": "csharp", "framework": "agent-framework", "protocol": "responses", - "displayName": "Workflows", - "description": "Workflow chains three translation agents to show intermediate language outputs.", + "displayName": "Translation Workflow", + "description": "Chains three translation agents English→French→Spanish→English and returns each translation.", "path": "samples/csharp/hosted-agents/agent-framework/workflows", "requiresModel": true }, @@ -328,8 +328,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "HelloWorld", - "description": "Hosted agent that returns a streaming hello world response from a Foundry model.", + "displayName": "Hello World Agent", + "description": "Minimal agent that calls a model and returns the reply as an SSE event stream.", "path": "samples/csharp/hosted-agents/bring-your-own/invocations/HelloWorld", "requiresModel": true }, @@ -337,8 +337,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Human In The Loop", - "description": "Agent that generates proposals and waits for human approval before proceeding.", + "displayName": "Human Approval Gate", + "description": "Agent that generates proposals and pauses for human approval, revision, or rejection.", "path": "samples/csharp/hosted-agents/bring-your-own/invocations/human-in-the-loop", "requiresModel": true }, @@ -346,8 +346,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Notetaking Agent", - "description": "Agent that saves and retrieves session-persistent notes with real-time streaming responses.", + "displayName": "Note-Taking Agent", + "description": "Agent that creates, retrieves, and stores session-persistent notes with real-time responses.", "path": "samples/csharp/hosted-agents/bring-your-own/invocations/notetaking-agent", "requiresModel": true }, @@ -355,8 +355,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "HelloWorld", - "description": "Routes real-time audio between a browser and a managed voice service session.", + "displayName": "Voice Live Hello World", + "description": "Minimal real-time voice agent that bridges browser audio to a managed Voice Live session.", "path": "samples/csharp/hosted-agents/bring-your-own/invocations_ws/HelloWorld", "requiresModel": false }, @@ -364,8 +364,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "responses", - "displayName": "HelloWorld", - "description": "Minimal hosted agent that returns a greeting from a Foundry model using the Responses API.", + "displayName": "Hello World Agent", + "description": "Minimal hosted agent that calls a model and returns the reply.", "path": "samples/csharp/hosted-agents/bring-your-own/responses/HelloWorld", "requiresModel": true }, @@ -374,7 +374,7 @@ "framework": "bring-your-own", "protocol": "responses", "displayName": "Background Agent", - "description": "Handles research analysis requests asynchronously with background processing and streaming updates.", + "description": "Background agent that streams multi-section research results and supports asynchronous processing.", "path": "samples/csharp/hosted-agents/bring-your-own/responses/background-agent", "requiresModel": true }, @@ -382,8 +382,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Browser Automation", - "description": "Provisions and controls a remote browser session, runs browsing tasks, and streams a live view.", + "displayName": "Browser Automation Agent", + "description": "Agent that runs Playwright CLI commands on a remote Chromium and streams a live view.", "path": "samples/csharp/hosted-agents/bring-your-own/responses/browser-automation", "requiresModel": true }, @@ -392,7 +392,7 @@ "framework": "bring-your-own", "protocol": "responses", "displayName": "Env Vars Agent", - "description": "Agent retrieves environment variable values with safety policies based on their type.", + "description": "Agent that exposes env vars injected from connections and returns kind-aware responses.", "path": "samples/csharp/hosted-agents/bring-your-own/responses/env-vars-agent", "requiresModel": true }, @@ -400,8 +400,8 @@ "language": "csharp", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Notetaking Agent", - "description": "Agent that saves and retrieves session-persistent notes for each user.", + "displayName": "Note-Taking Agent", + "description": "Agent that captures and retrieves session-persistent notes stored locally.", "path": "samples/csharp/hosted-agents/bring-your-own/responses/notetaking-agent", "requiresModel": true }, @@ -409,8 +409,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Caller", - "description": "Concierge agent that delegates questions to a remote specialist and summarizes the answers.", + "displayName": "Delegating Caller", + "description": "Agent that delegates user questions to a remote specialist and summarizes the reply.", "path": "samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller", "requiresModel": true }, @@ -418,8 +418,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Executor", - "description": "Answers arithmetic and math questions as an expert agent.", + "displayName": "Math Expert", + "description": "Agent that answers arithmetic and math questions for a caller.", "path": "samples/python/hosted-agents/agent-framework/a2a/01-delegation/executor", "requiresModel": true }, @@ -427,8 +427,8 @@ "language": "python", "framework": "agent-framework", "protocol": "invocations", - "displayName": "Basic", - "description": "Agent with in-memory session management for multi-turn conversations.", + "displayName": "Session Management Agent", + "description": "Agent that maintains per-session conversation history in memory.", "path": "samples/python/hosted-agents/agent-framework/invocations/01-basic", "requiresModel": true }, @@ -436,8 +436,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Tools", - "description": "Agent with custom tools like weather lookup callable during conversation.", + "displayName": "Agent with Local Tools", + "description": "Agent that provides local tools the model can invoke during conversations.", "path": "samples/python/hosted-agents/agent-framework/responses/02-tools", "requiresModel": true }, @@ -445,8 +445,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Files", - "description": "Agent that lists, reads, and interprets files using shell and code tools.", + "displayName": "File Tools Agent", + "description": "Agent with local file tools and a code interpreter for inspecting and manipulating files.", "path": "samples/python/hosted-agents/agent-framework/responses/06-files", "requiresModel": true }, @@ -454,8 +454,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Skills", - "description": "Agent that discovers and runs local file-based skills like generating PDF travel guides.", + "displayName": "Local Skills Provider", + "description": "Agent that discovers and runs local file-based skills and generates a PDF travel guide.", "path": "samples/python/hosted-agents/agent-framework/responses/07-skills", "requiresModel": true }, @@ -463,8 +463,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Teams Activity", - "description": "Agent that answers questions about Teams and calendar, including handling file attachments.", + "displayName": "Teams Activity Agent", + "description": "Agent that interacts via Teams, handles file attachments, and answers calendar queries.", "path": "samples/python/hosted-agents/agent-framework/responses/07-teams-activity", "requiresModel": true }, @@ -472,8 +472,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Observability", - "description": "Agent with built-in observability and telemetry for diagnostics and monitoring.", + "displayName": "Agent Observability", + "description": "Instrumented agent that captures telemetry, traces, metrics, and logs via env vars.", "path": "samples/python/hosted-agents/agent-framework/responses/08-observability", "requiresModel": true }, @@ -482,7 +482,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Declarative Customer Support", - "description": "Customer support triage agent that routes requests to specialized agents based on conversation history.", + "description": "Declarative multi-turn customer support workflow that triages and routes requests to agents.", "path": "samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support", "requiresModel": true }, @@ -490,8 +490,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Downstream Azure", - "description": "Agent performs data operations on Blob Storage and Service Bus using its own identity.", + "displayName": "Per-Agent Azure Identity", + "description": "Agent that accesses Blob Storage and Service Bus using a per-agent Microsoft Entra identity.", "path": "samples/python/hosted-agents/agent-framework/responses/10-downstream-azure", "requiresModel": true }, @@ -499,8 +499,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Skills", - "description": "Agent that loads behavioral guidelines from Foundry Skills at startup for dynamic updates.", + "displayName": "Foundry Skills Agent", + "description": "Agent that loads and applies behavioral guidelines from Foundry Skills.", "path": "samples/python/hosted-agents/agent-framework/responses/12-foundry-skills", "requiresModel": true }, @@ -508,8 +508,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Memory", - "description": "Agent that remembers user facts across sessions using persistent semantic memory.", + "displayName": "Foundry Semantic Memory", + "description": "Agent with persistent semantic memory backed by an Azure Foundry memory store.", "path": "samples/python/hosted-agents/agent-framework/responses/13-foundry-memory", "requiresModel": true }, @@ -518,7 +518,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Browser Automation Agent", - "description": "Automates web browsing tasks such as navigation, scraping, and form filling in a remote browser.", + "description": "Agent that controls a remote browser for browsing, scraping, and form filling.", "path": "samples/python/hosted-agents/agent-framework/responses/14-browser-automation-agent", "requiresModel": true }, @@ -526,8 +526,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Optimization Travel Approver", - "description": "Agent that reviews and approves travel requests for employees.", + "displayName": "Travel Request Approver", + "description": "Agent that approves travel requests by checking policy, cost, and manager authorization.", "path": "samples/python/hosted-agents/agent-framework/responses/15-optimization-travel-approver", "requiresModel": true }, @@ -536,7 +536,7 @@ "framework": "agent-framework", "protocol": "responses", "displayName": "Content Safety Guardrail", - "description": "Agent screens prompts and responses for harmful content based on a safety policy.", + "description": "Agent with a Responsible AI content safety guardrail to filter harmful prompts and responses.", "path": "samples/python/hosted-agents/agent-framework/responses/16-content-safety-guardrail", "requiresModel": true }, @@ -544,8 +544,8 @@ "language": "python", "framework": "agent-framework", "protocol": "responses", - "displayName": "Foundry Iq Toolbox", - "description": "Answers user questions using a knowledge base accessed through a remote toolbox connection.", + "displayName": "Foundry IQ Knowledge Base", + "description": "Agent that answers questions from a Foundry IQ knowledge base via a Foundry Toolbox.", "path": "samples/python/hosted-agents/agent-framework/responses/17-foundry-iq-toolbox", "requiresModel": true }, @@ -553,8 +553,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "AG UI", - "description": "Agent that streams AG-UI lifecycle events in response to user input.", + "displayName": "Automatic UI Events", + "description": "Agent that streams UI events and handles the full run lifecycle.", "path": "samples/python/hosted-agents/bring-your-own/invocations/ag-ui", "requiresModel": true }, @@ -563,7 +563,7 @@ "framework": "bring-your-own", "protocol": "invocations", "displayName": "Diagnostic Agent", - "description": "Runs network probes on hostnames and returns a JSON report of connectivity results.", + "description": "Agent that runs network probes and returns a structured reachability report.", "path": "samples/python/hosted-agents/bring-your-own/invocations/diagnostic-agent", "requiresModel": false }, @@ -572,7 +572,7 @@ "framework": "bring-your-own", "protocol": "invocations", "displayName": "Event Grid Trigger", - "description": "Processes new blobs in Azure Storage by summarizing content and saving results to a summary container.", + "description": "Agent triggered by Event Grid that summarizes uploaded blobs and writes JSON summaries.", "path": "samples/python/hosted-agents/bring-your-own/invocations/event-grid-trigger", "requiresModel": true }, @@ -580,8 +580,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Hello World", - "description": "Hosted agent that streams a model's reply as a server-sent event.", + "displayName": "Hello World Agent", + "description": "Minimal agent that calls a Foundry model and streams the reply.", "path": "samples/python/hosted-agents/bring-your-own/invocations/hello-world", "requiresModel": true }, @@ -589,8 +589,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Human In The Loop", - "description": "Agent that generates proposals and waits for human approval before proceeding.", + "displayName": "Human in the Loop", + "description": "Agent that generates proposals and pauses for human approval, revision, or rejection.", "path": "samples/python/hosted-agents/bring-your-own/invocations/human-in-the-loop", "requiresModel": true }, @@ -598,8 +598,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Langgraph Chat", - "description": "Conversational agent with session history and built-in time and calculator tools.", + "displayName": "Tool-Enabled Multi-Turn Chat", + "description": "Multi-turn conversational agent with conditional tool calling and session state.", "path": "samples/python/hosted-agents/bring-your-own/invocations/langgraph-chat", "requiresModel": true }, @@ -607,8 +607,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Notetaking Agent", - "description": "Agent that saves and retrieves notes with per-session isolation and streaming responses.", + "displayName": "Note-Taking Agent", + "description": "Note-taking agent that saves and retrieves per-session notes via session files.", "path": "samples/python/hosted-agents/bring-your-own/invocations/notetaking-agent", "requiresModel": true }, @@ -616,8 +616,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Toolbox", - "description": "Agent connects to a toolbox server and enables tool discovery and invocation during chat.", + "displayName": "MCP Tool Calling Agent", + "description": "Agent that discovers toolbox tools via MCP and lets the model call them.", "path": "samples/python/hosted-agents/bring-your-own/invocations/toolbox", "requiresModel": true }, @@ -625,8 +625,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "Duplex Live Agent", - "description": "Real-time voice agent that handles conversation and background tasks in parallel.", + "displayName": "Voice Live Duplex Agent", + "description": "Real-time voice agent with low-latency conversation and parallel background workers.", "path": "samples/python/hosted-agents/bring-your-own/invocations_ws/duplex-live-agent", "requiresModel": false }, @@ -634,8 +634,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "Hello World", - "description": "Real-time voice agent that relays audio and events between browser and a managed service.", + "displayName": "Voice Live Hello World", + "description": "Real-time agent that forwards browser audio to Voice Live for STT and TTS.", "path": "samples/python/hosted-agents/bring-your-own/invocations_ws/hello-world", "requiresModel": false }, @@ -643,8 +643,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "Livekit Server", - "description": "Voice agent connects to a LiveKit server for real-time audio conversations.", + "displayName": "LiveKit Voice Agent", + "description": "Voice agent that uses LiveKit to receive audio, transcribe, generate responses, and synthesize speech.", "path": "samples/python/hosted-agents/bring-your-own/invocations_ws/livekit-server", "requiresModel": false }, @@ -652,8 +652,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "Pipecat Webrtc", - "description": "Real-time voice agent with multi-agent pipeline and WebRTC audio media support.", + "displayName": "Pipecat Real-Time Voice Agent (WebRTC)", + "description": "Real-time voice agent hosted as a bring-your-own container with multi-agent pipeline.", "path": "samples/python/hosted-agents/bring-your-own/invocations_ws/pipecat-webrtc", "requiresModel": false }, @@ -661,8 +661,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations_ws", - "displayName": "Pipecat Ws Server", - "description": "Real-time voice agent with greeting and order-checking capabilities.", + "displayName": "Pipecat Real-Time Voice Agent (WebSocket)", + "description": "Real-time voice agent that transcribes, routes to LLM sub-agents, and streams speech output.", "path": "samples/python/hosted-agents/bring-your-own/invocations_ws/pipecat-ws-server", "requiresModel": false }, @@ -671,7 +671,7 @@ "framework": "bring-your-own", "protocol": "responses", "displayName": "Background Agent", - "description": "Handles long-running research analysis requests with background processing and streaming updates.", + "description": "Background agent that processes requests asynchronously and streams partial results.", "path": "samples/python/hosted-agents/bring-your-own/responses/background-agent", "requiresModel": true }, @@ -679,8 +679,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Bring Your Own Toolbox", - "description": "Agent connects to a remote toolbox and enables tool invocation during conversations.", + "displayName": "MCP Toolbox Integration (BYO)", + "description": "Agent that connects to a Foundry toolbox and discovers and invokes tools via MCP.", "path": "samples/python/hosted-agents/bring-your-own/responses/bring-your-own-toolbox", "requiresModel": true }, @@ -689,7 +689,7 @@ "framework": "bring-your-own", "protocol": "responses", "displayName": "Browser Automation", - "description": "Automates web browsers for tasks like form filling and web scraping with session control.", + "description": "Agent that automates browsers with lazy sessions, multi-session control, and on-demand skills.", "path": "samples/python/hosted-agents/bring-your-own/responses/browser-automation", "requiresModel": true }, @@ -697,8 +697,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Env Vars Agent", - "description": "Agent that reveals environment variable values with safety policies based on their type.", + "displayName": "Environment Variable Injection", + "description": "Agent exposing a tool to return connection-backed env vars with kind-aware safety.", "path": "samples/python/hosted-agents/bring-your-own/responses/env-vars-agent", "requiresModel": true }, @@ -706,8 +706,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Hello World", - "description": "Hosted agent that returns a hello world message from a Foundry model using a custom backend.", + "displayName": "Hello World Agent", + "description": "Minimal hosted agent that calls a Foundry model and returns the reply.", "path": "samples/python/hosted-agents/bring-your-own/responses/hello-world", "requiresModel": true }, @@ -715,8 +715,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Langgraph Chat", - "description": "Conversational agent with built-in calculator and time tools, streaming responses.", + "displayName": "Multi-Turn Chat", + "description": "Conversational agent with server-side history and conditional tool calling.", "path": "samples/python/hosted-agents/bring-your-own/responses/langgraph-chat", "requiresModel": true }, @@ -724,8 +724,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Langgraph Toolbox", - "description": "Agent that dynamically loads tools from a remote toolbox and handles incoming requests.", + "displayName": "MCP Toolbox Agent (LangGraph)", + "description": "Agent that loads tools from a remote Foundry toolbox and responds to incoming requests.", "path": "samples/python/hosted-agents/bring-your-own/responses/langgraph-toolbox", "requiresModel": true }, @@ -733,8 +733,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Langgraph Toolbox User Identity", - "description": "Agent that accesses mail, calendar, and GitHub tools for user-identity scenarios.", + "displayName": "MCP User-Identity OAuth Agent", + "description": "Agent that uses a Foundry toolbox via MCP to access user-identity OAuth tools.", "path": "samples/python/hosted-agents/bring-your-own/responses/langgraph-toolbox-user-identity", "requiresModel": true }, @@ -742,8 +742,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Notetaking Agent", - "description": "Agent that saves and retrieves notes with per-session isolation and streaming responses.", + "displayName": "Note-Taking Agent", + "description": "Agent that saves and retrieves per-session notes with persistent session storage.", "path": "samples/python/hosted-agents/bring-your-own/responses/notetaking-agent", "requiresModel": true }, @@ -751,8 +751,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Optimization Customer Support", - "description": "Customer support agent for troubleshooting and product assistance.", + "displayName": "Customer Support Agent", + "description": "Customer support agent for consumer electronics handling inquiries and troubleshooting.", "path": "samples/python/hosted-agents/bring-your-own/responses/optimization-customer-support", "requiresModel": true }, @@ -760,8 +760,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Optimization Hello World", - "description": "Minimal hosted agent that returns a static greeting message.", + "displayName": "Agent Optimizer Hello World", + "description": "Minimal agent demonstrating Agent Optimizer for response optimization.", "path": "samples/python/hosted-agents/bring-your-own/responses/optimization-hello-world", "requiresModel": true }, @@ -770,7 +770,7 @@ "framework": "bring-your-own", "protocol": "responses", "displayName": "Session Multiplexing", - "description": "Supports multiple users sharing a session while keeping response chains user-specific.", + "description": "Agent showing multiple users sharing an agent session while isolating response chains.", "path": "samples/python/hosted-agents/bring-your-own/responses/session-multiplexing", "requiresModel": true }, @@ -778,8 +778,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Foreground Background Agents Responses Voicelive", - "description": "Voice agent that routes quick replies to users and delegates long tasks to a background worker.", + "displayName": "Foreground Background Agents", + "description": "Router agent delegates long tasks to a background worker and manages task status.", "path": "samples/python/hosted-agents/bring-your-own/voicelive/foreground-background-agents-responses-voicelive", "requiresModel": true }, @@ -787,8 +787,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "responses", - "displayName": "Handoff Langgraph Responses Voicelive", - "description": "Customer support agent with triage and handoff between refund and order specialists.", + "displayName": "Customer Support Handoff", + "description": "Multi-agent customer support handoff optimized for voice-first interactions.", "path": "samples/python/hosted-agents/bring-your-own/voicelive/handoff-langgraph-responses-voicelive", "requiresModel": true }, @@ -796,8 +796,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Hello World Invocations Voicelive", - "description": "Minimal hosted agent for real-time voice interactions with audio transcription streaming.", + "displayName": "Voice Live Invocations Agent", + "description": "Agent that streams model audio transcriptions for real-time VoiceLive interactions.", "path": "samples/python/hosted-agents/bring-your-own/voicelive/hello-world-invocations-voicelive", "requiresModel": true }, @@ -805,8 +805,8 @@ "language": "python", "framework": "bring-your-own", "protocol": "invocations", - "displayName": "Hotel Booking Invocations Voicelive", - "description": "Hotel booking agent that handles speech and UI actions with session state and streaming.", + "displayName": "Hotel Booking Agent", + "description": "Voice-enabled hotel booking agent with multi-turn state and UI actions.", "path": "samples/python/hosted-agents/bring-your-own/voicelive/hotel-booking-invocations-voicelive", "requiresModel": true }, @@ -814,8 +814,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "A2a Caller", - "description": "Concierge agent that delegates specialist questions to a remote expert agent.", + "displayName": "A2A Caller", + "description": "Concierge agent that delegates specialist questions to a remote A2A executor.", "path": "samples/python/hosted-agents/langgraph/a2a/a2a-caller", "requiresModel": true }, @@ -823,8 +823,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "A2a Executor", - "description": "Math expert agent that answers questions and explains steps, callable by other agents.", + "displayName": "Math Expert Executor", + "description": "Executor agent that answers arithmetic questions and exposes an incoming agent endpoint.", "path": "samples/python/hosted-agents/langgraph/a2a/a2a-executor", "requiresModel": true }, @@ -832,8 +832,8 @@ "language": "python", "framework": "langgraph", "protocol": "invocations", - "displayName": "Langgraph Chat", - "description": "Multi-turn chat agent with tools for current time and math calculations.", + "displayName": "Chat With Local Tools", + "description": "Multi-turn chat agent with two local tools and per-session memory.", "path": "samples/python/hosted-agents/langgraph/invocations/01-langgraph-chat", "requiresModel": true }, @@ -841,8 +841,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Files", - "description": "Agent that analyzes and manipulates files using local and sandboxed tools.", + "displayName": "File Manipulation Agent", + "description": "Agent that manipulates uploaded files using filesystem tools and a code interpreter.", "path": "samples/python/hosted-agents/langgraph/responses/06-files", "requiresModel": true }, @@ -850,8 +850,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Human In The Loop", - "description": "Agent drafts proposals and pauses for human review with approve, revise, or reject options.", + "displayName": "Human in the Loop", + "description": "Agent that pauses for human review and supports approve, revise, or reject decisions.", "path": "samples/python/hosted-agents/langgraph/responses/07-human-in-the-loop", "requiresModel": true }, @@ -859,8 +859,8 @@ "language": "python", "framework": "langgraph", "protocol": "responses", - "displayName": "Observability", - "description": "Agent with automatic tracing for observability of all actions and tool invocations.", + "displayName": "Automatic Tracing", + "description": "Agent with automatic OpenTelemetry tracing across LLM calls, nodes, and tools.", "path": "samples/python/hosted-agents/langgraph/responses/08-observability", "requiresModel": true }