Skip to content

Conversation

@ahmedmuhsin
Copy link

Motivation and Context

This PR addresses the need for simpler Azure Functions samples that demonstrate real-time agent and workflow execution without the complexity of durable orchestration. Many developers want to:

  1. Get started quickly with Agent Framework in Azure Functions without setting up storage accounts and durable orchestration
  2. Stream responses in real-time to provide immediate feedback to users
  3. Run short-lived tasks (< 230 seconds) that don't require state persistence
  4. Understand the difference between stateless HTTP streaming and durable orchestration patterns

Description

This PR adds two new samples under non-durable that demonstrate stateless HTTP streaming for agents and workflows:

1. 01_agent_http_streaming

  • Single agent with tool calling capability (get_weather)
  • Streams text responses using Server-Sent Events (SSE)
  • Uses azurefunctions-extensions-http-fastapi for StreamingResponse
  • Demonstrates agent.run_stream() pattern
  • No storage or orchestration required

2. 02_workflow_http_streaming

  • Multi-agent sequential workflow (Research → Writer)
  • Streams text from both agents as they execute
  • Uses SequentialBuilder().participants([...]) pattern
  • Filters AgentRunUpdateEvent to extract text via event.data.text
  • Shows agent handoffs without durable orchestration

Key Implementation Details:

  • Both samples use AzureCliCredential for authentication
  • Streaming format: data: {"text": "chunk"}\n\n (SSE)
  • AsyncGenerator pattern for yielding chunks
  • FastAPI extension required for streaming support
  • Setting: PYTHON_ENABLE_INIT_INDEXING=1 for HTTP streaming

Supporting Files:

  • Comprehensive READMEs with setup, testing, and comparison tables
  • demo.http files with multiple test cases
  • requirements.txt with all dependencies
  • local.settings.json.template for configuration
  • Parent README.md explaining when to use non-durable vs durable samples

Documentation Highlights:

  • Clear comparison tables (non-durable vs durable approaches)
  • Multiple testing methods (REST Client, cURL, Python, JavaScript)
  • Limitations section (timeout constraints, no state persistence)
  • Architecture diagrams
  • Expected output examples

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible (Note: These are sample files, no unit tests required)
  • Is this a breaking change? No

Copilot AI review requested due to automatic review settings December 20, 2025 06:18
@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation python labels Dec 20, 2025
@github-actions github-actions bot changed the title Add Non-durable Azure Functions Samples Python: Add Non-durable Azure Functions Samples Dec 20, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two new Azure Functions samples demonstrating stateless HTTP streaming for agents and workflows without durable orchestration. The samples provide developers with simpler alternatives to durable functions for real-time agent interactions that complete within HTTP timeout limits.

Key Changes:

  • Added 01_agent_http_streaming sample showing single agent with tool calling via SSE streaming
  • Added 02_workflow_http_streaming sample demonstrating multi-agent sequential workflows with real-time streaming
  • Added comprehensive parent README with comparison tables and guidance on when to use non-durable vs durable approaches

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
python/samples/getting_started/azure_functions/non-durable/README.md Parent directory overview with comparison tables and setup instructions
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/function_app.py Single agent HTTP streaming implementation with SSE format
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/README.md Comprehensive documentation for agent streaming sample
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/demo.http Test cases and client examples for agent streaming
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/requirements.txt Python dependencies for agent sample
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/local.settings.json.template Configuration template for agent sample
python/samples/getting_started/azure_functions/non-durable/01_agent_http_streaming/host.json Azure Functions host configuration
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/function_app.py Multi-agent workflow streaming implementation
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/README.md Comprehensive documentation for workflow streaming sample
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/demo.http Test cases and client examples for workflow streaming
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/requirements.txt Python dependencies for workflow sample
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/local.settings.json.template Configuration template for workflow sample
python/samples/getting_started/azure_functions/non-durable/02_workflow_http_streaming/host.json Azure Functions host configuration

Comment on lines +7 to +8
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4",
"AZURE_OPENAI_API_KEY": "<AZURE_OPENAI_API_KEY>"
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local.settings.json.template includes both AzureCliCredential (default in code) and AZURE_OPENAI_API_KEY placeholder. This could be confusing since the code uses AzureCliCredential by default and doesn't read the API key from settings. Consider either:

  1. Removing the API_KEY line and adding a comment explaining how to switch to API key authentication
  2. Adding code to check for the API_KEY setting and use it if present

The same pattern appears in sample 02 as well.

Suggested change
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4",
"AZURE_OPENAI_API_KEY": "<AZURE_OPENAI_API_KEY>"
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4"

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +8
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4",
"AZURE_OPENAI_API_KEY": "<AZURE_OPENAI_API_KEY>"
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local.settings.json.template includes both AzureCliCredential (default in code) and AZURE_OPENAI_API_KEY placeholder. This could be confusing since the code uses AzureCliCredential by default and doesn't read the API key from settings. Consider either:

  1. Removing the API_KEY line and adding a comment explaining how to switch to API key authentication
  2. Adding code to check for the API_KEY setting and use it if present
