一键导入
wxo-builder
// Expert guidance for generating watsonx Orchestrate native solutions from SOPs or simple prompts. Implements agents, flows, tools, and knowledge bases based on business requirements.
// Expert guidance for generating watsonx Orchestrate native solutions from SOPs or simple prompts. Implements agents, flows, tools, and knowledge bases based on business requirements.
| 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"] |
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.
Generate complete watsonx Orchestrate implementations from:
sop-builder skill to generate SOPs from BPMN diagrams, n8n JSON, Langflow JSON, or other workflow models first)Start with Business Requirements:
sop-builder skill to generate an SOP firstGenerate wxO Solution: This skill (wxo-builder) transforms the SOP or prompt into a complete watsonx Orchestrate implementation with:
ADK Repository: https://github.com/IBM/ibm-watsonx-orchestrate-adk
The ADK provides:
orchestrate command) for managing agents, tools, and environmentsAll examples and source code are available in the official GitHub repository:
Repository: https://github.com/IBM/watsonx-orchestrate-adk
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
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
Functions that agents can invoke. Three main types:
@toolWorkflows 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:
def build_<flow_name>(aflow: Flow) -> Flow:aflow with type FlowFlowbuild_Authenticated connections to external services (ServiceNow, Salesforce, etc.)
Document repositories that agents can search through for information
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:
aflow.prompt() node in flows with groq/openai/gpt-oss-120bknowledge_bases configurationwatsonx Orchestrate supports multiple knowledge base providers for RAG (Retrieval Augmented Generation) implementations. Choose the appropriate provider based on your existing infrastructure or requirements.
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:
Authentication: None required (managed service)
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:
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>
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:
Authentication: Basic Auth
orchestrate connections configure -a my_milvus_connection --kind basic
orchestrate connections set-credentials -a my_milvus_connection -u <USERNAME> -p <PASSWORD>
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:
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>
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.
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
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
| 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 |
Browse: examples/agent_builder/
Browse: examples/flow_builder/
docproc)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
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"}
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:
def build_<flow_name>(aflow: Flow) -> Flow:aflow: FlowFlowbuild_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:
def build_<name>(aflow: Flow) -> Flow:tools/[flow_name]_flow.pytools/[category]_tools.pyValidation Checklist:
@tool or @flowdef build_<name>(aflow: Flow) -> Flow: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:
import json, from typing import Optional)import requests, from pydantic import BaseModel)ibm_watsonx_orchestrate package imports (e.g., from ibm_watsonx_orchestrate.agent_builder.tools import tool)NOT Allowed:
from .utils import helper_function)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:
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
is_default_prompts: false to use custom promptsid (e.g., default0, default1, etc.)state: active for all promptswelcome_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 withis_default_message: false to use custom contentExample 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:
spec_version: v1 (will cause import errors)kind: nativellm, style, or toolsstarter_prompts and welcome_content (reduces user experience quality)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())
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:
orchestrate tools import -k python -f <path_to_tool.py>orchestrate tools import -k flow -f <path_to_flow.py>orchestrate agents import -f <path_to_agent.yaml>DO NOT:
python3 main_flow.py)ALWAYS:
orchestrate CLI commands shown above-k flag to specify tool kind (python or flow)-f flag to specify the file path${SCRIPT_DIR} for relative paths in the scriptUse 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/
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:
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/
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:
Example: user_activity/, book_a_flight/
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/
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
# 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}"}
# 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
# 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
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())
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:
orchestrate tools import -k python -f <file>orchestrate tools import -k flow -f <file>orchestrate agents import -f <file>chmod +x my_example/import-all.sh
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
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
./import-all.shorchestrate chat startmy_agentexport PYTHONPATH=<ADK>/src:<ADK>python3 main.pyDescription 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
export PYTHONPATH=/path/to/adk/src:/path/to/adk
cd examples/category/my_example
python3 main.py
Architecture Diagram Guidelines:
Workflow Diagram Guidelines:
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
class MyOutputSchema(BaseModel):
field_name: str = Field(description="Field description")
# This will cause Pydantic validation errors
output_schema=type('MySchema', (BaseModel,), {
'field_name': (str, Field(description="Field description"))
})
MUST use Google-style docstrings for all Python tools. The watsonx Orchestrate tool parser requires strict adherence to this 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
param_name (Type): DescriptionTypeName: Descriptionfrom 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}"
)
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 additional auth_type field)key_value_creds - For key-value credential pairsAPI 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:
kind: connections (plural) - Must be kind: connection (singular)kind: inside environments - Must be security_scheme:auth_type: authorization_code for OAuth2 - Must be auth_type: oauth2_auth_codeauth_type field for OAuth2 connectionsspec_version fieldCRITICAL: 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 pairsImport Script Requirements: When importing tools with credentials, you must:
--app-id flag 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:
orchestrate connections configureconnections moduleapp_id in connection YAML must match the app_id in ExpectedCredentialsIMPORTANT: 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:
DocProcKVPSchema and DocProcField from ibm_watsonx_orchestrate.flow_builder.typesDocProcKVPSchema to wrap the entire schema definitionDocProcField for each field in the fields dictionaryDocProcField should include:
description: Clear description of what the field containsdefault: Default value (typically empty string for optional fields)example: Example value to guide extractionfrom 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
aflow.script() to add a code block nodePython Tool - More flexible, but slower
@tool decoratoraflow.tool(my_tool_function)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
system_prompt parameter is REQUIRED for all prompt nodessystem_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:
key object with semantic_label (the field name from your schema)value object with raw_text (the extracted value)semantic_label and extract the raw_textTwo Approaches to Handle KVPs:
# 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"
)
map_outputsemantic_label and extract raw_text# ✅ 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
@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
@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
Expert guidance for creating high-level solution architecture documents from business requirements, use cases, or problem statements. Produces three focused documents - business overview, technical architecture, and implementation plan - with sufficient detail for elaboration into detailed SOPs.
Analyzes watsonx Orchestrate (wxO) projects and generates a 3-report documentation set covering the overall solution, agents, and tools/connections/other components with Mermaid diagrams.
Build MCP servers for customer care agents following Watson Orchestrate specifications. Guides agents through tool creation, transaction patterns, authentication, widgets, and context management with strict adherence to reference specifications. Use when creating customer care MCP servers.
Expert guidance for building a Standard Operating Procedure (SOP) from a workflow diagram, Langflow JSON, n8n JSON, BPMN model or workflow description. Focuses on business problem, data requirements, and business logic in plain language.