Home Gallery AISPA Paper GitHub Follow

promptflow system prompt

Category: General-purpose assistants. Audited against the AISPA standard.

4 Prompts on record
0 Flagged instructions
AI audit Audit source
D1 · Identity Transparency D2 · Truthfulness & Information Integrity D3 · Privacy & Data Protection D4 · Tool/Action Safety D5 · User Agency & Manipulation Prevention D6 · Unsafe Request Handling D7 · Harm Prevention & User Safety D8 · Fairness, Inclusion & Neutrality

promptflow - .github skills promptflow to maf references nod...

5218 characters

# Node Mapping Reference > Lookup table for converting Prompt Flow node types to MAF equivalents. > Read this when you need to map a specific Prompt Flow concept to MAF code. ## Core Mapping Table | Prompt Flow Concept | MAF Equivalent | |---|---| | `flow.dag.yaml` (flow definition) | `WorkflowBuilder(name=..., start_executor=...).add_edge(...).build()` | | Any node | `Executor` subclass with a `@handler` method | | LLM node (`type: llm`) | `Agent(client=OpenAIChatClient(...), instructions=...)` inside an Executor | | Python node (`type: python`, `source.type: code`) | Plain Python logic inside an `Executor` `@handler` | | Custom-tool node (`type: python`, `source.type: package`) | **Call the tool's underlying Python function directly inside an `Executor` `@handler`. Do NOT remap to `OpenAIChatClient` / `Agent`.** See [topics/custom-tool-nodes.md](../topics/custom-tool-nodes.md). | | Prompt node (`.jinja2` template) | System prompt string passed to `Agent(instructions=...)`, or string formatting in `@handler` | | Conditional / If node (`activate_config`) | `.add_edge(source, target, condition=fn)` | | Parallel nodes (no shared deps) | `.add_fan_out_edges(source, [targetA, targetB])` | | Merge / aggregate node | `.add_fan_in_edges([sourceA, sourceB], target)` | | `aggregation: true` node (eval batch) | Standalone function + `EvalRunner` orchestrator. See [topics/evaluation-flows.md](../topics/evaluation-flows.md). | | Embed Text + Vector Lookup + LLM (RAG) | `AzureAISearchContextProvider` via `context_providers=[...]` on `Agent` | | Python tool node | Plain function passed to `Agent(tools=[fn1, fn2])` | | Flow inputs | Type annotation on start Executor's `@handler` parameter (use `@dataclass` for multiple inputs) | | Flow outputs (`is_chat_output`) | `await ctx.yield_output(value)` in the terminal Executor | | Connections (credentials) | Environment variables + `OpenAIChatClient(azure_endpoint=..., api_key=...)` for key auth, or `credential=DefaultAzureCredential()` for Microsoft Entra / managed identity. | | `chat_history` input | Format into prompt string in an InputExecutor before passing to Agent | | Variants | Separate Agent instances with different `instructions` strings | | Multimodal input (image URL) | `Content.from_uri(url, media_type="image/png")` inside a `Message` (see [topics/multimodal.md](../topics/multimodal.md)) | | Multimodal input (base64 image) | `Content.from_data(data=bytes, media_type="image/png")` inside a `Message` | | `custom_llm` node with images | Executor that builds a `Message("user", [Content.from_uri(...), text])` and passes it to `Agent.run()` | ## Node Collapsing Patterns The default mapping is **1 PF node → 1 MAF Executor**, and the graph topology must be preserved (see SKILL.md Rule 13). The patterns below are the **only** merges allowed. Any merge MUST be annotated in the Phase 1 node-mapping table with the pattern name (e.g., "Collapsed via: Prompt + LLM"). If a combination is not on this list, keep the nodes separate as 1:1 Executors — do not invent new merges. ### Allowed merges (closed list) - **Prompt template + LLM node** → Merge into one Executor: extract system prompt to `Agent(instructions=...)`, format user prompt as a string with variables, then call `Agent.run()` - Example: `hello_prompt` (`.jinja2`) + `llm` (LLM) → single `LLMExecutor` with both template and agent - Required: the prompt node must feed *only* the LLM node (no other downstream consumers). - **LLM + simple post-processing Python node** → Merge if post-processing is a few lines (e.g., extract substring, parse JSON, format output) - Required: post-processing is ≤ ~20 lines, has no external API calls, and the LLM node feeds *only* this post-processing node. - **Static-data Python node** → Inline as module-level constant (e.g., `prepare_examples()`, `math_example()`) if data is <50 lines and the node has no inputs (pure constant producer). ### When to keep separate - Node would need concurrent execution (e.g., two branches from the same source run in parallel) - Post-processing is complex (>20 lines) or calls external APIs - Output is consumed by multiple downstream nodes (keep it separate for clarity and reuse) - Node has stateful side effects that should be isolated ### Example: Prompt + LLM collapse ```python # Instead of two Executors: class PromptExecutor(Executor): @handler async def receive(self, text: str, ctx: WorkflowContext[str]) -> None: prompt = f"Write a simple {text} program..." await ctx.send_message(prompt) class LLMExecutor(Executor): @handler async def call_llm(self, prompt: str, ctx: WorkflowContext[Never, str]) -> None: response = await self._agent.run(prompt) await ctx.yield_output(response.text) # Can merge into one: class PromptAndLLMExecutor(Executor): def __init__(self, **kwargs): super().__init__(**kwargs) self._agent = Agent(client=..., instructions=...) @handler async def generate(self, text: str, ctx: WorkflowContext[Never, str]) -> None: prompt = f"Write a simple {text} program..." response = await self._agent.run(prompt) await ctx.yield_output(response.text) ```