Suggested change
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4",
"AZURE_OPENAI_API_KEY": "<AZURE_OPENAI_API_KEY>"
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4"

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +50
"""Stream agent responses in real-time.

Request body: {"message": "What's the weather in Seattle?"}
Response: Server-Sent Events stream with text chunks
"""
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function docstring states "Stream agent responses in real-time" but lacks detail about the request/response format and error handling behavior. According to the custom coding guidelines for samples, code should be well-documented with comments explaining the purpose of each step. Consider expanding the docstring to explain the SSE streaming format and error conditions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +61 to +65
"""Stream workflow execution in real-time.

Request body: {"message": "Research Seattle weather and write about it"}
Response: Server-Sent Events stream with workflow events
"""
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function docstring states "Stream workflow execution in real-time" but lacks detail about the request/response format and error handling behavior. According to the custom coding guidelines for samples, code should be well-documented with comments explaining the purpose of each step. Consider expanding the docstring to explain the SSE streaming format and what events are emitted.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +162 to +168
const eventSource = new EventSource('http://localhost:7071/api/agent/stream?message=Hello');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.text) {
document.body.innerHTML += data.text;
}
};
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The demo.http file includes examples with EventSource in JavaScript that assume GET requests with query parameters, but the actual endpoint only accepts POST requests with JSON body. The JavaScript example on line 162 shows:

const eventSource = new EventSource('http://localhost:7071/api/agent/stream?message=Hello');

However, EventSource only supports GET requests and cannot send POST requests with JSON bodies. This example will not work with the implemented endpoint which requires POST with a JSON body containing the message field.

Suggested change
const eventSource = new EventSource('http://localhost:7071/api/agent/stream?message=Hello');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.text) {
document.body.innerHTML += data.text;
}
};
async function startStreaming() {
const response = await fetch('http://localhost:7071/api/agent/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'Hello' }),
});
if (!response.body) {
console.error('Streaming not supported in this browser.');
return;
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6));
if (data.text) {
document.body.innerHTML += data.text;
}
} catch (e) {
console.error('Failed to parse SSE data:', e);
}
}
}
}
}
startStreaming().catch((error) => {
console.error('Streaming error:', error);
});

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +94
```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com",
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "gpt-4"
}
}
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration setting "AzureWebJobsFeatureFlags": "EnableWorkerIndexing" is mentioned in the README documentation but does not match the actual template file which uses "PYTHON_ENABLE_INIT_INDEXING": "1". These should be consistent. The template file appears to use the correct setting for HTTP streaming support in Azure Functions Python.

Copilot uses AI. Check for mistakes.
}

### Stream workflow - Creative task
POST http localhost:7071/api/workflow/stream
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing colon after "POST" in the HTTP request. The correct format should be "POST http://localhost:7071/api/workflow/stream" (with colon after POST, like on line 44) instead of "POST http" without the colon separator.

Suggested change
POST http localhost:7071/api/workflow/stream
POST http://localhost:7071/api/workflow/stream

Copilot uses AI. Check for mistakes.
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.text) {
document.body.innerHTML += data.text;
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaScript example appends untrusted streaming text directly into the DOM via document.body.innerHTML += data.text, which can enable cross-site scripting if the agent response ever includes attacker-controlled markup (e.g., via user prompts or external data). An attacker could cause the agent to emit HTML with event handlers (like <img onerror=...>) that would execute in the context of your page when inserted with innerHTML. Use a safe text API such as textContent or a proper HTML sanitizer when rendering agent output.

Suggested change
document.body.innerHTML += data.text;
document.body.textContent += data.text;

Copilot uses AI. Check for mistakes.
# eventSource.onmessage = (event) => {
# const data = JSON.parse(event.data);
# if (data.text) {
# document.body.innerHTML += data.text;
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This JavaScript browser example appends data.text directly into the DOM using document.body.innerHTML += data.text, which allows any HTML emitted by the agent to be interpreted and can lead to cross-site scripting. If an attacker can influence the agent’s output (for example through crafted prompts), they can inject HTML with event handlers that runs in the page context. Prefer using textContent or building DOM nodes safely instead of writing untrusted strings to innerHTML.

Suggested change
# document.body.innerHTML += data.text;
# const textNode = document.createTextNode(data.text);
# document.body.appendChild(textNode);

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +160
# output.innerHTML += '<div class="workflow-start">Workflow Started</div>';
# break;
# case 'agent_started':
# currentAgent = data.agent;
# output.innerHTML += `<div class="agent-start">[${currentAgent}]</div>`;
# break;
# case 'agent_transition':
# output.innerHTML += `<div class="transition">${data.from} → ${data.to}</div>`;
# break;
# case 'text':
# output.innerHTML += data.text;
# break;
# case 'tool_call':
# output.innerHTML += `<div class="tool">🔧 ${data.tool}</div>`;
# break;
# case 'done':
# output.innerHTML += '<div class="complete">✓ Completed</div>';
# eventSource.close();
# break;
# case 'error':
# output.innerHTML += `<div class="error">Error: ${data.error}</div>`;
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow JavaScript example repeatedly appends untrusted fields like data.text, data.tool, and data.error into output.innerHTML, which can result in cross-site scripting if any of those values contain attacker-controlled markup. Because these values originate from streamed server responses (ultimately based on user input and model output), an attacker could cause HTML with event handlers to be injected and executed in the browser. Use text-safe APIs (e.g., textContent) or sanitize/escape these values before inserting them into the DOM instead of concatenating into innerHTML.

Suggested change
# output.innerHTML += '<div class="workflow-start">Workflow Started</div>';
# break;
# case 'agent_started':
# currentAgent = data.agent;
# output.innerHTML += `<div class="agent-start">[${currentAgent}]</div>`;
# break;
# case 'agent_transition':
# output.innerHTML += `<div class="transition">${data.from} → ${data.to}</div>`;
# break;
# case 'text':
# output.innerHTML += data.text;
# break;
# case 'tool_call':
# output.innerHTML += `<div class="tool">🔧 ${data.tool}</div>`;
# break;
# case 'done':
# output.innerHTML += '<div class="complete">✓ Completed</div>';
# eventSource.close();
# break;
# case 'error':
# output.innerHTML += `<div class="error">Error: ${data.error}</div>`;
# const workflowStartDiv = document.createElement('div');
# workflowStartDiv.className = 'workflow-start';
# workflowStartDiv.textContent = 'Workflow Started';
# output.appendChild(workflowStartDiv);
# break;
# case 'agent_started':
# currentAgent = data.agent;
# const agentStartDiv = document.createElement('div');
# agentStartDiv.className = 'agent-start';
# agentStartDiv.textContent = `[${currentAgent}]`;
# output.appendChild(agentStartDiv);
# break;
# case 'agent_transition':
# const transitionDiv = document.createElement('div');
# transitionDiv.className = 'transition';
# transitionDiv.textContent = `${data.from} → ${data.to}`;
# output.appendChild(transitionDiv);
# break;
# case 'text':
# const textSpan = document.createElement('span');
# textSpan.textContent = data.text;
# output.appendChild(textSpan);
# break;
# case 'tool_call':
# const toolDiv = document.createElement('div');
# toolDiv.className = 'tool';
# toolDiv.textContent = `🔧 ${data.tool}`;
# output.appendChild(toolDiv);
# break;
# case 'done':
# const completeDiv = document.createElement('div');
# completeDiv.className = 'complete';
# completeDiv.textContent = '✓ Completed';
# output.appendChild(completeDiv);
# eventSource.close();
# break;
# case 'error':
# const errorDiv = document.createElement('div');
# errorDiv.className = 'error';
# errorDiv.textContent = `Error: ${data.error}`;
# output.appendChild(errorDiv);

Copilot uses AI. Check for mistakes.
@larohra
Copy link
Contributor

larohra commented Dec 22, 2025

I wonder if the https://github.com/azure-samples org is a better place for these samples than here, since they showcase a way we can use agent-framework in tandem with azure-function's streaming responses API.

If you would prefer keeping in this repo, a better place would be to move it under samples/getting_started/agents/azurefunctions vs samples/getting_started/azurefunctions since the latter is meant primarily for the examples that use the agent-framework-azurefunctions package.

# pass

###
# JavaScript Browser Example
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the demo.http is the right file for adding all these client examples. Maybe add it in separate file specific to the clients?


Before running this sample:

1. **Azure OpenAI Resource**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Common pre-reqs should be added to the parent readme to avoid duplication.


## 🚀 Setup

### 1. Create Virtual Environment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this readme should be small and clean with specific things only for this sample

}
```

