Tôi vẫn nhớ rõ ngày hôm đó — dự án xử lý đơn hàng tự động của tôi đang chạy ngon trên Coze khi bỗng nhiên nhận được notification: ConnectionError: timeout exceeded 30s. Toàn bộ pipeline bị treo, 200 đơn hàng chờ xử lý. Sau 3 tiếng debug, tôi phát hiện vấn đề nằm ở chỗ: node LLM gọi tool mà không có timeout handler, và condition branch bị sai logic dẫn đến loop vô hạn.
Bài viết hôm nay sẽ chia sẻ kinh nghiệm thực chiến về cách cấu hình Coze workflow nodes — đặc biệt là LLM tool calling và condition branches — để bạn tránh những cái bẫy mà tôi đã gặp.
Tổng quan về Kiến trúc Coze Workflow
Coze cho phép xây dựng workflow bằng cách kết nối các node với nhau. Với HolySheep AI — nền tảng API AI có độ trễ dưới 50ms, tỷ giá chỉ ¥1=$1 (tiết kiệm 85%+ so với OpenAI) — bạn có thể tích hợp LLM mạnh mẽ vào workflow một cách đáng tin cậy.
Cấu hình LLM Node với Tool Calling
Tool calling cho phép LLM chủ động gọi function để thực hiện tác vụ cụ thể. Dưới đây là cách cấu hình đúng cách.
Bước 1: Định nghĩa Tool Schema
{
"name": "get_order_status",
"description": "Truy vấn trạng thái đơn hàng theo order_id",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Mã đơn hàng cần tra cứu"
},
"include_history": {
"type": "boolean",
"description": "Bao gồm lịch sử thay đổi trạng thái",
"default": false
}
},
"required": ["order_id"]
}
}
Bước 2: Kết nối với HolySheep API
#!/usr/bin/env python3
import httpx
import json
from typing import Optional
class CozeLLMConnector:
"""Kết nối Coze workflow với HolySheep AI qua tool calling"""
BASE_URL = "https://api.holysheep.ai/v1"
def __init__(self, api_key: str):
self.api_key = api_key
self.client = httpx.Client(
timeout=30.0, # Timeout 30s - quan trọng để tránh treo
limits=httpx.Limits(max_keepalive_connections=20)
)
def call_with_tools(
self,
model: str,
messages: list,
tools: list,
tool_choice: str = "auto"
) -> dict:
"""
Gọi LLM với tool calling qua HolySheep API
Args:
model: Tên model (gpt-4.1, claude-sonnet-4.5, deepseek-v3.2)
messages: Lịch sử hội thoại
tools: Danh sách tool schema
tool_choice: "auto", "none", hoặc {"type": "function", "function": {...}}
Returns:
Response với tool_calls nếu có
"""
endpoint = f"{self.BASE_URL}/chat/completions"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"tools": tools,
"tool_choice": tool_choice,
"stream": False
}
try:
response = self.client.post(endpoint, headers=headers, json=payload)
response.raise_for_status()
return response.json()
except httpx.TimeoutException:
# Xử lý timeout - trả về fallback response
return {
"error": "timeout",
"message": "LLM response exceeded 30s timeout",
"fallback_action": "retry_with_simpler_prompt"
}
except httpx.HTTPStatusError as e:
# Xử lý HTTP error codes
if e.response.status_code == 401:
return {"error": "unauthorized", "message": "Invalid API key"}
elif e.response.status_code == 429:
return {"error": "rate_limit", "message": "Rate limit exceeded", "retry_after": 60}
raise
Sử dụng với DeepSeek V3.2 - model rẻ nhất, chỉ $0.42/MTok
connector = CozeLLMConnector(api_key="YOUR_HOLYSHEEP_API_KEY")
result = connector.call_with_tools(
model="deepseek-v3.2",
messages=[
{"role": "system", "content": "Bạn là trợ lý xử lý đơn hàng"},
{"role": "user", "content": "Kiểm tra trạng thái đơn hàng ORD-2024-8847"}
],
tools=[{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Truy vấn trạng thái đơn hàng",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"}
},
"required": ["order_id"]
}
}
}]
)
print(f"Kết quả: {json.dumps(result, indent=2, ensure_ascii=False)}")
Cấu hình Condition Branch — Tránh Loop Vô Hạn
Đây là phần dễ gây ra bug nhất trong workflow. Condition branch cần được thiết kế cẩn thận với exit condition rõ ràng.
#!/usr/bin/env python3
"""
Coze Workflow: Condition Branch Logic Handler
Xử lý các nhánh điều kiện với timeout protection
"""
from enum import Enum
from dataclasses import dataclass
from typing import Callable, Optional
import time
class WorkflowBranch(Enum):
"""Các nhánh có thể có trong workflow"""
SUCCESS = "success"
RETRY = "retry"
FALLBACK = "fallback"
ERROR = "error"
TIMEOUT = "timeout"
@dataclass
class BranchCondition:
"""Định nghĩa một điều kiện rẽ nhánh"""
name: str
check: Callable[[dict], bool]
max_retries: int = 3
timeout_seconds: float = 30.0
class ConditionBranchHandler:
"""
Xử lý rẽ nhánh có điều kiện trong Coze workflow
Điểm quan trọng:
1. Luôn có exit condition
2. Retry với exponential backoff
3. Timeout protection cho mỗi nhánh
"""
def __init__(self):
self.conditions: list[BranchCondition] = []
self.execution_log: list[dict] = []
def add_condition(
self,
name: str,
check_fn: Callable[[dict], bool],
max_retries: int = 3
):
"""Thêm một điều kiện rẽ nhánh"""
self.conditions.append(
BranchCondition(name=name, check=check_fn, max_retries=max_retries)
)
def evaluate(
self,
context: dict,
llm_response: dict,
available_tools: list[str]
) -> WorkflowBranch:
"""
Đánh giá response và quyết định nhánh thực thi
Returns:
WorkflowBranch tương ứng với điều kiện thỏa mãn
"""
# Kiểm tra lỗi HTTP
if "error" in llm_response:
error_type = llm_response["error"]
if error_type == "timeout":
self.execution_log.append({
"branch": "timeout",
"timestamp": time.time(),
"context": context.get("order_id", "unknown")
})
return WorkflowBranch.RETRY
elif error_type == "unauthorized":
self.execution_log.append({
"branch": "error",
"error": "API key invalid",
"timestamp": time.time()
})
return WorkflowBranch.ERROR
elif error_type == "rate_limit":
self.execution_log.append({
"branch": "retry",
"reason": "rate_limit",
"retry_after": llm_response.get("retry_after", 60),
"timestamp": time.time()
})
return WorkflowBranch.FALLBACK
# Kiểm tra tool_calls từ LLM response
tool_calls = llm_response.get("choices", [{}])[0].get("message", {}).get("tool_calls", [])
if tool_calls:
# LLM muốn gọi tool
for call in tool_calls:
tool_name = call.get("function", {}).get("name", "")
if tool_name in available_tools:
self.execution_log.append({
"branch": "success",
"tool_called": tool_name,
"timestamp": time.time()
})
return WorkflowBranch.SUCCESS
else:
# Tool không có sẵn -> fallback
self.execution_log.append({
"branch": "fallback",
"reason": f"Tool {tool_name} not available",
"timestamp": time.time()
})
return WorkflowBranch.FALLBACK
# Kiểm tra điều kiện tùy chỉnh
for condition in self.conditions:
if condition.check(context):
return WorkflowBranch.SUCCESS
# Mặc định: fallback
return WorkflowBranch.FALLBACK
def get_retry_strategy(self, attempt: int) -> float:
"""
Exponential backoff cho retry
Args:
attempt: Số lần thử (bắt đầu từ 1)
Returns:
Thời gian chờ tính bằng giây
"""
base_delay = 1.0 # 1 giây
max_delay = 60.0 # Tối đa 60 giây
delay = min(base_delay * (2 ** (attempt - 1)), max_delay)
# Thêm jitter để tránh thundering herd
import random
jitter = random.uniform(0, 0.3 * delay)
return delay + jitter
Demo sử dụng
handler = ConditionBranchHandler()
Thêm điều kiện tùy chỉnh
handler.add_condition(
name="high_value_order",
check_fn=lambda ctx: ctx.get("order_value", 0) > 10000,
max_retries=5
)
Test với mock response
test_context = {"order_id": "ORD-2024-8847", "order_value": 15000}
Trường hợp 1: Timeout
timeout_response = {
"error": "timeout",
"message": "LLM response exceeded 30s"
}
branch = handler.evaluate(test_context, timeout_response, [])
print(f"Branch cho timeout: {branch.value}") # -> retry
Trường hợp 2: Tool call thành công
success_response = {
"choices": [{
"message": {
"tool_calls": [{
"function": {"name": "get_order_status"}
}]
}
}]
}
branch = handler.evaluate(test_context, success_response, ["get_order_status"])
print(f"Branch cho tool call: {branch.value}") # -> success
print(f"\nExecution log: {json.dumps(handler.execution_log, indent=2)}")
Tích hợp Hoàn chỉnh vào Coze Workflow
Dưới đây là workflow hoàn chỉnh kết hợp LLM tool calling với condition branches:
#!/usr/bin/env python3
"""
Coze Workflow: Complete Order Processing Pipeline
Pipeline hoàn chỉnh xử lý đơn hàng với error handling
"""
import asyncio
import httpx
from typing import Optional
from dataclasses import dataclass
import json
@dataclass
class OrderContext:
"""Context cho workflow xử lý đơn hàng"""
order_id: str
customer_id: str
items: list[dict]
total_amount: float
priority: str = "normal"
class OrderProcessingWorkflow:
"""
Workflow xử lý đơn hàng hoàn chỉnh
Nodes trong workflow:
1. LLM Analyze (phân tích đơn hàng, quyết định action)
2. Condition Router (định tuyến theo loại action)
3. Tool Executor (gọi tool tương ứng)
4. Result Aggregator (tổng hợp kết quả)
"""
BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_KEY = "YOUR_HOLYSHEEP_API_KEY"
def __init__(self):
self.http_client = httpx.AsyncClient(
timeout=httpx.Timeout(30.0, connect=5.0),
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20)
)
self.available_tools = [
"check_inventory",
"calculate_shipping",
"apply_discount",
"confirm_payment",
"create_shipment"
]
async def llm_analyze_node(self, order: OrderContext) -> dict:
"""
Node 1: LLM phân tích đơn hàng
Prompt gửi sang HolySheep API (model: gpt-4.1 hoặc deepseek-v3.2)
"""
system_prompt = """Bạn là AI phân tích đơn hàng tự động.
Với mỗi đơn hàng, quyết định action cần thực hiện:
- "validate": Xác thực thông tin đơn hàng
- "discount": Áp dụng giảm giá nếu đủ điều kiện
- "fulfill": Xử lý fulfillment trực tiếp
- "review": Cần human review
Luôn trả về JSON với fields: action, reasoning, confidence"""
user_prompt = f"""Phân tích đơn hàng:
- Mã đơn: {order.order_id}
- Khách hàng: {order.customer_id}
- Sản phẩm: {json.dumps(order.items, ensure_ascii=False)}
- Tổng tiền: ${order.total_amount}
- Ưu tiên: {order.priority}"""
headers = {
"Authorization": f"Bearer {self.HOLYSHEEP_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": "deepseek-v3.2", # Model rẻ nhất, $0.42/MTok
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"tools": [{
"type": "function",
"function": {
"name": tool,
"description": f"Tool để {tool.replace('_', ' ')}"
}
} for tool in self.available_tools],
"tool_choice": "auto",
"response_format": {"type": "json_object"}
}
try:
response = await self.http_client.post(
f"{self.BASE_URL}/chat/completions",
headers=headers,
json=payload
)
response.raise_for_status()
result = response.json()
return {
"success": True,
"action": result.get("choices", [{}])[0].get("message", {}).get("content", "{}"),
"tool_calls": result.get("choices", [{}])[0].get("message", {}).get("tool_calls", [])
}
except httpx.TimeoutException:
return {
"success": False,
"error": "timeout",
"action": "retry",
"retry_count": 1
}
except httpx.HTTPStatusError as e:
return {
"success": False,
"error": f"HTTP_{e.response.status_code}",
"action": "fallback"
}
def condition_router(self, llm_result: dict) -> str:
"""
Node 2: Condition Router - định tuyến theo kết quả LLM
Flow:
- success + tool_calls -> execute_tools
- success + no tool -> direct_fulfillment
- error = timeout -> retry
- error = other -> human_review
"""
if not llm_result.get("success"):
error_type = llm_result.get("error", "")
if error_type == "timeout":
return "retry"
return "human_review"
tool_calls = llm_result.get("tool_calls", [])
if tool_calls:
return "execute_tools"
action = llm_result.get("action", "")
try:
action_obj = json.loads(action) if isinstance(action, str) else action
if action_obj.get("action") == "review":
return "human_review"
except:
pass
return "direct_fulfillment"
async def execute_tools_node(self, tool_calls: list) -> dict:
"""
Node 3: Tool Executor - thực thi các tool được gọi
"""
results = []
for call in tool_calls:
tool_name = call.get("function", {}).get("name", "")
tool_args = json.loads(call.get("function", {}).get("arguments", "{}"))
# Thực thi tool (mock implementation)
result = await self._execute_single_tool(tool_name, tool_args)
results.append({
"tool": tool_name,
"args": tool_args,
"result": result
})
return {"tool_results": results, "all_success": all(r.get("success") for r in results)}
async def _execute_single_tool(self, tool_name: str, args: dict) -> dict:
"""Execute một tool đơn lẻ"""
# Mock implementation - thay bằng logic thực tế
return {
"success": True,
"tool": tool_name,
"output": f"Executed {tool_name} with args {args}"
}
async def run(self, order: OrderContext) -> dict:
"""
Chạy complete workflow
Returns:
Dict chứa kết quả và execution trace
"""
trace = []
max_retries = 3
for attempt in range(1, max_retries + 1):
# Node 1: LLM Analyze
llm_result = await self.llm_analyze_node(order)
trace.append({
"node": "llm_analyze",
"attempt": attempt,
"result": llm_result
})
# Node 2: Condition Router
next_step = self.condition_router(llm_result)
trace.append({
"node": "condition_router",
"decision": next_step
})
if next_step == "retry":
if attempt < max_retries:
await asyncio.sleep(2 ** attempt) # Exponential backoff
continue
else:
return {"status": "failed", "reason": "max_retries_exceeded", "trace": trace}
# Node 3: Execute Tools
if next_step