chore: import upstream snapshot with attribution
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
Flake8 Lint / flake8 (push) Has been cancelled
Spell check CI / Spell_Check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Copy this file to .env and fill in your values.
|
||||
# cp .env.example .env
|
||||
|
||||
# Azure OpenAI endpoint
|
||||
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
|
||||
|
||||
AZURE_OPENAI_API_KEY=your-api-key
|
||||
|
||||
AZURE_OPENAI_DEPLOYMENT=gpt-4o
|
||||
@@ -0,0 +1,46 @@
|
||||
# Basic Chat — Microsoft Agent Framework
|
||||
|
||||
This is the [Microsoft Agent Framework (MAF)](https://devblogs.microsoft.com/agent-framework/microsoft-agent-framework-version-1-0/) version of the [chat-basic](../chat-basic/) Prompt Flow example.
|
||||
|
||||
It implements the same behaviour: a helpful assistant chatbot that remembers conversation history and responds to user questions.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
[InputExecutor] ──→ [ChatExecutor]
|
||||
(question + (Agent with
|
||||
chat_history) FoundryChatClient)
|
||||
```
|
||||
|
||||
| Prompt Flow concept | MAF equivalent |
|
||||
|---|---|
|
||||
| `flow.dag.yaml` | `WorkflowBuilder` in `chat_flow.py` |
|
||||
| `chat.jinja2` (system prompt) | `Agent(instructions="You are a helpful assistant.")` |
|
||||
| LLM node (`api: chat`) | `FoundryChatClient` + `Agent.run()` |
|
||||
| `chat_history` input | Message list assembled in `InputExecutor` |
|
||||
| `open_ai_connection` | Environment variables (`FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`) + `DefaultAzureCredential` |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+
|
||||
- An Azure subscription with a Microsoft Foundry project (or Azure OpenAI resource)
|
||||
- `az login` completed
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
|
||||
cp .env.example .env
|
||||
# Edit .env with your Foundry project endpoint and model deployment name
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
python chat_flow.py
|
||||
```
|
||||
|
||||
This runs two test interactions:
|
||||
1. A single-turn question with no history
|
||||
2. A follow-up question with one prior turn of chat history
|
||||
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
Basic Chat — Microsoft Agent Framework version.
|
||||
|
||||
Migrated from the Prompt Flow chat-basic example.
|
||||
Original flow: Input (question + chat_history) → LLM node → answer
|
||||
|
||||
This workflow uses a single Agent with FoundryChatClient to replicate
|
||||
the same chat behaviour: a helpful assistant that remembers conversation
|
||||
history and responds to user questions.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import Agent, Executor, WorkflowBuilder, WorkflowContext, handler
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatInput:
|
||||
"""Mirrors the Prompt Flow inputs: question + chat_history."""
|
||||
|
||||
question: str
|
||||
chat_history: list | None = None
|
||||
|
||||
|
||||
class InputExecutor(Executor):
|
||||
"""Replaces the Prompt Flow Input node.
|
||||
|
||||
Accepts a ChatInput, formats the conversation history into the prompt,
|
||||
and forwards the assembled prompt string to the LLM executor.
|
||||
"""
|
||||
|
||||
@handler
|
||||
async def receive(self, chat_input: ChatInput, ctx: WorkflowContext[str]) -> None:
|
||||
parts = []
|
||||
|
||||
# Replay chat history as a formatted conversation
|
||||
if chat_input.chat_history:
|
||||
for turn in chat_input.chat_history:
|
||||
parts.append(f"User: {turn['inputs']['question']}")
|
||||
parts.append(f"Assistant: {turn['outputs']['answer']}")
|
||||
|
||||
# Append the current user question
|
||||
parts.append(chat_input.question)
|
||||
|
||||
await ctx.send_message("\n".join(parts))
|
||||
|
||||
|
||||
class ChatExecutor(Executor):
|
||||
"""Replaces the Prompt Flow LLM (chat) node.
|
||||
|
||||
Uses OpenAIChatClient (Azure routing) + Agent with the same system prompt
|
||||
as the original chat.jinja2 template: "You are a helpful assistant."
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
client = OpenAIChatClient(
|
||||
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
||||
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
|
||||
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
||||
)
|
||||
self._agent = Agent(
|
||||
client=client,
|
||||
name="ChatAgent",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
@handler
|
||||
async def call_llm(self, question: str, ctx: WorkflowContext[Never, str]) -> None:
|
||||
response = await self._agent.run(question)
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
# ── Build the workflow ────────────────────────────────────────────────────────
|
||||
def create_workflow():
|
||||
"""Return a fresh workflow instance (safe for concurrent / repeated runs)."""
|
||||
_input = InputExecutor(id="input")
|
||||
_chat = ChatExecutor(id="chat")
|
||||
return (
|
||||
WorkflowBuilder(name="BasicChatWorkflow", start_executor=_input)
|
||||
.add_edge(_input, _chat)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
# Simple single-turn test (no history)
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(ChatInput(question="What is ChatGPT?"))
|
||||
print("Answer:", result.get_outputs()[0])
|
||||
print()
|
||||
|
||||
# Multi-turn test (with chat history)
|
||||
history = [
|
||||
{
|
||||
"inputs": {"question": "What is ChatGPT?"},
|
||||
"outputs": {"answer": "ChatGPT is a large language model chatbot developed by OpenAI."},
|
||||
}
|
||||
]
|
||||
workflow2 = create_workflow()
|
||||
result = await workflow2.run(
|
||||
ChatInput(
|
||||
question="What is the difference between ChatGPT and GPT-4?",
|
||||
chat_history=history,
|
||||
)
|
||||
)
|
||||
print("Answer:", result.get_outputs()[0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,3 @@
|
||||
agent-framework>=1.0.1
|
||||
agent-framework-openai>=1.0.1
|
||||
python-dotenv
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Sample script to test the Basic Chat MAF workflow.
|
||||
|
||||
Run:
|
||||
python test_chat_flow.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from chat_flow import ChatInput, create_workflow
|
||||
|
||||
|
||||
async def main():
|
||||
# Test 1: Single-turn — no chat history
|
||||
print("=" * 60)
|
||||
print("Test 1: Single-turn (no history)")
|
||||
print("=" * 60)
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(ChatInput(question="What is ChatGPT?"))
|
||||
print("Q: What is ChatGPT?")
|
||||
print(f"A: {result.get_outputs()[0]}\n")
|
||||
|
||||
# Test 2: Multi-turn — with one prior exchange
|
||||
print("=" * 60)
|
||||
print("Test 2: Multi-turn (with history)")
|
||||
print("=" * 60)
|
||||
history = [
|
||||
{
|
||||
"inputs": {"question": "What is ChatGPT?"},
|
||||
"outputs": {"answer": "ChatGPT is a large language model chatbot developed by OpenAI."},
|
||||
}
|
||||
]
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
ChatInput(
|
||||
question="How is it different from GPT-4?",
|
||||
chat_history=history,
|
||||
)
|
||||
)
|
||||
print("Q: How is it different from GPT-4?")
|
||||
print(f"A: {result.get_outputs()[0]}\n")
|
||||
|
||||
# Test 3: Multi-turn — longer conversation
|
||||
print("=" * 60)
|
||||
print("Test 3: Multi-turn (longer conversation)")
|
||||
print("=" * 60)
|
||||
history = [
|
||||
{
|
||||
"inputs": {"question": "What is 2+2?"},
|
||||
"outputs": {"answer": "4"},
|
||||
},
|
||||
{
|
||||
"inputs": {"question": "Multiply that by 3"},
|
||||
"outputs": {"answer": "12"},
|
||||
},
|
||||
]
|
||||
workflow = create_workflow()
|
||||
result = await workflow.run(
|
||||
ChatInput(
|
||||
question="Now divide by 6",
|
||||
chat_history=history,
|
||||
)
|
||||
)
|
||||
print("Q: Now divide by 6")
|
||||
print(f"A: {result.get_outputs()[0]}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user