### Using cURL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're providing sample files for each client then maybe not needed in the Readme. Or just point to those files in the readme

Comment on lines +255 to +287

## 🆚 Comparison with Durable Samples

| Feature | This Sample | Durable Samples (01-10) |
|---------|-------------|-------------------------|
| Response Mode | Real-time streaming | Fire-and-forget + polling |
| State Storage | None | Azure Storage/Azurite |
| Timeout | ~230s (HTTP timeout) | Hours/days |
| Status Queries | Not supported | Supported |
| Complexity | Low | Medium-High |
| Setup Required | Minimal | Storage + orchestration |

## ⚠️ Limitations

1. **Timeout Constraints**
- HTTP connections time out (~230 seconds)
- Not suitable for very long-running tasks
- Use durable samples for longer executions

2. **No State Persistence**
- Can't query status after completion
- Can't resume interrupted executions
- Use durable samples if you need these features

3. **No Orchestration Patterns**
- No built-in concurrency, conditionals, or HITL
- Use durable samples for complex workflows

## 🎓 Next Steps

- **[02_workflow_http_streaming](../02_workflow_http_streaming)** - Stream multi-agent workflows
- **[04_single_agent_orchestration_chaining](../../04_single_agent_orchestration_chaining)** - Learn durable orchestration
- **[07_single_agent_orchestration_hitl](../../07_single_agent_orchestration_hitl)** - Add human-in-the-loop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think any of these lines are necessary since you already do cover it in the parent readme

@@ -0,0 +1,5 @@
agent-framework
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already includes agent-framework-azure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants