Skip to content

Commit 54695ca

Browse files
kuojianluCopilot
andauthored
feat: Add LangGraph hosted-agent samples on top of langchain_azure_ai.agents.hosting (microsoft-foundry#324)
* Add LangGraph Responses sample: chat with local tools Adds samples/python/hosted-agents/langgraph/responses/01-langgraph-chat: - LangGraph agent built with langchain.agents.create_agent - Two local tools: get_current_time, calculator - Hosted with LangGraphResponsesHostServer (langchain_azure_ai.agents.hosting) - Multi-turn via platform-managed previous_response_id Also adds the LangGraph parent README (samples/python/hosted-agents/langgraph/README.md) modeled on the agent-framework parent, and the test-payload for cloud E2E pipeline. * Add LangGraph Responses sample: Foundry Toolbox Adds samples/python/hosted-agents/langgraph/responses/02-foundry-toolbox: - LangGraph agent loading tools from a Foundry Toolbox via AzureAIProjectToolbox - Manifest provisions a toolbox with web_search + code_interpreter - Hosted with LangGraphResponsesHostServer (langchain_azure_ai.agents.hosting) Includes test-payload for the cloud E2E pipeline. * Add LangGraph Invocations sample: chat with local tools Adds samples/python/hosted-agents/langgraph/invocations/01-langgraph-chat: - LangGraph agent with get_current_time + calculator tools - Hosted with LangGraphInvocationsHostServer (langchain_azure_ai.agents.hosting) - Multi-turn via agent_session_id wired to a LangGraph MemorySaver checkpointer - Supports streaming SSE token deltas with {\"stream\": true} Includes test-payload for the cloud E2E pipeline. * fix(cloud-e2e): pass --output json to azd ai agent show for jq parsing * use AzureChatOpenAI * revert to use ChatOpenAI but keeps token provider * sync with new class name * Add LangGraph Responses sample: remote MCP tools * Add LangGraph Responses sample: multi-agent workflow * Add LangGraph Responses sample: files and code interpreter * Add LangGraph Responses sample: human-in-the-loop * Add LangGraph Responses sample: observability * docs: list new LangGraph samples in parent READMEs * tweak to fix e2e * use AzureAIOpenAIApiChatModel * revert to use ChatOpenAI * docs(langgraph): add Foundry Toolkit VS Code Extension subsections to sample READMEs Adds two subsections to all 8 LangGraph sample READMEs, mirroring the agent-framework sample convention: - Test in Agent Inspector (under 'Interacting with the agent') with a sample-specific message - Deploying with the Foundry Toolkit VS Code Extension (under 'Deploying the Agent to Foundry') with the 5-step wizard * refactor(langgraph): rename 02-foundry-toolbox -> 02-langgraph-toolbox; align with BYO Mirror samples/python/hosted-agents/bring-your-own/responses/langgraph-toolbox: - Rename folder via git mv (history preserved); agent name is now toolbox-langgraph. - Swap scenario from web_search + code_interpreter to web_search + GitHub Copilot MCP via a connection-backed Toolbox tool (custom-keys auth). - agent.manifest.yaml: add github_pat parameter (kind: string, secret: true) and a github-mcp-conn RemoteTool connection; toolbox now lists web_search + the MCP tool wired via project_connection_id. - main.py: add citation-grounded SYSTEM_PROMPT, OAuth consent helpers, and a tool schema sanitizer; install handle_tool_error on every loaded tool so consent (-32006) errors surface a friendly URL instead of crashing. - README.md: rewrite scenario, add Prerequisites (azd env set GITHUB_PAT), sample queries for web_search and GitHub MCP, and a Troubleshooting section adapted from BYO. - Update parent sample tables in langgraph/README.md and hosted-agents/README.md. * build(langgraph): use public langchain-azure-ai 1.2.4 instead of local wheels The hosting extra (ResponsesHostServer / InvocationsHostServer) shipped in langchain-azure-ai 1.2.4 on PyPI, so the samples no longer need to install the local 1.2.3+local wheel from a wheels/ directory. - Drop --find-links ./wheels and pin langchain-azure-ai[hosting]>=1.2.4 (and [hosting,opentelemetry]>=1.2.4 for 08-observability). - Remove the vendored langchain_azure_ai-1.2.3+local wheel from all 8 langgraph samples. * fix(langgraph): align 02-langgraph-toolbox manifest with BYO for E2E pipeline The cloud E2E pipeline (azd) failed to resolve the github_pat secret with the parameters.properties[] form, erroring: 'unable to prompt for secret parameter in no-prompt mode'. The BYO langgraph-toolbox manifest, which the pipeline accepts, uses the flat-map parameters.<name> form instead. - Switch parameters to the flat-map form (drops kind: string). - Drop the yaml-language-server schema reference: the IDE schema requires parameters.properties[] which the cloud pipeline does not accept. - Align the github-mcp-conn connection with BYO: category: remoteTool (lowercase) and drop the redundant authType field. - Add server_url to the MCP toolbox tool to match BYO. * fix(langgraph): lazy-load toolbox in 02-langgraph-toolbox to fix session_not_ready Foundry's session manager waits for /readiness to return 200 before routing the first /responses request. The eager asyncio.run(_load_toolbox_tools()) in main() blocked the HTTP server from coming up; if the toolbox call exceeded the readiness timeout the session never became ready and clients got HTTP 424 session_not_ready. Subclass ResponsesHostServer as _LazyToolboxHostServer: start with a placeholder empty-tools graph so the server can serve /readiness immediately, then build the real toolbox-bound graph inside a double-checked lock on the first request. Mirrors the lazy pattern used by the BYO langgraph-toolbox sample. Update README to mention tools are loaded lazily on the first request. * test(hosted-agents): split 06-files payload, drop dead 02-foundry-toolbox payload 06-files: turn 2 timed out on the cloud E2E pipeline (HTTP 408) because a single turn chained get_cwd -> list_files -> read_file -> code_interpreter and exceeded Foundry's per-request timeout. Split into three independent turns: list tools, read file & report Q1 revenue, then compute the diff with the code interpreter standalone. 02-foundry-toolbox: delete test-payload.txt — the cloud E2E workflow detects any hosted-agents sample whose dir name contains 'toolbox' as is_toolbox=true and overrides PAYLOAD_FILE with TOOLBOX_QUERY, so the per-sample payload was never read. * ci(hosted-agents): route 02-langgraph-toolbox to westus2 toolbox project The ncus toolbox project still reproduces the Foundry MCP dot-prefix tool-name bug for Foundry-hosted toolboxes (web-search, ms-learn, github-mcp); westus2 has the fix. The BYO langgraph-toolbox sample passes in CI for exactly this reason — it is already in the use_westus2 list while 02-langgraph-toolbox was not, causing its github-mcp tool to fail at runtime with 'Authorization header is badly formatted' against https://api.githubcopilot.com/mcp. Add samples/python/hosted-agents/langgraph/responses/02-langgraph-toolbox to the use_westus2 case so it deploys against the same fixed project as its BYO parity. * ci(hosted-agents): use GH_PAT secret for github_pat GitHub forbids user-defined secret names that start with GITHUB_, so rename the lookup from secrets.GITHUB_PAT to secrets.GH_PAT. The secrets.GITHUB_TOKEN fallback is unchanged (that one is auto-issued by Actions and is fine to read). * fix(ci): support non-src scaffold paths in hosted agents e2e * fix: normalize nested azd project dir in cloud hosted-agents e2e * ci(hosted-agents): pin azure.ai.agents extension to 0.1.34-preview Newer extension builds omit the Foundry-Features: HostedAgents=V1Preview header on 'azd ai agent invoke -p invocations', which the platform now rejects with HTTP 403 preview_feature_required. 0.1.34-preview still sends the header. * revert(langgraph): drop 02-langgraph-toolbox sample to unblock PR The toolbox sample's E2E job is blocked on platform-side fixes (MCP tool-name dot-prefix bug on ncus, github-mcp PAT routing, and the preview_feature_required header gate on the invocations protocol). Remove the sample directory, the two README rows that listed it, and the westus2 routing entry so the PR can merge with the remaining LangGraph samples. The sample, including all iteration history, is preserved on the kuojianlu/langgraph-toolbox-backup branch and will be reintroduced in a separate PR once the platform side stabilizes. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 707f533 commit 54695ca

68 files changed

Lines changed: 2562 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/hosted-agents-cloud-e2e.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,12 @@ jobs:
350350
uses: Azure/setup-azd@v2
351351

352352
- name: Install azd AI Agents extension
353-
run: azd ext install azure.ai.agents
353+
# Pinned: the default-installed version of azure.ai.agents omits
354+
# the `Foundry-Features: HostedAgents=V1Preview` header on
355+
# `azd ai agent invoke -p invocations`, which the platform now
356+
# requires (HTTP 403 preview_feature_required). 0.1.34-preview
357+
# includes the header.
358+
run: azd ext install azure.ai.agents --version 0.1.34-preview
354359

355360
- name: Azure login (OIDC)
356361
uses: azure/login@v2
@@ -379,8 +384,9 @@ jobs:
379384
env:
380385
# Provide the github_pat secret parameter for `azd ai agent init --no-prompt`.
381386
# azd looks for an env var matching the exact (lowercase) parameter name.
382-
# Use a stored GITHUB_PAT secret if available; fall back to GITHUB_TOKEN.
383-
github_pat: ${{ secrets.GITHUB_PAT || secrets.GITHUB_TOKEN }}
387+
# Use a stored GH_PAT secret if available; fall back to GITHUB_TOKEN.
388+
# (GitHub forbids user-defined secret names that start with GITHUB_.)
389+
github_pat: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
384390
run: |
385391
WORK_DIR="/tmp/ci-${{ matrix.combo_id }}"
386392
mkdir -p "$WORK_DIR" && cd "$WORK_DIR"
@@ -409,6 +415,9 @@ jobs:
409415
# When the scaffolded service directory has no source files, copy them
410416
# from the actual sample directory in the repo.
411417
SERVICE_SRC="src/${{ matrix.name }}"
418+
if [ ! -d "$SERVICE_SRC" ] && [ -d "${{ matrix.name }}" ]; then
419+
SERVICE_SRC="${{ matrix.name }}"
420+
fi
412421
SAMPLE_SRC="${{ github.workspace }}/${{ matrix.path }}"
413422
if [ ! -f "$SERVICE_SRC/main.py" ] && [ ! -f "$SERVICE_SRC/Program.cs" ]; then
414423
echo "Scaffolded service has no source — copying from sample directory: $SAMPLE_SRC"
@@ -459,6 +468,15 @@ jobs:
459468
rm -rf /tmp/ci-${{ matrix.combo_id }}/hooks
460469
echo "Removed post-deploy hooks (toolbox sample — name renamed per matrix job)"
461470
fi
471+
# Some templates scaffold the azd project in ./<agent-name>/.
472+
# Normalize to the step working directory so all subsequent azd
473+
# commands (env/deploy/invoke/monitor) resolve azure.yaml correctly.
474+
if [ ! -f azure.yaml ] && [ -f "${{ matrix.name }}/azure.yaml" ]; then
475+
echo "Flattening nested azd project directory: ${{ matrix.name }}"
476+
shopt -s dotglob nullglob
477+
mv "${{ matrix.name }}"/* .
478+
rmdir "${{ matrix.name }}"
479+
fi
462480
timeout-minutes: 5
463481

464482
# ── Configure azd environment ────────────────────────────────────

internal/playwright-tests/helpers/agentApiSmoke.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ export async function smokeTestDeployedAgentApis(
9898
// against this sample, exhausting the full retry window. Cloud E2E
9999
// (which sends store=false) passes; deploy itself succeeds. Skip the
100100
// Responses smoke for this one sample until the upstream SDK is fixed.
101-
const skipResponsesSmoke = samplePath.includes('/agent-framework/responses/04-foundry-toolbox');
101+
const skipResponsesSmoke =
102+
samplePath.includes('/agent-framework/responses/04-foundry-toolbox') ||
103+
samplePath.includes('/langgraph/responses/07-human-in-the-loop');
102104

103105
let lastError: unknown;
104106
for (const protocol of protocolsToTest) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"message":"What time is it right now?"}
2+
{"message":"Compute 42 multiplied by 17."}
3+
{"message":"What is the square root of 144?"}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
What time is it right now?
2+
Compute 42 multiplied by 17.
3+
What is the square root of 144?
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
List my 5 most recently updated GitHub repos.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Create a slogan for a new electric SUV that is affordable and fun to drive.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
What tools do you have?
2+
Find the quarterly report under {cwd}/resources and tell me the Q1 2026 and Q1 2025 revenue.
3+
Use the code interpreter to compute 1482.6 minus 1330.7.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Draft a marketing email for our new AI product launch.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
What time is it right now, and what is 42 multiplied by 17?

samples/python/hosted-agents/README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ Hosted agents run any code you can put in a container. These samples cover three
8686
| | **Agent Framework** | **LangGraph** | **Bring Your Own** |
8787
| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
8888
| **Best for** | Starting fresh on Foundry — also supports AutoGen and Semantic Kernel | Already using LangChain / LangGraph | Already built with CrewAI or your own stack |
89-
| **SDK** | `agent-framework-foundry-hosting` (includes core, openai, foundry, orchestrations) | `azure-ai-agentserver-responses` / `azure-ai-agentserver-invocations` | `azure-ai-agentserver-responses` / `azure-ai-agentserver-invocations`, or `azure-ai-agentserver-core` for fully custom HTTP |
90-
| **Foundry integration** | Native — sessions, tools, memory, streaming all built in | Adapter — sessions and tools wired through LangGraph adapter | Core adapter hosts the web server and exposes `/invocations` and `/responses` endpoints; you supply the agent logic |
89+
| **SDK** | `agent-framework-foundry-hosting` (includes core, openai, foundry, orchestrations) | `langchain-azure-ai[hosting]` (`ResponsesHostServer` / `InvocationsHostServer`) | `azure-ai-agentserver-responses` / `azure-ai-agentserver-invocations`, or `azure-ai-agentserver-core` for fully custom HTTP |
90+
| **Foundry integration** | Native — sessions, tools, memory, streaming all built in | Native via `langchain_azure_ai.agents.hosting` — sessions, streaming, and tool-call surfacing built in for LangGraph agents (`create_agent`) and custom `StateGraph`s | Core adapter hosts the web server and exposes `/invocations` and `/responses` endpoints; you supply the agent logic |
9191
| **Protocols** | Responses and Invocations | Responses and Invocations | Responses and Invocations |
9292
| **Language support** | Python and C# | Python only | Any language (Python and C# samples provided) |
93-
| **Start here** | [Basic Agent →](agent-framework/responses/01-basic/) | [LangGraph Chat →](bring-your-own/responses/langgraph-chat/) | [Hello World →](bring-your-own/responses/hello-world/) |
93+
| **Start here** | [Basic Agent →](agent-framework/responses/01-basic/) | [LangGraph Chat →](langgraph/responses/01-langgraph-chat/) | [Hello World →](bring-your-own/responses/hello-world/) |
9494

95-
> **Which should I choose?** If you're building a new agent — or already using AutoGen or Semantic Kernel — start with **Agent Framework**. It has the tightest Foundry integration, supports those orchestrators natively, and has the most samples to learn from. If you already have LangGraph code, use the **LangGraph** adapter to bring it to Foundry. If you have an existing agent in another framework (e.g., CrewAI), **Bring Your Own** shows how to containerize and deploy it unchanged.
95+
> **Which should I choose?** If you're building a new agent — or already using AutoGen or Semantic Kernel — start with **Agent Framework**. It has the tightest Foundry integration, supports those orchestrators natively, and has the most samples to learn from. If you already have LangGraph code, use the **LangGraph** hosting integration (`langchain_azure_ai.agents.hosting`) to bring it to Foundry. If you have an existing agent in another framework (e.g., CrewAI), **Bring Your Own** shows how to containerize and deploy it unchanged.
9696
9797
---
9898

@@ -135,7 +135,26 @@ Full control over the HTTP request/response cycle. You define the payload schema
135135

136136
## LangGraph samples
137137

138-
LangGraph samples are included in the **Bring Your Own** section below — see [`bring-your-own/responses/langgraph-chat/`](bring-your-own/responses/langgraph-chat/) and [`bring-your-own/invocations/langgraph-chat/`](bring-your-own/invocations/langgraph-chat/).
138+
Bring your existing [LangGraph](https://langchain-ai.github.io/langgraph/) graphs to Foundry. These samples use [`langchain_azure_ai.agents.hosting`](https://github.com/langchain-ai/langchain-azure/tree/main/libs/azure-ai/langchain_azure_ai/agents/hosting) (`ResponsesHostServer` / `InvocationsHostServer`) to expose LangGraph agents (`create_agent`) and custom `StateGraph`s over the hosted agent protocols, with native Foundry session, streaming, and tool wiring.
139+
140+
See [`langgraph/README.md`](langgraph/) for the full list and the local-run guide.
141+
142+
### Responses protocol
143+
144+
| Sample | What it shows |
145+
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
146+
| **[Chat](langgraph/responses/01-langgraph-chat/)** | Minimal LangGraph agent with two local tools (`get_current_time`, `calculator`); multi-turn via `previous_response_id`. |
147+
| **[MCP](langgraph/responses/04-mcp/)** | LangGraph agent that loads tools from a remote MCP server (default: GitHub Copilot MCP) via `langchain_mcp_adapters`. |
148+
| **[Workflows](langgraph/responses/05-workflows/)** | Custom `StateGraph` chaining three specialized LLM nodes — slogan writer, legal reviewer, formatter — each seeing only the prior agent's output. |
149+
| **[Files](langgraph/responses/06-files/)** | LangGraph agent with local filesystem tools and a Foundry-Toolbox `code_interpreter` for session-uploaded files. |
150+
| **[Human-in-the-Loop](langgraph/responses/07-human-in-the-loop/)** | `StateGraph` that drafts a proposal and pauses for approval via `langgraph.types.interrupt`, serialized as `mcp_approval_request` + `function_call`. |
151+
| **[Observability](langgraph/responses/08-observability/)** | GenAI OpenTelemetry tracing enabled with `enable_auto_tracing()` — spans, metrics, and logs flow to Application Insights. |
152+
153+
### Invocations protocol
154+
155+
| Sample | What it shows |
156+
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
157+
| **[Chat](langgraph/invocations/01-langgraph-chat/)** | Minimal LangGraph agent with local tools; session state via `agent_session_id` (URL param / `x-agent-session-id` header) backed by a LangGraph checkpointer. |
139158

140159
---
141160

0 commit comments

Comments
 (0)