promptflow - docs how to guides develop a prompty prompty ou...

8500 characters

# Prompty output format :::{admonition} Experimental feature This is an experimental feature, and may change at any time. Learn [more](../faq.md#stable-vs-experimental). ::: In this doc, you will learn: - Understand how to handle output format of prompty like: `text`, `json_object`. - Understand how to consume **stream** output of prompty ## Formatting prompty output ### Text output By default, prompty returns the message from the first choice in the response. Below is an example of how to format a prompty for text output: ```yaml --- name: Text Format Prompt description: A basic prompt that uses the GPT-3 chat API to answer questions model: api: chat configuration: type: azure_openai connection: open_ai_connection azure_deployment: gpt-35-turbo-0125 parameters: max_tokens: 128 temperature: 0.2 inputs: first_name: type: string last_name: type: string question: type: string sample: first_name: John last_name: Doe question: what is the meaning of life? --- system: You are an AI assistant who helps people find information. As the assistant, you answer questions briefly, succinctly, and in a personable manner using markdown and even add some personal flair with appropriate emojis. # Safety - You **should always** reference factual statements to search results based on [relevant documents] - Search results based on [relevant documents] may be incomplete or irrelevant. You do not make assumptions # Customer You are helping {{first_name}} {{last_name}} to find answers to their questions. Use their name to address them in your responses. user: {{question}} ``` The output of the prompty is a string content, as shown in the example below: ```text Ah, the age-old question about the meaning of life! 🌍🤔 The meaning of life is a deeply philosophical and subjective topic. Different people have different perspectives on it. Some believe that the meaning of life is to seek happiness and fulfillment, while others find meaning in personal relationships, accomplishments, or spiritual beliefs. Ultimately, it's up to each individual to explore and discover their own purpose and meaning in life. 🌟 ``` ### Json object output Prompty can return the content of the first choice as a dictionary object when the following conditions are met: - The `response_format` is defined as `type: json_object` in the parameters - The template specifies the JSON format for the return value. **Note**: `json_object` response_format is compatible with `GPT-4 Turbo` and all GPT-3.5 Turbo models newer than `gpt-3.5-turbo-1106`. For more details, refer to this [document](https://platform.openai.com/docs/api-reference/chat/create#chat-create-response_format). Here’s how to configure a prompty for JSON object output: ```yaml --- name: Json Format Prompt description: A basic prompt that uses the GPT-3 chat API to answer questions model: api: chat configuration: type: azure_openai azure_deployment: gpt-35-turbo-0125 connection: open_ai_connection parameters: max_tokens: 128 temperature: 0.2 response_format: type: json_object inputs: first_name: type: string last_name: type: string question: type: string sample: first_name: John last_name: Doe question: what is the meaning of life? --- system: You are an AI assistant who helps people find information. As the assistant, you answer questions briefly, succinctly. Your structured response. Only accepts JSON format, likes below: {"name": customer_name, "answer": the answer content} # Customer You are helping {{first_name}} {{last_name}} to find answers to their questions. Use their name to address them in your responses. user: {{question}} ``` The output of the prompty is a JSON object containing the content of the first choice: ```json { "name": "John", "answer": "The meaning of life is a philosophical question that varies depending on individual beliefs and perspectives." } ``` Users can also specify the fields to be returned by configuring the output section: ```yaml --- name: Json Format Prompt description: A basic prompt that uses the GPT-3 chat API to answer questions model: api: chat configuration: type: azure_openai azure_deployment: gpt-35-turbo-0125 connection: open_ai_connection parameters: max_tokens: 128 temperature: 0.2 response_format: type: json_object inputs: first_name: type: string last_name: type: string question: type: string outputs: answer: type: string sample: first_name: John last_name: Doe question: what is the meaning of life? --- system: You are an AI assistant who helps people find information. As the assistant, you answer questions briefly, succinctly. Your structured response. Only accepts JSON format, likes below: {"name": customer_name, "answer": the answer content} # Customer You are helping {{first_name}} {{last_name}} to find answers to their questions. Use their name to address them in your responses. user: {{question}} ``` Prompty will then return the outputs as specified by the user: ```json { "answer": "The meaning of life is a philosophical question that varies depending on individual beliefs and perspectives." } ``` ### All choices In certain scenarios, users may require access to the original response from the language model (LLM) for further processing. This can be achieved by setting `response=all`, which allows retrieval of the original LLM response. For detailed information, please refer to the [LLM response](https://platform.openai.com/docs/api-reference/chat/object). ```yaml --- name: All Choices Text Format Prompt description: A basic prompt that uses the GPT-3 chat API to answer questions model: api: chat configuration: type: azure_openai connection: open_ai_connection azure_deployment: gpt-35-turbo-0125 parameters: max_tokens: 128 temperature: 0.2 n: 3 response: all inputs: first_name: type: string last_name: type: string question: type: string sample: first_name: John last_name: Doe question: what is the meaning of life? --- system: You are an AI assistant who helps people find information. As the assistant, you answer questions briefly, succinctly, and in a personable manner using markdown and even add some personal flair with appropriate emojis. # Safety - You **should always** reference factual statements to search results based on [relevant documents] - Search results based on [relevant documents] may be incomplete or irrelevant. You do not make assumptions # Customer You are helping {{first_name}} {{last_name}} to find answers to their questions. Use their name to address them in your responses. user: {{question}} ``` ### Streaming output For prompty configurations where the response_format is `text`, setting `stream=true` in the parameters will result in the Promptflow SDK returning a generator. Each item from the generator represents the content of a chunk. Here’s how to configure a prompty for streaming text output: ```yaml --- name: Stream Mode Text Format Prompt description: A basic prompt that uses the GPT-3 chat API to answer questions model: api: chat configuration: type: azure_openai connection: open_ai_connection azure_deployment: gpt-35-turbo-0125 parameters: max_tokens: 512 temperature: 0.2 stream: true inputs: first_name: type: string last_name: type: string question: type: string sample: first_name: John last_name: Doe question: What's the steps to get rich? --- system: You are an AI assistant who helps people find information. and in a personable manner using markdown and even add some personal flair with appropriate emojis. # Safety - You **should always** reference factual statements to search results based on [relevant documents] - Search results based on [relevant documents] may be incomplete or irrelevant. You do not make assumptions # Customer You are helping user to find answers to their questions. user: {{question}} ``` To retrieve elements from the generator results, use the following Python code: ```python from promptflow.core import Prompty # load prompty as a flow prompty_func = Prompty.load("stream_output.prompty") # execute the flow as function question = "What's the steps to get rich?" result = prompty_func(first_name="John", last_name="Doh", question=question) # Type of the result is generator for item in result: print(item, end="") ```

