name: wxo-builder description: Expert guidance for generating watsonx Orchestrate native solutions from SOPs or simple prompts. Implements agents, flows, tools, and knowledge bases based on business requirements. tags: - watsonx-orchestrate - wxo - agent-development - workflow-automation - sop-to-code
watsonx Orchestrate (wxO) Solution Generator
Table of Contents
- Overview
- Navigating the ADK
- Core Concepts
- Example Categories
- Standard Project Structure
- Implementation Patterns
- Quick Start Guide
Overview
This guide helps you generate watsonx Orchestrate native solutions from Standard Operating Procedures (SOPs) or simple prompts. It uses the IBM watsonx Orchestrate Agent Development Kit (ADK) as the foundation for implementing agents, flows, tools, and knowledge bases.
Purpose
Generate complete watsonx Orchestrate implementations from:
- SOPs: Standard Operating Procedures (recommended - use
sop-builderskill to generate SOPs from BPMN diagrams, n8n JSON, Langflow JSON, or other workflow models first) - Simple Prompts: Direct descriptions of business requirements or workflows
Workflow
Start with Business Requirements:
- If you have BPMN diagrams, n8n JSON, Langflow JSON, or other workflow models → Use
sop-builderskill to generate an SOP first - If you have a simple business requirement → Provide it directly as a prompt
- If you have BPMN diagrams, n8n JSON, Langflow JSON, or other workflow models → Use
Generate wxO Solution: This skill (
wxo-builder) transforms the SOP or prompt into a complete watsonx Orchestrate implementation with:- Agent configurations (YAML)
- Python tools and flows
- Knowledge base integrations
- Import scripts and documentation
ADK Repository: https://github.com/IBM/ibm-watsonx-orchestrate-adk
The ADK provides:
- Python SDK for programmatic agent development
- CLI tool (
orchestratecommand) for managing agents, tools, and environments - Developer Edition - a local, self-contained instance of watsonx Orchestrate
- Production Integration - ability to deploy to production watsonx Orchestrate instances
Navigating the ADK
GitHub Repository
All examples and source code are available in the official GitHub repository:
Repository: https://github.com/IBM/watsonx-orchestrate-adk
Key Directories in Repository
watsonx-orchestrate-adk/
├── examples/ # Example implementations (START HERE)
│ ├── agent_builder/ # Agent examples
│ ├── flow_builder/ # Flow examples
│ ├── channel-integrations/ # Channel integration examples
│ └── plugins/ # Plugin examples
├── src/ibm_watsonx_orchestrate/ # SDK source (for reference)
│ ├── agent_builder/ # Agent creation APIs
│ ├── flow_builder/ # Flow/workflow APIs
│ └── cli/ # CLI commands
└── packages/ # Additional packages
How to Use This Guide
- Browse GitHub Examples - Visit the examples directory to find examples similar to your use case
- Study Standard Structure - Understand the consistent project layout
- Follow Implementation Patterns - Use proven patterns for common scenarios
- Use Quick Start Guide - Create new projects based on examples
Core Concepts
1. Agents
AI assistants that can use tools and interact with users. Defined using YAML configuration:
spec_version: v1
kind: native
name: my_agent
description: Agent description
instructions: Detailed instructions for the agent
llm: groq/openai/gpt-oss-120b
style: default
tools:
- tool_name_1
- tool_name_2
2. Tools
Functions that agents can invoke. Three main types:
- Python Tools: Python functions decorated with
@tool - Flow Tools: Workflows built with the flow builder
- OpenAPI Tools: REST APIs defined by OpenAPI specs
3. Flows
Workflows that orchestrate multiple steps, tools, and logic:
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
@flow(
name="my_flow",
display_name="My Flow",
description="Flow description",
input_schema=MyInputSchema
)
def build_my_flow(aflow: Flow) -> Flow:
# Define flow nodes and sequence
node1 = aflow.tool(my_tool_function)
node2 = aflow.prompt(
name="process_data",
system_prompt=["Process the data"],
user_prompt=["Process this: {input}"],
llm="meta-llama/llama-3-3-70b-instruct",
input_schema=MyInputSchema,
output_schema=MyOutputSchema
)
aflow.sequence(START, node1, node2, END)
return aflow
CRITICAL - Flow Function Signature:
- Flow functions MUST follow this exact signature:
def build_<flow_name>(aflow: Flow) -> Flow: - The parameter MUST be named
aflowwith typeFlow - The function MUST return
Flow - The function name MUST start with
build_ - Do NOT invent alternative signatures or parameter names
4. Connections
Authenticated connections to external services (ServiceNow, Salesforce, etc.)
5. Knowledge Bases
Document repositories that agents can search through for information
LLM Usage Guidelines
When to Use Built-in Prompt Nodes vs. Agents
For Direct LLM Generation Tasks: When your specification calls for using an LLM directly to generate content, analyze text, or perform transformations, use the built-in Prompt node in Flow rather than creating custom tools or external LLM calls.
Default LLM Model: groq/openai/gpt-oss-120b
Example - Using Prompt Node in Flow:
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
@flow(
name="content_generator",
display_name="Content Generator",
description="Generate content using LLM"
)
def build_content_generator(aflow: Flow) -> Flow:
# Use built-in Prompt node for LLM generation
generate_node = aflow.prompt(
name="generate_content",
system_prompt=["You are a helpful content generator."],
user_prompt=["Generate content based on: {input}"],
llm="groq/openai/gpt-oss-120b", # Default LLM model
input_schema=InputSchema,
output_schema=OutputSchema
)
aflow.sequence(START, generate_node, END)
return aflow
For Knowledge-Based Tasks: When your specification requires accessing knowledge bases, retrieving information from documents, or performing RAG (Retrieval-Augmented Generation), rely on the agent's built-in knowledge base capabilities rather than implementing custom retrieval logic.
Example - Agent with Knowledge Base:
spec_version: v1
kind: native
name: knowledge_assistant
description: Assistant with access to knowledge base
instructions: |
You are a helpful assistant with access to a knowledge base.
Use the knowledge base to answer questions accurately.
llm: groq/openai/gpt-oss-120b
knowledge_bases:
- my_knowledge_base
tools:
- my_flow_tool
Key Principles:
- LLM Generation → Use
aflow.prompt()node in flows withgroq/openai/gpt-oss-120b - Knowledge Retrieval → Use agent's
knowledge_basesconfiguration - Custom Logic → Use Python tools only for business logic, API calls, or data transformations
- Don't Reinvent → Leverage built-in capabilities instead of custom implementations
Knowledge Base Providers
watsonx Orchestrate supports multiple knowledge base providers for RAG (Retrieval Augmented Generation) implementations. Choose the appropriate provider based on your existing infrastructure or requirements.
Supported Knowledge Base Providers
1. Built-in Milvus (Managed)
Use When: You don't have an existing vector database and want a fully managed solution.
Configuration:
spec_version: v1
kind: knowledge_base
name: my_knowledge_base
description: Knowledge base with uploaded documents
documents:
- path: document1.pdf
- path: document2.pdf
vector_index:
embeddings_model_name: ibm/slate-125m-english-rtrvr-v2
Features:
- Automatic document ingestion and indexing
- No external infrastructure required
- Supports PDF, DOCX, PPTX, XLSX, CSV, HTML, TXT
- Built-in embedding generation
Authentication: None required (managed service)
2. AstraDB (DataStax)
Use When: You have an existing AstraDB instance or need Cassandra-based vector storage.
Configuration:
spec_version: v1
kind: knowledge_base
name: my_astradb_kb
description: Knowledge base connected to AstraDB
app_id: my_astradb_connection
prioritize_built_in_index: false
conversational_search_tool:
index_config:
- astradb:
api_endpoint: 'https://xxx-us-east-2.apps.astra.datastax.com'
data_type: collection # or 'table'
collection: my_collection
embedding_model_id: nvidia/nv-embedqa-e5-v5
embedding_mode: server # or 'client'
port: '443'
search_mode: vector # 'vector', 'lexical', or 'hybrid'
limit: 5
field_mapping:
title: title_field
body: content_field
url: url_field
Features:
- Server-side or client-side embeddings
- Multiple search modes (vector, lexical, hybrid)
- Collection or table-based storage
- Scalable cloud-native solution
Authentication: API Key (Application Token)
orchestrate connections configure -a my_astradb_connection --kind api_key
orchestrate connections set-credentials -a my_astradb_connection --api-key <TOKEN>
3. Milvus (External)
Use When: You have an existing Milvus instance or need self-hosted vector storage.
Configuration:
spec_version: v1
kind: knowledge_base
name: my_milvus_kb
description: Knowledge base connected to external Milvus
app_id: my_milvus_connection
prioritize_built_in_index: false
conversational_search_tool:
index_config:
- milvus:
endpoint: 'https://my-milvus-instance.com'
collection_name: my_collection
embedding_provider: nvidia
embedding_model: nvidia/nv-embedqa-e5-v5
embedding_dimension: 1024
field_mapping:
title: title
body: content
url: source_url
Features:
- Self-hosted or cloud-hosted options
- High-performance vector search
- Flexible schema design
- Open-source foundation
Authentication: Basic Auth
orchestrate connections configure -a my_milvus_connection --kind basic
orchestrate connections set-credentials -a my_milvus_connection -u <USERNAME> -p <PASSWORD>
4. Elasticsearch
Use When: You have an existing Elasticsearch cluster or need full-text + vector search.
Configuration:
spec_version: v1
kind: knowledge_base
name: my_elasticsearch_kb
description: Knowledge base connected to Elasticsearch
app_id: my_elasticsearch_connection
prioritize_built_in_index: false
conversational_search_tool:
index_config:
- elasticsearch:
endpoint: 'https://my-elasticsearch-cluster.com'
index_name: my_index
embedding_field: vector_embedding
field_mapping:
title: title
body: content
url: url
Features:
- Combined full-text and vector search
- Mature ecosystem and tooling
- Advanced query capabilities
- Hybrid search support
Authentication: API Key or Basic Auth
# API Key
orchestrate connections configure -a my_elasticsearch_connection --kind api_key
orchestrate connections set-credentials -a my_elasticsearch_connection --api-key <KEY>
# Basic Auth
orchestrate connections configure -a my_elasticsearch_connection --kind basic
orchestrate connections set-credentials -a my_elasticsearch_connection -u <USER> -p <PASS>
When to Use Custom Python Tools Instead
If your vector database or search system is NOT one of the supported providers above, create a custom Python tool instead of using a knowledge base.
Unsupported Systems Requiring Custom Tools:
- Pinecone
- Weaviate
- Qdrant
- Chroma
- Custom REST APIs
- Legacy search systems
- Proprietary databases
Custom Tool Pattern for RAG:
from ibm_watsonx_orchestrate.agent_builder.tools import tool
from pydantic import BaseModel, Field
from typing import List, Dict, Any
import requests
class SearchQuery(BaseModel):
"""Input for searching the knowledge base."""
query: str = Field(..., description="The search query")
top_k: int = Field(default=5, description="Number of results to return")
class SearchResult(BaseModel):
"""Search result from the knowledge base."""
results: List[Dict[str, Any]] = Field(..., description="List of search results")
@tool(
name="search_custom_vector_db",
description="Search a custom vector database for relevant information"
)
def search_custom_vector_db(query: SearchQuery) -> SearchResult:
"""
Search a custom vector database and return relevant results.
Args:
query: SearchQuery containing the search query and parameters
Returns:
SearchResult containing the list of relevant documents
"""
# Example: Call your custom vector database API
response = requests.post(
"https://my-custom-db.com/search",
json={
"query": query.query,
"limit": query.top_k
},
headers={"Authorization": f"Bearer {get_api_key()}"}
)
results = response.json()
# Format results for the agent
formatted_results = []
for result in results.get("matches", []):
formatted_results.append({
"title": result.get("metadata", {}).get("title", ""),
"content": result.get("text", ""),
"score": result.get("score", 0.0),
"source": result.get("metadata", {}).get("source", "")
})
return SearchResult(results=formatted_results)
Agent Configuration with Custom Tool:
spec_version: v1
kind: native
name: my_agent_with_custom_search
description: Agent using custom search tool
instructions: |
You are a helpful assistant. When users ask questions, use the
search_custom_vector_db tool to find relevant information, then
provide a clear answer with citations.
llm: groq/openai/gpt-oss-120b
style: react
tools:
- search_custom_vector_db
Provider Selection Decision Tree
Do you have an existing vector database?
├─ No → Use Built-in Milvus (managed)
└─ Yes → What type?
├─ AstraDB → Use AstraDB provider
├─ Milvus → Use Milvus provider
├─ Elasticsearch → Use Elasticsearch provider
└─ Other (Pinecone, Weaviate, etc.) → Create Custom Python Tool
Authentication Support Matrix
| Provider | Basic Auth | API Key | Bearer Token | OAuth |
|---|---|---|---|---|
| Built-in Milvus | N/A | N/A | N/A | N/A |
| AstraDB | ❌ | ✅ | ❌ | ❌ |
| Milvus (External) | ✅ | ❌ | ❌ | ❌ |
| Elasticsearch | ✅ | ✅ | ❌ | ❌ |
| Custom Tool | Depends on implementation |
Best Practices
- Use Built-in Providers When Possible: They offer better integration and automatic RAG orchestration
- Field Mapping: Always configure field_mapping to match your data schema
- Embedding Models: Choose embedding models compatible with your provider
- Connection Management: Use connections for secure credential storage
- Testing: Test knowledge base connectivity before deploying agents
- Custom Tools: Only create custom tools when no built-in provider exists
Example Categories
1. Agent Builder Examples
Browse: examples/agent_builder/
Customer Care
- Location: customer_care/
- Purpose: Healthcare customer service agent
- Features: ServiceNow integration, benefits queries, doctor search
- Key Components:
- Agent YAML configuration
- Python tools for API integration
- Connection setup for ServiceNow
Voice-Enabled Agents
- voice_enabled_deepgram/ - Deepgram voice integration
- voice_enabled_elevenlabs/ - ElevenLabs voice integration
- voice_enabled_watson/ - Watson voice integration
2. Flow Builder Examples
Browse: examples/flow_builder/
Simple Flows
- Purpose: Basic flow demonstrating message generation
- Pattern: Simple tool invocation
- Use Case: Learning flow basics
- Purpose: Fetch and display pet facts
- Pattern: External API integration
- Use Case: Simple data retrieval
Document Processing Flows
- Purpose: Extract structured data from documents
- Pattern: Watson Document Understanding integration
- Key Features:
- KVP (Key-Value Pair) schema definition
- Document processing node (
docproc) - Support for PDFs and images
- Purpose: Classify documents by type
- Pattern: Document analysis and categorization
- Purpose: General document data extraction
- Pattern: Flexible extraction framework
Workflow Patterns
- Purpose: Interactive user input collection
- Pattern: User activity nodes
- Use Case: Gathering structured user input
- Purpose: Process multiple emails
- Pattern: Loop/iteration over collections
- Use Case: Batch processing
- Purpose: Tuition reimbursement workflow
- Pattern: Multi-step approval process
- Use Case: Business process automation
Conditional Logic
- Purpose: Conditional flow execution
- Pattern: If-else branching
- Use Case: Decision-based workflows
Advanced Patterns
- Purpose: Multiple agents working together
- Pattern: Agent collaboration
- Use Case: Complex multi-agent scenarios
- Purpose: Agent swarm for task distribution
- Pattern: Dynamic agent selection
- Use Case: Intelligent task routing
- Purpose: Scheduled agent execution
- Pattern: Time-based triggers
- Use Case: Automated periodic tasks
Standard Project Structure
Every example follows a consistent structure:
example_name/
├── __init__.py # Python package initialization
├── README.md # Documentation and usage instructions
├── main_flow.py # Programmatic testing script to test Flow. Not needed if no flow is created.
├── import-all.sh # Import script for CLI
├── .env (optional) # Environment variables
├── tools/ # Tool implementations
│ ├── __init__.py
│ ├── tool_name.py # Python tool definitions
│ └── flow_name.py # Flow definitions
├── agents/ # Agent configurations
│ └── agent_name.yaml # Agent YAML files
└── generated/ # Generated artifacts
└── flow_spec.json # Compiled flow specifications
Key Files Explained
1. tools/[tool_name].py
Python tools decorated with @tool:
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
@tool(permission=ToolPermission.READ_ONLY)
def my_tool(param: str) -> dict:
"""Tool description"""
# Implementation
return {"result": "value"}
2. tools/[flow_name].py
Flow definitions using @flow decorator:
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
@flow(
name="my_flow",
display_name="My Flow",
description="Flow description",
input_schema=InputSchema
)
def build_my_flow(aflow: Flow) -> Flow:
# Build flow
return aflow
IMPORTANT - Flow Function Signature:
- ALWAYS use:
def build_<flow_name>(aflow: Flow) -> Flow: - Parameter MUST be
aflow: Flow - Return type MUST be
Flow - Function name MUST start with
build_
CRITICAL CONSTRAINTS - MUST FOLLOW
⚠️ Decorator Requirements
ALL functions MUST use decorators:
# Tools - ALWAYS @tool
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
@tool(permission=ToolPermission.READ_ONLY) # or READ_WRITE
def my_tool(param: str) -> dict:
"""Tool description"""
return {"result": "value"}
# Flows - ALWAYS @flow with signature: def build_<name>(aflow: Flow) -> Flow
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
@flow(name="my_flow", display_name="My Flow", input_schema=Schema)
def build_my_flow(aflow: Flow) -> Flow:
node = aflow.tool(my_tool)
aflow.edge(START, node)
aflow.edge(node, END)
return aflow
Rules:
- ❌ NEVER regular functions without decorators
- ✅ Flow signature:
def build_<name>(aflow: Flow) -> Flow: - ✅ One flow per file:
tools/[flow_name]_flow.py - ✅ Tools can be grouped:
tools/[category]_tools.py - ✅ Credentials as regular parameters (no ExpectCredentials)
Validation Checklist:
- All functions have
@toolor@flow - Flows:
def build_<name>(aflow: Flow) -> Flow: - One flow per file
- Proper ToolPermission values
⚠️ Python Tool Self-Containment Requirement
CRITICAL: Each Python tool implementation file MUST be self-contained.
All function definitions, type definitions, and class definitions used within a Python tool file must be defined within that same file. Cross-file references between local Python files are NOT allowed.
Allowed References:
- ✅ Standard Python library imports (e.g.,
import json,from typing import Optional) - ✅ Common third-party packages (e.g.,
import requests,from pydantic import BaseModel) - ✅
ibm_watsonx_orchestratepackage imports (e.g.,from ibm_watsonx_orchestrate.agent_builder.tools import tool)
NOT Allowed:
- ❌ Importing functions/types from other local Python files in the same project
- ❌ Relative imports from sibling modules (e.g.,
from .utils import helper_function) - ❌ Importing custom types from other local files (e.g.,
from tools.shared_types import MyModel)
Example - ❌ INCORRECT:
# tools/my_tool.py
from tools.shared_utils import format_response # ❌ NOT ALLOWED
from .types import CustomModel # ❌ NOT ALLOWED
@tool(permission=ToolPermission.READ_ONLY)
def my_tool(input: str) -> CustomModel:
return format_response(input)
Example - ✅ CORRECT:
# tools/my_tool.py
from pydantic import BaseModel, Field
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
class CustomModel(BaseModel): # ✅ Defined in same file
result: str = Field(description="Result")
def format_response(input: str) -> str: # ✅ Helper defined in same file
return f"Formatted: {input}"
@tool(permission=ToolPermission.READ_ONLY)
def my_tool(input: str) -> CustomModel:
"""Process input and return formatted result."""
formatted = format_response(input)
return CustomModel(result=formatted)
Rationale:
- Ensures tools are portable and can be deployed independently
- Prevents dependency issues during tool import and execution
- Simplifies tool maintenance and debugging
- Aligns with watsonx Orchestrate's tool isolation model
3. agents/[agent_name].yaml
Agent configuration:
CRITICAL - Required Agent YAML Fields: All agent YAML files MUST include these required fields:
spec_version: v1 # REQUIRED - Always use v1
kind: native # REQUIRED - Use 'native' for standard agents
name: agent_name # REQUIRED - Unique agent identifier
description: Agent description # REQUIRED - Clear description of agent purpose
instructions: Detailed instructions # REQUIRED - Instructions for the LLM
llm: groq/openai/gpt-oss-120b # REQUIRED - LLM model to use
style: default # REQUIRED - Agent style (default, react, etc.)
collaborators: [] # OPTIONAL - List of collaborator agents
tools: # REQUIRED - List of tools/flows
- tool_or_flow_name
knowledge_base: [] # OPTIONAL - List of knowledge bases
starter_prompts: # RECOMMENDED - Suggested prompts for users
is_default_prompts: false
prompts:
- id: default0
title: Short action title
prompt: Example prompt text that users can click
state: active
- id: default1
title: Another action
prompt: Another example prompt
state: active
welcome_content: # RECOMMENDED - Welcome message for users
welcome_message: Welcome to [Agent Name]
description: Brief description of what the agent can help with
is_default_message: false
RECOMMENDED - Starter Prompts and Welcome Content:
Always include starter_prompts and welcome_content to improve user experience:
starter_prompts: Provide 2-4 suggested prompts that guide users on what the agent can do
- Use clear, action-oriented titles (e.g., "Report a concern", "Create support ticket")
- Write prompts that demonstrate the agent's capabilities
- Set
is_default_prompts: falseto use custom prompts - Each prompt needs a unique
id(e.g., default0, default1, etc.) - Set
state: activefor all prompts
welcome_content: Create a welcoming first impression
welcome_message: A friendly greeting that includes the agent's name/purposedescription: A brief explanation of what the agent can help with- Set
is_default_message: falseto use custom content
Example from St. Mary's Hospital Agent:
starter_prompts:
is_default_prompts: false
prompts:
- id: default0
title: Report a concern
prompt: I need to report a concern about my care
state: active
- id: default1
title: Create support ticket
prompt: I want to create a support ticket
state: active
- id: default2
title: Follow up on issue
prompt: I need to follow up on a previous issue
state: active
welcome_content:
welcome_message: Welcome to St. Mary's Group of Hospitals Support
description: I'm here to help you report concerns and create support tickets. How can I assist you today?
is_default_message: false
DO NOT:
- ❌ Omit
spec_version: v1(will cause import errors) - ❌ Omit
kind: native - ❌ Omit required fields like
llm,style, ortools - ❌ Skip
starter_promptsandwelcome_content(reduces user experience quality)
4. main_flow.py
Programmatic testing:
import asyncio
from pathlib import Path
from examples.example_name.tools.flow_name import build_flow
async def main():
flow_def = await build_flow().compile_deploy()
generated_folder = f"{Path(__file__).resolve().parent}/generated"
flow_def.dump_spec(f"{generated_folder}/flow.json")
await flow_def.invoke({"input": "value"}, debug=True)
if __name__ == "__main__":
asyncio.run(main())
5. import-all.sh
CLI import script:
CRITICAL - Import CLI Syntax:
You MUST use the orchestrate CLI commands to import flows and agents. Do NOT use Python scripts or custom import methods.
#!/usr/bin/env bash
# orchestrate env activate local # only used if user asked to activate local env
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Import Python tools
for tool in tool1.py tool2.py; do
orchestrate tools import -k python -f ${SCRIPT_DIR}/tools/${tool}
done
# Import Flow tools
for flow in flow1.py; do
orchestrate tools import -k flow -f ${SCRIPT_DIR}/tools/${flow}
done
# Import agents
for agent in agent1.yaml; do
orchestrate agents import -f ${SCRIPT_DIR}/agents/${agent}
done
IMPORTANT - CLI Command Reference:
- Import Python Tools:
orchestrate tools import -k python -f <path_to_tool.py> - Import Flow Tools:
orchestrate tools import -k flow -f <path_to_flow.py> - Import Agents:
orchestrate agents import -f <path_to_agent.yaml>
DO NOT:
- ❌ Use custom Python import scripts (e.g.,
python3 main_flow.py) - ❌ Use API client methods directly in import scripts
- ❌ Invent alternative import methods
ALWAYS:
- ✅ Use the
orchestrateCLI commands shown above - ✅ Use the
-kflag to specify tool kind (python or flow) - ✅ Use the
-fflag to specify the file path - ✅ Use
${SCRIPT_DIR}for relative paths in the script
Implementation Patterns
Pattern 1: Simple Tool Flow
Use Case: Basic data retrieval or processing
Structure:
example/
├── tools/
│ ├── my_tool.py # Python tool
│ └── my_flow.py # Flow that uses the tool
├── agents/
│ └── my_agent.yaml # Agent configuration
└── main.py # Testing script
Example: get_pet_facts_if_else/
Pattern 2: Document Processing Flow
Use Case: Extract structured data from documents
Structure:
example/
├── tools/
│ ├── get_kvp_schemas.py # Define extraction schema
│ └── processing_flow.py # Document processing flow
├── agents/
│ └── doc_agent.yaml # Agent configuration
└── main.py # Testing script
Key Components:
- KVP Schema Tool: Defines what fields to extract
- Document Processing Node: Uses Watson Document Understanding
- Flow: Orchestrates schema retrieval and document processing
IMPORTANT - Document Upload Handling:
When a flow expects a document as input (e.g., DocProcInput), the agent should invoke the flow tool directly without asking the user to upload the document first. The flow itself will handle the document upload prompt.
✅ Correct Agent Instructions:
instructions: | When the user wants to process a document, immediately invoke the document_processing_flow tool. The flow will prompt the user to upload the document.❌ Incorrect Agent Instructions:
instructions: | Ask the user to upload a document first, then pass it to the document_processing_flow tool. # This will NOT work - the agent cannot pass uploaded documents to flows
Why: Agents cannot directly pass user-uploaded documents to flow tools. The flow's document input nodes (like docproc) handle the upload interaction directly with the user. The agent should simply invoke the flow tool and let the flow manage the document upload process.
Example: extract_airline_invoice/, document_processing/, expense_report_agent/, invoice_agent_6/
Pattern 3: User Activity Flow
Use Case: Interactive multi-step workflows
Structure:
example/
├── tools/
│ └── activity_flow.py # Flow with user activity nodes
├── agents/
│ └── activity_agent.yaml # Agent configuration
└── main.py # Testing script
Key Features:
- User activity nodes for input collection
- Form handling
- Multi-turn conversations
Example: user_activity/, book_a_flight/
Pattern 4: Multi-Agent Collaboration
Use Case: Complex tasks requiring multiple specialized agents
Structure:
example/
├── tools/
│ ├── agent1_tools.py # Tools for agent 1
│ ├── agent2_tools.py # Tools for agent 2
│ └── orchestration_flow.py # Coordination flow
├── agents/
│ ├── agent1.yaml # Specialized agent 1
│ ├── agent2.yaml # Specialized agent 2
│ └── coordinator.yaml # Coordinator agent
└── main.py # Testing script
Example: collaborator_agents/, triage_workflow_agent_swarm/
Quick Start Guide
Creating a New Example
Step 1: Create Directory Structure
Note: You can reference existing examples from the GitHub repository for structure and patterns.
mkdir -p my_example/{tools,agents,generated}
touch my_example/{__init__.py,main_flow.py,README.md,import-all.sh}
touch my_example/tools/__init__.py
Step 2: Create Python Tool (if needed)
# tools/my_tool.py
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
@tool(permission=ToolPermission.READ_ONLY)
def my_tool(input_param: str) -> dict:
"""Tool description"""
return {"result": f"Processed: {input_param}"}
Step 3: Create Flow
# tools/my_flow.py
from pydantic import BaseModel
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
from .my_tool import my_tool
class MyFlowInput(BaseModel):
input_param: str
@flow(
name="my_flow",
display_name="My Flow",
description="Flow description",
input_schema=MyFlowInput
)
def build_my_flow(aflow: Flow) -> Flow:
"""
CRITICAL: Flow function signature MUST be:
def build_<flow_name>(aflow: Flow) -> Flow:
"""
tool_node = aflow.tool(my_tool)
aflow.sequence(START, tool_node, END)
return aflow
Step 4: Create Agent Configuration
# agents/my_agent.yaml
spec_version: v1
kind: native
name: my_agent
description: My agent description
instructions: Invoke my_flow tool and output the result
llm: groq/openai/gpt-oss-120b
style: default
tools:
- my_flow
Step 5: Create Main Script (only needed if there are flows in the projects)
Tip: See flow examples for complete working implementations.
# main_flow.py
import asyncio
from pathlib import Path
from my_example.tools.my_flow import build_my_flow
async def main():
flow_def = await build_my_flow().compile_deploy()
generated_folder = f"{Path(__file__).resolve().parent}/generated"
flow_def.dump_spec(f"{generated_folder}/my_flow.json")
await flow_def.invoke({"input_param": "test"}, debug=True)
if __name__ == "__main__":
asyncio.run(main())
Step 6: Create Import Script
CRITICAL: Always use the orchestrate CLI commands to import flows and agents.
# import-all.sh
#!/usr/bin/env bash
# orchestrate env activate local
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Import Python tools (if any)
for tool in my_tool.py; do
orchestrate tools import -k python -f ${SCRIPT_DIR}/tools/${tool}
done
# Import Flow tools - MUST use: orchestrate tools import -k flow
for flow in my_flow.py; do
orchestrate tools import -k flow -f ${SCRIPT_DIR}/tools/${flow}
done
# Import agents - MUST use: orchestrate agents import
for agent in my_agent.yaml; do
orchestrate agents import -f ${SCRIPT_DIR}/agents/${agent}
done
Required CLI Commands:
- Python tools:
orchestrate tools import -k python -f <file> - Flow tools:
orchestrate tools import -k flow -f <file> - Agents:
orchestrate agents import -f <file>
Step 7: Make Import Script Executable
chmod +x my_example/import-all.sh
Step 8: Create README with Diagrams
Examples: Browse GitHub examples to see complete README files with diagrams.
# My Example
## Overview
Brief description of what this example demonstrates.
## Architecture Diagram
```mermaid
graph TB
User[User] -->|Interacts| Agent[My Agent]
Agent -->|Invokes| Flow[My Flow Tool]
Flow -->|Executes| Tool[My Tool]
Tool -->|Returns| Result[Result]
Result -->|Formatted by| Agent
Agent -->|Presents| User
style Agent fill:#4A90E2,stroke:#2E5C8A,color:#fff
style Flow fill:#50C878,stroke:#2E7D4E,color:#fff
style Tool fill:#F39C12,stroke:#C87F0A,color:#fff
Workflow Diagram
flowchart TD
Start([START]) --> Input[Input Data]
Input --> Process[Processing Node]
Process --> Output[Output Result]
Output --> End([END])
style Start fill:#2ECC71,stroke:#27AE60,color:#fff
style End fill:#E74C3C,stroke:#C0392B,color:#fff
style Process fill:#F39C12,stroke:#D68910,color:#fff
Usage
Via Chat UI
- Run
./import-all.sh - Launch chat:
orchestrate chat start - Select
my_agent - Interact with the agent
Programmatically
- Set PYTHONPATH:
export PYTHONPATH=<ADK>/src:<ADK> - Run:
python3 main.py
Features
- Feature 1
- Feature 2
Output
Description of expected output
### Testing Your Example
#### Option 1: Via Chat UI
```bash
cd examples/category/my_example
./import-all.sh
orchestrate chat start
# Select your agent and interact
Option 2: Programmatically
export PYTHONPATH=/path/to/adk/src:/path/to/adk
cd examples/category/my_example
python3 main.py
Best Practices
1. Naming Conventions
- Use snake_case for Python files and functions
- Use descriptive names that indicate purpose
- Agent names should match their YAML file names
2. Documentation
- Always include a README.md with:
- Purpose and overview
- Architecture Diagram: Mermaid diagram showing agent, flow, and tool relationships
- Workflow Diagram(s): One Mermaid diagram per agentic workflow showing the flow execution path
- Usage instructions (both CLI and programmatic)
- Expected inputs/outputs
- Prerequisites or dependencies
Creating Effective Diagrams
Architecture Diagram Guidelines:
- Show the high-level system components (User → Agent → Flow → Tools/Services)
- Include external services or APIs being used
- Use consistent color coding (e.g., agents in blue, flows in green, tools in orange)
- Keep it simple and focused on the main interaction flow
Workflow Diagram Guidelines:
- Create one diagram per agentic workflow (flow tool)
- Show the complete flow from START to END
- Include all nodes: tool nodes, LLM nodes, decision points, user activity nodes
- Label branches clearly for conditional logic
- Use different colors for different node types
- Include key data transformations or processing steps
Example Mermaid Syntax:
graph TB
User[User] -->|Action| Component[Component Name]
style Component fill:#4A90E2,stroke:#2E5C8A,color:#fff
flowchart TD
Start([START]) --> Node1[Processing Step]
Node1 --> Decision{Condition?}
Decision -->|Yes| Node2[Path A]
Decision -->|No| Node3[Path B]
Node2 --> End([END])
Node3 --> End
4. Error Handling
- Include proper error handling in tools
- Provide meaningful error messages
- Use try-except blocks for external API calls
5. Type Hints and Pydantic Models
- Use Pydantic models for input/output schemas
- Include type hints in function signatures
- Document expected types in docstrings
- IMPORTANT: Always define Pydantic models explicitly as classes, never use dynamic type creation
- ✅ Correct: Define models as proper classes
class MyOutputSchema(BaseModel): field_name: str = Field(description="Field description") - ❌ Incorrect: Do not use dynamic type creation
# This will cause Pydantic validation errors output_schema=type('MySchema', (BaseModel,), { 'field_name': (str, Field(description="Field description")) }) - All model fields must have proper type annotations
- Dynamic type creation causes "non-annotated attribute" errors during model loading
- ✅ Correct: Define models as proper classes
6. Python Docstring Format (CRITICAL)
MUST use Google-style docstrings for all Python tools. The watsonx Orchestrate tool parser requires strict adherence to this format.
✅ Correct Format
@tool(permission=ToolPermission.READ_WRITE)
def my_tool(
param1: str,
param2: int,
optional_param: Optional[str] = None
) -> MyOutputModel:
"""
Brief description of what the tool does.
Longer description providing more context about the tool's
purpose and behavior (optional).
Args:
param1 (str): Description of param1
param2 (int): Description of param2
optional_param (Optional[str]): Description of optional parameter
Returns:
MyOutputModel: Description of what is returned
"""
# Implementation
pass
Key Rules
- NO blank line between Args and Returns sections - Extra blank lines cause validation errors
- Always include type annotations in Args - Format:
param_name (Type): Description - Returns section must include both type and description - Format:
TypeName: Description - Type hints in function signature are REQUIRED - All parameters and return type must have type hints
- Types must match - Docstring types must match function signature type hints
- Use proper indentation - Args/Returns sections have no indentation, descriptions use 4 spaces
Complete Working Example
from pydantic import BaseModel, Field
from typing import Optional
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
class RequestResult(BaseModel):
"""Result of request processing"""
request_id: str
status: str
message: str
@tool(permission=ToolPermission.READ_WRITE)
def process_request(
request_id: str,
user_email: str,
description: str,
priority: Optional[str] = "normal"
) -> RequestResult:
"""
Process a service request and create a ticket.
This tool receives a service request from a user and creates
an official ticket in the system for tracking.
Args:
request_id (str): Unique identifier for the request
user_email (str): Email address of the requesting user
description (str): Detailed description of the issue
priority (Optional[str]): Priority level (default: normal)
Returns:
RequestResult: Processing result with status and message
"""
return RequestResult(
request_id=request_id,
status="created",
message=f"Request {request_id} created for {user_email}"
)
7. Credential Configuration
Connection YAML Files
Connection YAML files define how tools authenticate to external services. They must follow this exact format:
Required Fields for ALL Connections:
spec_version: v1 # REQUIRED: Must be 'v1'
kind: connection # REQUIRED: Must be 'connection' (singular, not 'connections')
app_id: my_connection_name # REQUIRED: Unique identifier for this connection
environments: # REQUIRED: At least 'draft' environment must be defined
draft: # REQUIRED: Draft environment configuration
security_scheme: <type> # REQUIRED: One of the valid security schemes (see below)
type: team # REQUIRED: 'team' or 'member' (team = shared credentials)
# Additional fields depend on security_scheme
Valid Security Schemes:
api_key_auth- For API key authenticationbearer_token- For bearer token authenticationbasic_auth- For basic username/password authenticationoauth2- For OAuth2 flows (requires additionalauth_typefield)key_value_creds- For key-value credential pairs
API Key Authentication Example:
spec_version: v1
kind: connection
app_id: apify
environments:
draft:
security_scheme: api_key_auth
type: team
server_url: https://api.apify.com
OAuth2 Authentication Example:
spec_version: v1
kind: connection
app_id: google_sheets
environments:
draft:
security_scheme: oauth2
auth_type: oauth2_auth_code # REQUIRED for OAuth2: Must be 'oauth2_auth_code'
type: team
server_url: https://sheets.googleapis.com
auth_url: https://accounts.google.com/o/oauth2/v2/auth
token_url: https://oauth2.googleapis.com/token
scope:
- https://www.googleapis.com/auth/spreadsheets.readonly
- https://www.googleapis.com/auth/drive.readonly
Common Mistakes in Connection YAMLs:
- ❌ Using
kind: connections(plural) - Must bekind: connection(singular) - ❌ Using
kind:inside environments - Must besecurity_scheme: - ❌ Using
auth_type: authorization_codefor OAuth2 - Must beauth_type: oauth2_auth_code - ❌ Missing
auth_typefield for OAuth2 connections - ❌ Missing
spec_versionfield
Python Tool Credential Declaration
CRITICAL: Tools requiring credentials must NOT include credential parameters in their function signature.
✅ Correct Tool Signature (credentials fetched at runtime):
from ibm_watsonx_orchestrate.agent_builder.tools import tool, ToolPermission
from ibm_watsonx_orchestrate.agent_builder.connections import ConnectionType, ExpectedCredentials
from ibm_watsonx_orchestrate.run import connections
APIFY_APP_ID = 'apify'
@tool(
permission=ToolPermission.READ_ONLY,
expected_credentials=[
ExpectedCredentials(app_id=APIFY_APP_ID, type=ConnectionType.API_KEY_AUTH)
]
)
def search_linkedin_person(
first_name: str,
last_name: str,
company: str,
max_results: int = 10
) -> List[Dict[str, Any]]:
"""
Search Google for a person's LinkedIn profile using Apify.
Args:
first_name (str): Person's first name
last_name (str): Person's last name
company (str): Company name
max_results (int): Maximum number of search results to return
"""
# Fetch credentials at runtime from connection
conn = connections.api_key_auth(APIFY_APP_ID)
apify_api_key = conn.api_key
# Use the API key in your requests
headers = {"Authorization": f"Bearer {apify_api_key}"}
# ... rest of implementation
❌ Incorrect Tool Signature (credential as parameter):
# WRONG: Do not include credential parameters in function signature
@tool(
permission=ToolPermission.READ_ONLY,
expected_credentials=[
ExpectedCredentials(app_id=APIFY_APP_ID, type=ConnectionType.API_KEY_AUTH)
]
)
def search_linkedin_person(
first_name: str,
last_name: str,
company: str,
apify_api_key: str, # ❌ WRONG: Remove this parameter
max_results: int = 10
) -> List[Dict[str, Any]]:
# This will cause type hint parsing warnings
Fetching Credentials at Runtime:
from ibm_watsonx_orchestrate.run import connections
# For API Key Auth
conn = connections.api_key_auth('my_app_id')
api_key = conn.api_key
# For OAuth2
conn = connections.oauth2_auth_code('my_app_id')
access_token = conn.access_token
# For Basic Auth
conn = connections.basic('my_app_id')
username = conn.username
password = conn.password
# For Bearer Token
conn = connections.bearer_token('my_app_id')
token = conn.token
Common ConnectionType Values:
ConnectionType.API_KEY_AUTH- For API key authenticationConnectionType.OAUTH2_AUTH_CODE- For OAuth2 authorization code flowConnectionType.BASIC_AUTH- For basic username/password authenticationConnectionType.BEARER_TOKEN- For bearer token authenticationConnectionType.KEY_VALUE- For key-value credential pairs
Import Script Requirements: When importing tools with credentials, you must:
- Import connections FIRST (before tools)
- Use
--app-idflag when importing tools
# Step 1: Import connections
orchestrate connections import -f connections/apify.yaml
orchestrate connections import -f connections/google_sheets.yaml
# Step 2: Import tools with --app-id flags
orchestrate tools import -k python -f tools/linkedin_tools.py --app-id apify
orchestrate tools import -k python -f tools/sheets_tools.py --app-id google_sheets
# Step 3: Configure credentials (done separately for security)
orchestrate connections configure apify draft
orchestrate connections configure google_sheets draft
Key Points:
- Connection YAMLs define the structure, NOT the actual credentials
- Actual credentials are set separately using
orchestrate connections configure - Tools fetch credentials at runtime using the
connectionsmodule - Never hardcode credentials in tool code or connection YAMLs
- The
app_idin connection YAML must match theapp_idinExpectedCredentials
8. Testing
- Provide both CLI and programmatic testing methods
- Include example inputs in README
- Test with various input scenarios
9. Modularity
- Keep tools focused and single-purpose
- Separate concerns (tools, flows, agents)
- Reuse common utilities
Common Patterns Reference
Document Processing Pattern
Defining KVP Schemas with DocProcField
IMPORTANT: When creating KVP schemas for DocProcNode, you must use the DocProcField class to describe each field, not plain Python dictionaries. This ensures proper type validation and schema structure.
from ibm_watsonx_orchestrate.flow_builder.types import (
DocProcKVPSchema,
DocProcField,
DocProcOutputFormat,
)
# ✅ Correct - Using DocProcField class for field definitions
INVOICE_KVP_SCHEMA = DocProcKVPSchema(
document_type="Invoice",
document_description="A business invoice document with itemized line items",
additional_prompt_instructions="Extract all values exactly as they appear in the document.",
fields={
"invoice_number": DocProcField(
description="The unique identifier for the invoice",
default="",
example="INV-2024-001234",
),
"invoice_date": DocProcField(
description="The date the invoice was issued",
default="",
example="2024-01-15",
),
"vendor_name": DocProcField(
description="The name of the company or person issuing the invoice",
default="",
example="ABC Services Inc.",
),
"total_amount": DocProcField(
description="The final total amount due",
default="",
example="$6,464.25",
),
}
)
# ❌ Incorrect - Using plain dictionaries (will cause errors)
WRONG_KVP_SCHEMA = {
"document_type": "Invoice",
"fields": {
"invoice_number": {
"description": "The unique identifier for the invoice",
"example": "INV-2024-001234"
}
}
}
Key Points:
- Always import
DocProcKVPSchemaandDocProcFieldfromibm_watsonx_orchestrate.flow_builder.types - Use
DocProcKVPSchemato wrap the entire schema definition - Use
DocProcFieldfor each field in thefieldsdictionary - Each
DocProcFieldshould include:description: Clear description of what the field containsdefault: Default value (typically empty string for optional fields)example: Example value to guide extraction
Complete Document Processing Flow Example
from pydantic import BaseModel, Field
from ibm_watsonx_orchestrate.flow_builder.flows import Flow, flow, START, END
from ibm_watsonx_orchestrate.flow_builder.types import (
DocProcInput,
DocProcKVPSchema,
DocProcField,
DocProcOutputFormat,
)
# 1. Define KVP Schema using DocProcField
DOCUMENT_KVP_SCHEMA = DocProcKVPSchema(
document_type="Invoice",
document_description="Business invoice document",
additional_prompt_instructions="Extract all values exactly as they appear.",
fields={
"field_name": DocProcField(
description="Field description",
default="",
example="Example value",
),
}
)
# 2. Create Document Processing Flow
@flow(name="doc_flow", input_schema=DocProcInput)
def build_doc_flow(aflow: Flow) -> Flow:
"""
CRITICAL: Always use signature: def build_<flow_name>(aflow: Flow) -> Flow:
"""
doc_node = aflow.docproc(
name="extract_data",
task="text_extraction",
document_structure=True,
enable_hw=True,
output_format=DocProcOutputFormat.object, # Returns JSON object instead of file reference
kvp_schemas=[DOCUMENT_KVP_SCHEMA], # Pass the schema directly
kvp_force_schema_name="Invoice",
)
# Explicit input mapping
doc_node.map_input(
input_variable="document_ref",
expression="flow.input.document_ref"
)
doc_node.map_input(
input_variable="kvp_schemas",
expression="flow.input.kvp_schemas"
)
aflow.sequence(START, doc_node, END)
# IMPORTANT: KVPs have complex structure with key.semantic_label and value.raw_text
# Recommended: Pass entire KVP array to a prompt node for formatting
# See "CRITICAL: Document Processing KVP Structure" section below for details
return aflow
Alternative: Using Code Blocks or Python Tools for Complex Processing
If you need custom Python logic that cannot be expressed in single-line expressions, you have two options:
Code Block (Script Node) - Faster, but with restrictions
- Use
aflow.script()to add a code block node - Executes Python code within the flow
- Restrictions: Limited imports, no file I/O, restricted libraries
- See: https://www.ibm.com/docs/en/watsonx/watson-orchestrate/base?topic=workflows-code-blocks
- Use
Python Tool - More flexible, but slower
- Create a separate Python tool with
@tooldecorator - Can use any Python libraries and imports
- Called as a tool node in the flow:
aflow.tool(my_tool_function) - Better for complex logic, external API calls, or file operations
- Create a separate Python tool with
Example using code block for KVP processing:
# Code block to extract specific KVP values
code_block = aflow.script(
name="extract_kvp_values",
code="""
# Extract values from KVP structure
vendor = next((kvp['value']['raw_text'] for kvp in kvps
if kvp.get('key', {}).get('semantic_label') == 'vendor_name'), '')
total = next((kvp['value']['raw_text'] for kvp in kvps
if kvp.get('key', {}).get('semantic_label') == 'total_amount'), '')
result = {'vendor': vendor, 'total': total}
""",
input_schema=KVPInput,
output_schema=ExtractedValues
)
Recommendation: For KVP processing, use a prompt node to format the data rather than code blocks or complex expressions. The LLM can intelligently parse the KVP structure and create user-friendly output.
IMPORTANT: Prompt Node Requirements
- The
system_promptparameter is REQUIRED for all prompt nodes - It must be a string or list of strings defining the assistant's role
- Example:
system_prompt="You are a helpful assistant that formats data."
CRITICAL: Document Processing KVP Structure
When using output_format=DocProcOutputFormat.object in docproc nodes, the kvps field is returned as a list of complex objects with the following structure:
{
"id": "KVP_000001",
"type": "only_value",
"key": {
"id": "KEY_000001",
"semantic_label": "vendor_name",
"raw_text": null,
"normalized_text": null,
"confidence_score": null,
"bbox": null
},
"value": {
"id": "VALUE_000001",
"raw_text": "ABC Store Inc.",
"normalized_text": null,
"confidence_score": 0.95,
"bbox": {...}
},
"group_id": null,
"table_id": null
}
Key Points:
- Each KVP has a
keyobject withsemantic_label(the field name from your schema) - Each KVP has a
valueobject withraw_text(the extracted value) - To access a specific field, match the
semantic_labeland extract theraw_text
Two Approaches to Handle KVPs:
- Pass Entire KVP Array to Prompt Node (Recommended)
- Let an LLM format the complex KVP structure into user-friendly output
- The prompt node receives the full KVP array and formats it
# Create a prompt node to format KVPs
# IMPORTANT: system_prompt is REQUIRED for prompt nodes
summary_node = aflow.prompt(
name="format_summary",
system_prompt="You are a helpful assistant that formats data.",
user_prompt=["Format this data: {kvps}"],
output_schema=SummaryOutput
)
# Map the entire KVP array to the prompt
summary_node.map_input(
input_variable="kvps",
expression="flow['extract_data'].output.kvps"
)
# Output the formatted summary
aflow.map_output(
output_variable="summary",
expression="flow['format_summary'].output.summary"
)
- Extract Individual Fields Using List Comprehension
- Use single-line Python expressions in
map_output - Match
semantic_labeland extractraw_text
- Use single-line Python expressions in
# ✅ Correct - Extract value by semantic_label using list comprehension
aflow.map_output(
output_variable="vendor_name",
expression="[kvp['value']['raw_text'] for kvp in flow['extract_data'].output.kvps if kvp.get('key', {}).get('semantic_label') == 'vendor_name'][0] if [kvp for kvp in flow['extract_data'].output.kvps if kvp.get('key', {}).get('semantic_label') == 'vendor_name'] else ''"
)
# ❌ Incorrect - Cannot use Python functions in expressions
def get_value(field): # This won't work at runtime!
return f"flow['node'].output.kvps[0].get('{field}')"
# ❌ Incorrect - Wrong structure (kvps is not a simple dictionary)
aflow.map_output(
output_variable="vendor_name",
expression="flow['extract_data'].output.kvps[0].get('vendor_name', '')"
)
IMPORTANT: Expression Constraints
- Output mapping expressions must be single-line Python expressions
- You cannot define or call Python functions in expressions
- Functions defined in your flow file are not available at runtime
- Use list comprehensions and inline logic only
- The flow engine evaluates expressions in its own runtime context
User Activity Pattern
@flow(name="user_flow", input_schema=InputSchema)
def build_user_flow(aflow: Flow) -> Flow:
activity_node = aflow.user_activity(
name="collect_input",
display_name="Collect User Input",
description="Gather information from user"
)
process_node = aflow.tool(process_data)
aflow.sequence(START, activity_node, process_node, END)
return aflow
Conditional Flow Pattern
@flow(name="conditional_flow", input_schema=InputSchema)
def build_conditional_flow(aflow: Flow) -> Flow:
"""
CRITICAL: Always use signature: def build_<flow_name>(aflow: Flow) -> Flow:
"""
check_node = aflow.tool(check_condition)
true_branch = aflow.tool(handle_true)
false_branch = aflow.tool(handle_false)
aflow.sequence(START, check_node)
aflow.if_else(
condition="flow.check_node.output.is_valid",
if_true=true_branch,
if_false=false_branch
)
aflow.sequence(true_branch, END)
aflow.sequence(false_branch, END)
return aflow
Additional Resources
- Official Documentation: https://developer.watson-orchestrate.ibm.com
- ADK GitHub Repository: https://github.com/IBM/watsonx-orchestrate-adk
- Examples Directory: examples/ in the ADK repository
- API Reference: src/ibm_watsonx_orchestrate/
- Support: IBM watsonx Orchestrate support channels