promptflow - docs how to guides enable streaming mode

15149 characters

# Use streaming endpoints deployed from prompt flow In prompt flow, you can [deploy flow as REST endpoint](./deploy-a-flow/index.md) for real-time inference. When consuming the endpoint by sending a request, the default behavior is that the online endpoint will keep waiting until the whole response is ready, and then send it back to the client. This can cause a long delay for the client and a poor user experience. To avoid this, you can use streaming when you consume the endpoints. Once streaming enabled, you don't have to wait for the whole response ready. Instead, the server will send back the response in chunks as they are generated. The client can then display the response progressively, with less waiting time and more interactivity. This article will describe the scope of streaming, how streaming works, and how to consume streaming endpoints. ## Create a streaming enabled flow If you want to use the streaming mode, you need to create a flow that has a node that produces a string generator as the flow’s output. A string generator is an object that can return one string at a time when requested. You can use the following types of nodes to create a string generator: - LLM node: This node uses a large language model to generate natural language responses based on the input. ```jinja {# Sample prompt template for LLM node #} # system: You are a helpful assistant. # user: {{question}} ``` - Python tools node: This node allows you to write custom Python code that can yield string outputs. You can use this node to call external APIs or libraries that support streaming. For example, you can use this code to echo the input word by word: ```python from promptflow.core import tool # Sample code echo input by yield in Python tool node @tool def my_python_tool(paragraph: str) -> str: yield "Echo: " for word in paragraph.split(): yield word + " " ``` In this guide, we will use the ["Chat with Wikipedia"](https://github.com/microsoft/promptflow/tree/main/examples/flows/chat/chat-with-wikipedia) sample flow as an example. This flow processes the user’s question, searches Wikipedia for relevant articles, and answers the question with information from the articles. It uses streaming mode to show the progress of the answer generation. ![chat_wikipedia.png](../media/how-to-guides/how-to-enable-streaming-mode/chat_wikipedia_center.png) ## Deploy the flow as an online endpoint To use the streaming mode, you need to deploy your flow as an online endpoint. This will allow you to send requests and receive responses from your flow in real time. Follow [this guide](./deploy-a-flow/index.md) to deploy your flow as an online endpoint. > [!NOTE] > > You can follow this document to deploy an [online endpoint](https://learn.microsoft.com/en-us/azure/machine-learning/prompt-flow/how-to-deploy-for-real-time-inference?view=azureml-api-2). > Please deploy with runtime environment version later than version `20230816.v10`. > You can check your runtime version and update runtime in the run time detail page. ## Understand the streaming process When you have an online endpoint, the client and the server need to follow specific principles for [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) to utilize the streaming mode: Content negotiation is like a conversation between the client and the server about the preferred format of the data they want to send and receive. It ensures effective communication and agreement on the format of the exchanged data. To understand the streaming process, consider the following steps: - First, the client constructs an HTTP request with the desired media type included in the `Accept` header. The media type tells the server what kind of data format the client expects. It's like the client saying, "Hey, I'm looking for a specific format for the data you'll send me. It could be JSON, text, or something else." For example, `application/json` indicates a preference for JSON data, `text/event-stream` indicates a desire for streaming data, and `*/*` means the client accepts any data format. > [!NOTE] > > If a request lacks an `Accept` header or has empty `Accept` header, it implies that the client will accept any media type in response. The server treats it as `*/*`. - Next, the server responds based on the media type specified in the `Accept` header. It's important to note that the client may request multiple media types in the `Accept` header, and the server must consider its capabilities and format priorities to determine the appropriate response. - First, the server checks if `text/event-stream` is explicitly specified in the `Accept` header: - For a stream-enabled flow, the server returns a response with a `Content-Type` of `text/event-stream`, indicating that the data is being streamed. - For a non-stream-enabled flow, the server proceeds to check for other media types specified in the header. - If `text/event-stream` is not specified, the server then checks if `application/json` or `*/*` is specified in the `Accept` header: - In such cases, the server returns a response with a `Content-Type` of `application/json`, providing the data in JSON format. - If the `Accept` header specifies other media types, such as `text/html`: - The server returns a `424` response with a PromptFlow runtime error code `UserError` and a runtime HTTP status `406`, indicating that the server cannot fulfill the request with the requested data format. > Note: Please refer [handle errors](#handle-errors) for details. - Finally, the client checks the `Content-Type` response header. If it is set to `text/event-stream`, it indicates that the data is being streamed. Let’s take a closer look at how the streaming process works. The response data in streaming mode follows the format of [server-sent events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). The overall process works as follows: ### 0. The client sends a message to the server. ``` POST https://<your-endpoint>.inference.ml.azure.com/score Content-Type: application/json Authorization: Bearer <key or token of your endpoint> Accept: text/event-stream { "question": "Hello", "chat_history": [] } ``` > [!NOTE] > > The `Accept` header is set to `text/event-stream` to request a stream response. ### 1. The server sends back the response in streaming mode. ``` HTTP/1.1 200 OK Content-Type: text/event-stream; charset=utf-8 Connection: close Transfer-Encoding: chunked data: {"answer": ""} data: {"answer": "Hello"} data: {"answer": "!"} data: {"answer": " How"} data: {"answer": " can"} data: {"answer": " I"} data: {"answer": " assist"} data: {"answer": " you"} data: {"answer": " today"} data: {"answer": " ?"} data: {"answer": ""} ``` Note that the `Content-Type` is set to `text/event-stream; charset=utf-8`, indicating the response is an event stream. The client should decode the response data as server-sent events and display them incrementally. The server will close the HTTP connection after all the data is sent. Each response event is the delta to the previous event. It is recommended for the client to keep track of the merged data in memory and send them back to the server as chat history in the next request. ### 2. The client sends another chat message, along with the full chat history, to the server. ``` POST https://<your-endpoint>.inference.ml.azure.com/score Content-Type: application/json Authorization: Bearer <key or token of your endpoint> Accept: text/event-stream { "question": "Glad to know you!", "chat_history": [ { "inputs": { "question": "Hello" }, "outputs": { "answer": "Hello! How can I assist you today?" } } ] } ``` ### 3. The server sends back the answer in streaming mode. ``` HTTP/1.1 200 OK Content-Type: text/event-stream; charset=utf-8 Connection: close Transfer-Encoding: chunked data: {"answer": ""} data: {"answer": "Nice"} data: {"answer": " to"} data: {"answer": " know"} data: {"answer": " you"} data: {"answer": " too"} data: {"answer": "!"} data: {"answer": " Is"} data: {"answer": " there"} data: {"answer": " anything"} data: {"answer": " I"} data: {"answer": " can"} data: {"answer": " help"} data: {"answer": " you"} data: {"answer": " with"} data: {"answer": "?"} data: {"answer": ""} ``` ### 4. The chat continues in a similar way. ## Handle errors The client should check the HTTP response code first. See [this table](https://learn.microsoft.com/azure/machine-learning/how-to-troubleshoot-online-endpoints?view=azureml-api-2&tabs=cli#http-status-codes) for common error codes returned by online endpoints. If the response code is "424 Model Error", it means that the error is caused by the model’s code. The error response from a PromptFlow model always follows this format: ```json { "error": { "code": "UserError", "message": "Media type text/event-stream in Accept header is not acceptable. Supported media type(s) - application/json", } } ``` * It is always a JSON dictionary with only one key "error" defined. * The value for "error" is a dictionary, containing "code", "message". * "code" defines the error category. Currently, it may be "UserError" for bad user inputs and "SystemError" for errors inside the service. * "message" is a description of the error. It can be displayed to the end user. ## How to consume the server-sent events ### Consume using Python In this sample usage, we are using the `SSEClient` class. This class is not a built-in Python class and needs to be installed separately. You can install it via pip: ```bash pip install sseclient-py ``` A sample usage would like: ```python import requests from sseclient import SSEClient from requests.exceptions import HTTPError try: response = requests.post(url, json=body, headers=headers, stream=stream) response.raise_for_status() content_type = response.headers.get('Content-Type') if "text/event-stream" in content_type: client = SSEClient(response) for event in client.events(): # Handle event, i.e. print to stdout else: # Handle json response except HTTPError: # Handle exceptions ``` ### Consume using JavaScript There are several libraries to consume server-sent events in JavaScript. Here is [one of them as an example](https://www.npmjs.com/package/sse.js?activeTab=code). ## A sample chat app using Python Here is a sample chat app written in Python. (Click [here](../media/how-to-guides/how-to-enable-streaming-mode/scripts/chat_app.py) to view the source code.) ![chat_app](../media/how-to-guides/how-to-enable-streaming-mode/chat_app.gif) ## Advance usage - hybrid stream and non-stream flow output Sometimes, you may want to get both stream and non-stream results from a flow output. For example, in the “Chat with Wikipedia” flow, you may want to get not only LLM’s answer, but also the list of URLs that the flow searched. To do this, you need to modify the flow to output a combination of stream LLM’s answer and non-stream URL list. In the sample "Chat With Wikipedia" flow, the output is connected to the LLM node `augmented_chat`. To add the URL list to the output, you need to add an output field with the name `url` and the value `${get_wiki_url.output}`. ![chat_wikipedia_dual_output_center.png](../media/how-to-guides/how-to-enable-streaming-mode/chat_wikipedia_dual_output_center.png) The output of the flow will be a non-stream field as the base and a stream field as the delta. Here is an example of request and response. ### 0. The client sends a message to the server. ``` POST https://<your-endpoint>.inference.ml.azure.com/score Content-Type: application/json Authorization: Bearer <key or token of your endpoint> Accept: text/event-stream { "question": "When was ChatGPT launched?", "chat_history": [] } ``` ### 1. The server sends back the answer in streaming mode. ``` HTTP/1.1 200 OK Content-Type: text/event-stream; charset=utf-8 Connection: close Transfer-Encoding: chunked data: {"url": ["https://en.wikipedia.org/w/index.php?search=ChatGPT", "https://en.wikipedia.org/w/index.php?search=GPT-4"]} data: {"answer": ""} data: {"answer": "Chat"} data: {"answer": "G"} data: {"answer": "PT"} data: {"answer": " was"} data: {"answer": " launched"} data: {"answer": " on"} data: {"answer": " November"} data: {"answer": " "} data: {"answer": "30"} data: {"answer": ","} data: {"answer": " "} data: {"answer": "202"} data: {"answer": "2"} data: {"answer": "."} data: {"answer": " \n\n"} ... data: {"answer": "PT"} data: {"answer": ""} ``` ### 2. The client sends another chat message, along with the full chat history, to the server. ``` POST https://<your-endpoint>.inference.ml.azure.com/score Content-Type: application/json Authorization: Bearer <key or token of your endpoint> Accept: text/event-stream { "question": "When did OpenAI announce GPT-4? How long is it between these two milestones?", "chat_history": [ { "inputs": { "question": "When was ChatGPT launched?" }, "outputs": { "url": [ "https://en.wikipedia.org/w/index.php?search=ChatGPT", "https://en.wikipedia.org/w/index.php?search=GPT-4" ], "answer": "ChatGPT was launched on November 30, 2022. \n\nSOURCES: https://en.wikipedia.org/w/index.php?search=ChatGPT" } } ] } ``` ### 3. The server sends back the answer in streaming mode. ``` HTTP/1.1 200 OK Content-Type: text/event-stream; charset=utf-8 Connection: close Transfer-Encoding: chunked data: {"url": ["https://en.wikipedia.org/w/index.php?search=Generative pre-trained transformer ", "https://en.wikipedia.org/w/index.php?search=Microsoft "]} data: {"answer": ""} data: {"answer": "Open"} data: {"answer": "AI"} data: {"answer": " released"} data: {"answer": " G"} data: {"answer": "PT"} data: {"answer": "-"} data: {"answer": "4"} data: {"answer": " in"} data: {"answer": " March"} data: {"answer": " "} data: {"answer": "202"} data: {"answer": "3"} data: {"answer": "."} data: {"answer": " Chat"} data: {"answer": "G"} data: {"answer": "PT"} data: {"answer": " was"} data: {"answer": " launched"} data: {"answer": " on"} data: {"answer": " November"} data: {"answer": " "} data: {"answer": "30"} data: {"answer": ","} data: {"answer": " "} data: {"answer": "202"} data: {"answer": "2"} data: {"answer": "."} data: {"answer": " The"} data: {"answer": " time"} data: {"answer": " between"} data: {"answer": " these"} data: {"answer": " two"} data: {"answer": " milestones"} data: {"answer": " is"} data: {"answer": " approximately"} data: {"answer": " "} data: {"answer": "3"} data: {"answer": " months"} data: {"answer": ".\n\n"} ... data: {"answer": "Chat"} data: {"answer": "G"} data: {"answer": "PT"} data: {"answer": ""} ```

promptflow - .github skills promptflow to maf SKILL

14619 characters

--- name: promptflow-to-maf description: "Convert Prompt Flow flow definitions to Microsoft Agent Framework (MAF) workflows. Parses flow.dag.yaml, maps nodes to Executors, and generates runnable Python code using agent-framework 1.0.x. WHEN: convert promptflow, migrate promptflow, promptflow to MAF, promptflow to agent framework, convert flow.dag.yaml, migrate flow to MAF, convert PF flow, PF to agent-framework, convert DAG flow to workflow, migrate LLM flow. DO NOT USE FOR: writing new MAF workflows from scratch (no source flow), deploying MAF workflows (use maf-online-endpoint), enabling tracing (use maf-tracing), or general agent-framework Q&A." license: MIT metadata: author: Team version: "2.0.0" --- # Prompt Flow → Microsoft Agent Framework Conversion > Convert Prompt Flow `flow.dag.yaml` definitions into runnable MAF `WorkflowBuilder` Python code. ## Triggers Activate this skill when the user wants to: - Convert a Prompt Flow flow to Microsoft Agent Framework - Migrate a `flow.dag.yaml` to MAF workflow code - Rebuild a Prompt Flow application using `agent-framework` --- ## What to Read When (Progressive Disclosure) This skill is split across multiple files. **Always read this file first.** Then read additional files based on what the source flow contains: | Situation | Required Reading | |---|---| | **Every conversion task** | This file + [references/gotchas.md](references/gotchas.md) | | Need to map a specific node type | [references/node-mapping.md](references/node-mapping.md) | | Writing Executor handlers / picking LLM client / setting `temperature`/`max_tokens` | [references/workflow-context.md](references/workflow-context.md) | | Source flow has a node with `source.type: package` | [topics/custom-tool-nodes.md](topics/custom-tool-nodes.md) | | Source flow has image / multimodal inputs | [topics/multimodal.md](topics/multimodal.md) + [examples/multimodal-chat.md](examples/multimodal-chat.md) | | Source flow has any node with `aggregation: true` | [topics/evaluation-flows.md](topics/evaluation-flows.md) + [templates/eval_runner.py](templates/eval_runner.py) + [examples/evaluation.md](examples/evaluation.md) | | Want a complete reference example | [examples/linear-chat.md](examples/linear-chat.md) (basic), [examples/multimodal-chat.md](examples/multimodal-chat.md), [examples/evaluation.md](examples/evaluation.md) | > **Don't pre-load everything.** Read each file lazily when its situation is detected during Phase 1 audit. --- ## Core Rules (apply to every conversion) 1. **Read the source flow first** — Always parse `flow.dag.yaml`, all referenced source files (`.jinja2`, `.py`), and `requirements.txt` before generating anything. 2. **Preserve prompts verbatim** — System prompts, user prompt templates, and any text from `.jinja2` or inline prompt nodes must be copied exactly as they appear in the original Prompt Flow. Do not rephrase, summarize, add, or remove any content — including examples, instructions, formatting, and preambles (e.g., "Read the following conversation and respond:"). The MAF workflow must send the identical prompt text to the LLM. 3. **One Executor per node** — Each Prompt Flow node becomes one `Executor` subclass with a `@handler` method. (Some node combinations may be safely merged — see [references/node-mapping.md](references/node-mapping.md) for "Node Collapsing Patterns".) 4. **Preserve behaviour** — The MAF workflow must produce the same outputs for the same inputs as the original flow. 5. **Use GA packages** — `agent-framework>=1.0.1`, `agent-framework-openai>=1.0.1`. Use preview packages (`--pre`) only for orchestrations, Azure AI Search, or multi-agent features. (Full table in [references/workflow-context.md](references/workflow-context.md).) 6. **Create output folder** — Place generated files in a sibling folder named `<original-folder>-maf/`. 7. **Copy user-defined Python packages** — If the flow imports from internal packages (e.g., `my_utils/`, helper modules), copy the entire package directory into the output folder. The MAF workflow imports directly from the local copy — no `sys.path` manipulation needed. 8. **Generate a test sample** — Always include a runnable `test_<name>.py` sample script. 9. **Never modify the original flow** — All output goes into the new folder. 10. **Evaluation flows use the EvalRunner pattern** — If any node has `aggregation: true`, the flow is an evaluation flow. See [topics/evaluation-flows.md](topics/evaluation-flows.md). 11. **Always export a `create_workflow()` factory** — MAF workflows do not support concurrent `run()` calls on a single instance (`RuntimeError: Workflow is already running`). Every generated `workflow.py` must export a `create_workflow()` factory function that creates a fresh workflow instance per call. Do NOT instantiate Executors or build the workflow at module level. This ensures callers can safely run multiple workflows concurrently (e.g., evaluation batches, parallel API requests, or test suites). For evaluation flows, `EvalRunner` relies on this factory to create one workflow per row. 12. **Copy ALL referenced resources into the output folder** — The generated `-maf/` project must be fully self-contained with zero dependencies on the original Prompt Flow folder. Copy every resource file the flow references: - **Data files** (`.jsonl`, `.csv`, `.json`, `.tsv`) used for testing or evaluation - **Prompt / template files** (`.jinja2`, `.md` used as prompts) - **User-defined Python modules** (`.py` files or packages imported by nodes — see rule 7) - **Any other non-code assets** (e.g., `samples.json`, config files, image assets) Update all file path references (e.g., `DEFAULT_DATA`, `_TEMPLATES_DIR`, `_PROMPT_TEMPLATE`) to point to the local copy using `Path(__file__).parent / ...`. Never use `parent.parent` or relative paths that reach back into the original flow directory. 13. **Preserve graph topology and conditions exactly** — The MAF workflow's graph structure MUST be equivalent to the original `flow.dag.yaml` graph. Specifically: - **Node coverage** — Every Prompt Flow node must map to exactly one MAF Executor (or be merged via an explicitly allowed Collapsing Pattern; see [references/node-mapping.md](references/node-mapping.md)). No PF node may be silently dropped, and no extra Executors may be invented that don't correspond to a PF node or an allowed merge. - **Edge coverage** — Every data reference `${node.output}` in `flow.dag.yaml` must correspond to a MAF edge (`add_edge` / `add_fan_out_edges` / `add_fan_in_edges`) connecting the equivalent Executors. No edges may be added or removed. - **Parallelism preserved** — If two PF nodes run in parallel from a shared upstream node, they must remain parallel in MAF (`add_fan_out_edges`). Do NOT serialize parallel branches. If multiple PF nodes fan into one downstream node, they must use `add_fan_in_edges`. - **Conditions preserved** — Every `activate_config` (when/is) in PF must become an `add_edge(..., condition=fn)` with semantically identical predicate logic. The truth value of the condition for any given input must match the original. - **No reordering** — The execution order implied by the dependency graph must be preserved. Do not move logic from a downstream node into an upstream node (or vice versa) in a way that changes when work happens relative to other branches. - **Mapping table required** — In Phase 1, produce an explicit PF-node → MAF-Executor / edge mapping table (see Phase 1 step 6) and verify it in Phase 4 (see Phase 4 step 22). Any allowed merge must be annotated with the matching Collapsing Pattern from [references/node-mapping.md](references/node-mapping.md). --- ## Conversion Workflow (4 Phases) ### Phase 1 — Audit the Prompt Flow 1. **Read `flow.dag.yaml`** — identify all inputs, outputs, nodes, their types, and edges (data references like `${node.output}`). - For every node, record `type` AND `source.type`. **A node with `source.type: package` is a custom user-defined tool — read [topics/custom-tool-nodes.md](topics/custom-tool-nodes.md) and call it directly from the Executor; do NOT remap to `OpenAIChatClient`/`Agent`.** 2. **Read source files** — open every `.jinja2` template, every `.py` file referenced by `source.type: code` nodes, and the package source for every `source.type: package` node. 3. **Read `requirements.txt`** — note any extra dependencies. 4. **Map the graph** — draw the node dependency graph from `${...}` references. Identify: - Linear chains (A → B → C) - Parallel branches (A → B, A → C) - Conditional branches (`activate_config`) - Fan-in / aggregation points 5. **Detect special cases — load the matching topic file:** - Any node with `aggregation: true` → evaluation flow → load [topics/evaluation-flows.md](topics/evaluation-flows.md) - Any node with `source.type: package` → custom tool → load [topics/custom-tool-nodes.md](topics/custom-tool-nodes.md) - Any image inputs (dict with `data:image/*;url` key, or string starting with `data:image/`) → multimodal → load [topics/multimodal.md](topics/multimodal.md) 6. **Produce a node-mapping table** — Before writing any MAF code, emit (in your reasoning or as a comment block at the top of `workflow.py`) an explicit table that lists, for every PF node: - PF node name and `type` (+ `source.type`) - The MAF Executor it maps to (or the merged Executor name, with the matching Collapsing Pattern from [references/node-mapping.md](references/node-mapping.md)) - The incoming edges (PF `${...}` references → MAF `add_edge` / `add_fan_in_edges`) - The outgoing edges (PF downstream consumers → MAF `add_edge` / `add_fan_out_edges`) - Any `activate_config` → the MAF `condition=fn` it becomes This table is the contract used to verify graph equivalence in Phase 4. Every PF node must appear; every `${...}` reference must appear as an edge. ### Phase 2 — Generate MAF Code 7. **Create output folder** — `<original-folder>-maf/`. 8. **Copy internal packages** — see Rule 7 above. 9. **Copy all referenced resources** — see Rule 12 above. 10. **Create one Executor per node** following the mapping table from Phase 1 step 6 and [references/node-mapping.md](references/node-mapping.md). Do not invent extra Executors and do not silently merge nodes outside of the explicitly allowed Collapsing Patterns. 11. **Wire the workflow inside a `create_workflow()` factory function** using `WorkflowBuilder`. The edges you add MUST exactly match the edges listed in the Phase 1 mapping table. Executor instantiation and `WorkflowBuilder.build()` must happen inside this function — not at module level — so each call returns a fresh, independent workflow instance: - `.add_edge(source, target)` for linear connections - `.add_edge(source, target, condition=fn)` for conditionals (one per PF `activate_config`, with semantically identical predicate) - `.add_fan_out_edges(source, [targets])` for parallel branches (preserve PF parallelism — never serialize) - `.add_fan_in_edges([sources], target)` for aggregation 12. **Handle LLM nodes**: - Extract system prompt from `.jinja2` template → `Agent(instructions="...")` - Pick the right client — see [references/workflow-context.md](references/workflow-context.md) - `Agent.run()` returns an `AgentResponse` — extract text with `.text` - **Preserve LLM parameters** — pass `temperature`, `max_tokens`, etc. via `OpenAIChatOptions` (see [references/workflow-context.md](references/workflow-context.md)) 13. **Handle chat history** — format prior turns into a prompt string in an InputExecutor, not as raw message dicts. 14. **Handle Python tool nodes** — convert to plain functions and pass to `Agent(tools=[fn])`. 15. **For evaluation flows / multimodal flows / custom-tool nodes** — follow the topic file you loaded in Phase 1 step 5. ### Phase 3 — Generate Supporting Files 16. **`requirements.txt`** — include only needed `agent-framework-*` packages. Add `azure-identity>=1.15.0` if any LLM client uses the identity template. 17. **`.env.example`** — template with required environment variables (endpoint, model, key only if the connection uses key auth). 18. **`test_<name>.py`** — runnable sample script exercising single-turn and multi-turn (if applicable). 19. **`README.md`** — brief setup and run instructions. (Other documentation only if the user requests it.) ### Phase 4 — Validate 20. **Create a virtual environment** and install dependencies. 21. **Run the test sample** to verify the workflow produces output. 22. **Verify graph topology equivalence against `flow.dag.yaml`** — re-open the source `flow.dag.yaml` and the Phase 1 mapping table, then check: - [ ] Every PF node appears in the mapping table and is realized as exactly one MAF Executor (or is part of an explicitly annotated Collapsing Pattern). - [ ] No MAF Executor exists that does not correspond to a PF node or an annotated merge. - [ ] Every PF `${node.output}` reference is realized as a MAF edge between the corresponding Executors. - [ ] No MAF edges exist that are not present in PF. - [ ] PF parallel branches use `add_fan_out_edges`; PF fan-in points use `add_fan_in_edges`. No parallel branch has been serialized. - [ ] Every PF `activate_config` has a matching `add_edge(..., condition=fn)` whose predicate is semantically identical (same truth value for the same inputs). If any check fails, fix the workflow before proceeding. 23. **Fix errors** — see [references/gotchas.md](references/gotchas.md). --- ## Skill File Index ``` .github/skills/promptflow-to-maf/ ├── SKILL.md ← This file: rules + 4-phase workflow + routing ├── references/ │ ├── node-mapping.md ← Prompt Flow node → MAF mapping table + collapse patterns │ ├── workflow-context.md ← WorkflowContext types, LLM clients, ChatOptions, packages │ └── gotchas.md ← Common pitfalls, runtime errors, anti-patterns ├── topics/ │ ├── custom-tool-nodes.md ← Handling source.type: package nodes │ ├── multimodal.md ← Image/multimodal input handling │ └── evaluation-flows.md ← aggregation: true + EvalRunner pattern ├── templates/ │ └── eval_runner.py ← Reusable runner — copy verbatim into eval flow output └── examples/ ├── linear-chat.md ← Single LLM node + chat history ├── multimodal-chat.md ← Image inputs (GPT-4V style) └── evaluation.md ← Per-row workflow + aggregation function + run_eval.py ```

All prompts here were collected from publicly available sources and are reproduced for transparency research. Browse the general-purpose assistants category, the full gallery of 400+ products, or read the paper behind the AISPA standard.