From 6568f2559e6247a952e46bb69e3432eccb3425be Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Mon, 30 Mar 2026 14:31:41 -0700 Subject: [PATCH 1/3] Update docstrings and OpenAI model --- README.md | 14 +-- data-pipeline/README.md | 36 ++++---- data-pipeline/template.yaml | 2 +- etl-job/README.md | 21 ++--- etl-job/main.py | 16 ++-- file-analyzer/README.md | 55 ++++++------ file-analyzer/api-service/main.py | 2 +- file-analyzer/workflow-service/main.py | 30 +++---- file-processing/README.md | 24 ++---- file-processing/main.py | 16 ++-- hello-world/README.md | 114 ++++++++++++------------- hello-world/main.py | 48 +++++------ hello-world/template.yaml | 4 +- openai-agent/README.md | 58 ++++++------- openai-agent/main.py | 6 +- openai-agent/template.yaml | 4 +- 16 files changed, 212 insertions(+), 238 deletions(-) diff --git a/README.md b/README.md index 03b3bcc..16f22e8 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ Render Workflows support both Python and TypeScript. This repo contains Python e | Example | Use Case | Key Patterns | Extra Dependencies | |---------|----------|--------------|-------------------| -| [**Hello World**](./hello-world/) | Learn workflow basics with simple number processing | Task definition, subtask calling with `await`, basic orchestration | None | -| [**ETL Job**](./etl-job/) | Process CSV data with validation and statistics | Subtasks, sequential processing, batch operations, data validation | None | -| [**OpenAI Agent**](./openai-agent/) | AI customer support agent with tool calling | Tool calling, nested subtasks (3 levels deep), stateful workflows, dynamic orchestration | `openai` | +| [**Hello World**](./hello-world/) | Learn workflow basics with simple number processing | Task definition, task chaining with `await`, basic orchestration | None | +| [**ETL Job**](./etl-job/) | Process CSV data with validation and statistics | Task chaining, sequential processing, batch operations, data validation | None | +| [**OpenAI Agent**](./openai-agent/) | AI customer support agent with tool calling | Tool calling, nested task chains (3 levels deep), stateful workflows, dynamic orchestration | `openai` | | [**File Processing**](./file-processing/) | Batch process multiple file formats in parallel | Parallel execution with `asyncio.gather()`, multi-format handling, aggregation | None | | [**Data Pipeline**](./data-pipeline/) | Multi-source customer analytics pipeline | Parallel extraction, data enrichment, combining parallel + sequential patterns | `httpx` | | [**File Analyzer**](./file-analyzer/) | API service calling workflow tasks for file analysis | Client SDK + Task SDK, workflow slugs, service separation, FastAPI integration | `fastapi`, `uvicorn` | @@ -28,8 +28,8 @@ Render Workflows support both Python and TypeScript. This repo contains Python e The simplest possible workflow — learn the fundamentals through simple number processing. - Ultra-simple task definitions -- Clear subtask calling examples -- Subtasks in loops demonstration +- Clear task-chaining examples +- Task chaining in loops demonstration - Multi-step workflow orchestration - Heavily commented code explaining every pattern @@ -43,7 +43,7 @@ Complete Extract, Transform, Load pipeline — process customer data from CSV fi - CSV data extraction with retry logic - Record validation and error tracking -- Batch processing with subtasks +- Batch processing with task chaining - Statistical aggregation - Comprehensive error handling @@ -56,7 +56,7 @@ Intelligent conversational agent — a customer support bot that can answer ques - Multi-turn conversations with context - Dynamic tool/function calling - Stateful workflow management -- Integration with OpenAI GPT-4 +- Integration with OpenAI models - Extensible tool framework [View OpenAI Agent Example →](./openai-agent/) diff --git a/data-pipeline/README.md b/data-pipeline/README.md index 8c348bb..6e4b707 100644 --- a/data-pipeline/README.md +++ b/data-pipeline/README.md @@ -189,11 +189,9 @@ task_run = await render.workflows.run_task( "data-pipeline-workflows/run_data_pipeline", {"user_ids": ["user_1", "user_2", "user_3", "user_4"]} ) - -result = await task_run -print(f"Pipeline status: {result.results['status']}") -print(f"Total revenue: ${result.results['insights']['revenue']['total']}") -print(f"Segment distribution: {result.results['segment_distribution']}") +print(f"Pipeline status: {task_run.results['status']}") +print(f"Total revenue: ${task_run.results['insights']['revenue']['total']}") +print(f"Segment distribution: {task_run.results['insights']['segment_distribution']}") ``` ## Pipeline Stages @@ -212,18 +210,18 @@ Using `asyncio.gather()` ensures all sources are fetched in parallel for maximum ### Stage 2: Transform -**`transform_user_data`**: Combines data from all sources and enriches each user by calling subtasks: +**`transform_user_data`**: Combines data from all sources and enriches each user by chaining task runs: ```python for user in users: - # SUBTASK CALL: Calculate metrics for this user + # TASK CHAINING: Calculate metrics for this user user_metrics = await calculate_user_metrics(user, transactions, engagement) - # SUBTASK CALL: Enrich with geographic data + # TASK CHAINING: Enrich with geographic data geo_data = await enrich_with_geo_data(user['email']) enriched_users.append({**user_metrics, 'geo': geo_data}) ``` -This demonstrates **sequential subtask calls per item** in a transformation loop. +This demonstrates **sequential task chaining per item** in a transformation loop. **`calculate_user_metrics`**: Calculates per-user metrics: - Total spent and refunded @@ -279,15 +277,15 @@ This demonstrates **sequential subtask calls per item** in a transformation loop ## Key Patterns Demonstrated -### Parallel Extraction with Subtasks +### Parallel Extraction with Task Chaining ```python -# SUBTASK PATTERN: Launch multiple subtasks in parallel +# TASK CHAINING PATTERN: Launch multiple task runs in parallel user_task = fetch_user_data(user_ids) transaction_task = fetch_transaction_data(user_ids) engagement_task = fetch_engagement_data(user_ids) -# SUBTASK CALLS: Wait for all three subtasks to complete +# CHAINED RUNS: Wait for all three task runs to complete user_data, transaction_data, engagement_data = await asyncio.gather( user_task, transaction_task, @@ -295,24 +293,24 @@ user_data, transaction_data, engagement_data = await asyncio.gather( ) ``` -This demonstrates **parallel subtask execution** - all three data sources are fetched simultaneously. +This demonstrates **parallel task execution** - all three data sources are fetched simultaneously. This reduces total extraction time from sum(A+B+C) to max(A,B,C). -### Data Enrichment with Subtasks +### Data Enrichment with Task Chaining -Each user is enriched by calling multiple subtasks: +Each user is enriched by chaining multiple task runs: ```python for user in users: - # SUBTASK CALL: Calculate user-specific metrics + # TASK CHAINING: Calculate user-specific metrics metrics = await calculate_user_metrics(user, transactions, engagement) - # SUBTASK CALL: Enrich with geographic data + # TASK CHAINING: Enrich with geographic data geo = await enrich_with_geo_data(user['email']) enriched_users.append({**metrics, 'geo': geo}) ``` -This shows **sequential subtask calls** for per-item enrichment. +This shows **sequential task chaining** for per-item enrichment. ### User Segmentation @@ -375,7 +373,7 @@ async def send_pipeline_notification(result: dict) -> dict: ## Important Notes -- **Python-only**: Workflows are only supported in Python via render-sdk +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support render.yaml blueprint configuration - **Mock Data**: Example uses simulated data; replace with real API calls in production - **Idempotency**: Design pipeline to be safely re-runnable diff --git a/data-pipeline/template.yaml b/data-pipeline/template.yaml index f1e517e..3628a86 100644 --- a/data-pipeline/template.yaml +++ b/data-pipeline/template.yaml @@ -13,7 +13,7 @@ renderStartCommand: python main.py nextSteps: - label: Enter your project directory command: cd {{dir}} - - label: Start your local task server + - label: Start your local workflow service command: render workflows dev -- {{startCommand}} hint: This runs your workflow service locally, allowing you to view and run tasks without deploying to Render. - label: Run the pipeline locally (in another terminal) diff --git a/etl-job/README.md b/etl-job/README.md index f265807..b0d4619 100644 --- a/etl-job/README.md +++ b/etl-job/README.md @@ -13,7 +13,7 @@ Process customer signup data from CSV files with validation, cleaning, and stati ## Features -- **Subtask Execution**: Demonstrates calling tasks from other tasks using `await` +- **Task Chaining**: Demonstrates calling tasks from other tasks using `await` - **Extract**: Read data from CSV files (extensible to APIs, databases) - **Transform**: Validate records with comprehensive error tracking - **Load**: Compute statistics and prepare aggregated insights @@ -136,11 +136,8 @@ task_run = await render.workflows.run_task( "etl-job-workflows/run_etl_pipeline", {"source_file": "sample_data.csv"} ) - -# Wait for completion -result = await task_run -print(f"Pipeline status: {result.results['status']}") -print(f"Valid records: {result.results['transform']['valid_count']}") +print(f"Pipeline status: {task_run.results['status']}") +print(f"Valid records: {task_run.results['transform']['valid_count']}") ``` ## Sample Data @@ -163,25 +160,25 @@ This demonstrates how the pipeline handles data quality issues. - Validates age range (0-120) - Returns cleaned data with error tracking -**`transform_batch`**: Processes all records by calling `validate_record` as a subtask for each one: +**`transform_batch`**: Processes all records by chaining `validate_record` for each one: ```python for record in records: - # Call validate_record as a subtask + # Chain validate_record for each record validated = await validate_record(record) ``` -This demonstrates **calling subtasks in a loop** for batch processing. +This demonstrates **task chaining in a loop** for batch processing. **`compute_statistics`**: Aggregates valid records to produce: - Country distribution - Age statistics (min, max, average) - Data quality metrics -**`run_etl_pipeline`**: Main orchestrator that calls three subtasks sequentially: +**`run_etl_pipeline`**: Main orchestrator that chains three task runs sequentially: 1. `await extract_csv_data(source_file)` - Extract data 2. `await transform_batch(raw_records)` - Validate records (which calls `validate_record` for each) 3. `await compute_statistics(valid_records)` - Generate insights -This demonstrates **sequential subtask orchestration** for multi-stage pipelines. +This demonstrates **sequential task chaining** for multi-stage pipelines. ## Extending This Example @@ -220,6 +217,6 @@ async def transform_batch_parallel(records: list[dict]) -> dict: ## Important Notes -- **Python-only**: Workflows are only supported in Python via render-sdk +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support render.yaml blueprint configuration - **Service Type**: Deploy as a Workflow service on Render (not Background Worker or Web Service) diff --git a/etl-job/main.py b/etl-job/main.py index 928754a..42c96b6 100644 --- a/etl-job/main.py +++ b/etl-job/main.py @@ -147,7 +147,7 @@ async def transform_batch(records: list[dict]) -> dict: """ Transform a batch of records by validating each one. - This demonstrates subtask execution in a loop, processing multiple + This demonstrates task chaining in a loop, processing multiple records individually while maintaining error tracking. Args: @@ -161,11 +161,11 @@ async def transform_batch(records: list[dict]) -> dict: valid_records = [] invalid_records = [] - # Process each record through validation subtask - # KEY PATTERN: Calling subtasks in a loop + # Process each record by chaining validate_record runs + # KEY PATTERN: Task chaining in a loop for i, record in enumerate(records, 1): logger.info(f"[TRANSFORM] Processing record {i}/{len(records)}") - # SUBTASK CALL: Each record is validated by calling validate_record as a subtask + # TASK CHAINING: Each record is validated by chaining validate_record validated = await validate_record(record) if validated['is_valid']: @@ -264,7 +264,7 @@ async def run_etl_pipeline(source_file: str) -> dict: 2. Transform: Validate and clean records 3. Load: Compute statistics and prepare for storage - This demonstrates a full workflow with multiple subtask calls and + This demonstrates a full workflow with multiple chained task runs and comprehensive error handling. Args: @@ -281,20 +281,20 @@ async def run_etl_pipeline(source_file: str) -> dict: try: # Stage 1: Extract logger.info("[PIPELINE] Stage 1/3: EXTRACT") - # SUBTASK CALL: Extract data from CSV + # TASK CHAINING: Extract data from CSV raw_records = await extract_csv_data(source_file) logger.info(f"[PIPELINE] Extracted {len(raw_records)} records") # Stage 2: Transform logger.info("[PIPELINE] Stage 2/3: TRANSFORM") - # SUBTASK CALL: Transform calls validate_record for each record + # TASK CHAINING: Transform chains validate_record for each record transform_result = await transform_batch(raw_records) logger.info(f"[PIPELINE] Transformation complete: " f"{transform_result['success_rate']:.1%} success rate") # Stage 3: Load (compute statistics) logger.info("[PIPELINE] Stage 3/3: LOAD") - # SUBTASK CALL: Compute final statistics + # TASK CHAINING: Compute final statistics statistics = await compute_statistics(transform_result['valid_records']) logger.info("[PIPELINE] Statistics computed") diff --git a/file-analyzer/README.md b/file-analyzer/README.md index a569bfa..7a37ac3 100644 --- a/file-analyzer/README.md +++ b/file-analyzer/README.md @@ -145,39 +145,39 @@ file-analyzer/ - Parses CSV content into structured data - Returns rows, columns, and metadata -**`calculate_statistics(data: dict) -> dict`** (Subtask) +**`calculate_statistics(data: dict) -> dict`** (Chained task) - Calculates statistical metrics for numeric columns - Returns min, max, avg, sum for each numeric column -**`identify_trends(data: dict) -> dict`** (Subtask) +**`identify_trends(data: dict) -> dict`** (Chained task) - Identifies patterns in categorical data - Returns distribution analysis and top values -**`generate_insights(stats: dict, trends: dict, metadata: dict) -> dict`** (Subtask) +**`generate_insights(stats: dict, trends: dict, metadata: dict) -> dict`** (Chained task) - Generates final insights report - Combines statistics and trends into actionable findings **`analyze_file(file_content: str) -> dict`** (Main orchestrator) - Coordinates the entire analysis pipeline -- Calls parse → calculate → identify → generate as subtasks +- Chains parse → calculate → identify → generate -### Subtask Pattern +### Task Chaining Pattern -The main `analyze_file` task demonstrates subtask orchestration: +The main `analyze_file` task demonstrates task chaining orchestration: ```python @app.task async def analyze_file(file_content: str) -> dict: - # SUBTASK CALL: Parse CSV data + # TASK CHAINING: Parse CSV data parsed_data = await parse_csv_data(file_content) - # SUBTASK CALL: Calculate statistics + # TASK CHAINING: Calculate statistics stats = await calculate_statistics(parsed_data) - # SUBTASK CALL: Identify trends + # TASK CHAINING: Identify trends trends = await identify_trends(parsed_data) - # SUBTASK CALL: Generate insights + # TASK CHAINING: Generate insights insights = await generate_insights(stats, trends, parsed_data) return {"statistics": stats, "trends": trends, "insights": insights} @@ -213,19 +213,16 @@ render = Render() service_slug = os.getenv("WORKFLOW_SERVICE_SLUG") task_identifier = f"{service_slug}/analyze_file" -# 3. Call the workflow task with arguments as a dict +# 3. Call the workflow task with arguments (list or dict) task_run = await render.workflows.run_task( task_identifier, {"file_content": file_content} ) -# 4. Await the task completion -result = await task_run - -# 5. Access the results -print(result.id) # Task run ID -print(result.status) # Task status (e.g., "SUCCEEDED") -print(result.results) # Task return value +# 4. Access the completed run details +print(task_run.id) # Task run ID +print(task_run.status) # Task status (e.g., "completed") +print(task_run.results) # Task return value ``` ## Local Development @@ -503,19 +500,16 @@ render = Render() **Calling Tasks:** ```python -# Format: render.workflows.run_task(task_identifier, {args}) +# Format: render.workflows.run_task(task_identifier, input_data) task_run = await render.workflows.run_task( "service-slug/task-name", {"arg1": value1, "arg2": value2} ) -# Await completion -result = await task_run - # Access results -print(result.id) # Task run ID -print(result.status) # "SUCCEEDED", "FAILED", etc. -print(result.results) # Return value from task +print(task_run.id) # Task run ID +print(task_run.status) # "completed", "failed", etc. +print(task_run.results) # Return value from task ``` ### 2. Task SDK Usage @@ -582,7 +576,8 @@ async def analyze_file( @app.post("/analyze") async def analyze_file(file: UploadFile): # Trigger analysis - result = await task_run + task_run = await render.workflows.run_task(task_identifier, {"file_content": content}) + result = task_run # Store in database db.insert({ @@ -661,7 +656,7 @@ def parse_excel_data(file_content: bytes) -> dict: 2. **Task Timeout**: Long-running analysis may timeout - Configure appropriate timeout in Client SDK - - Consider breaking into smaller subtasks + - Consider breaking into smaller chained tasks 3. **Concurrent Requests**: FastAPI handles concurrent requests well - Monitor workflow service capacity @@ -673,11 +668,11 @@ def parse_excel_data(file_content: bytes) -> dict: ## Important Notes -- **Python-only**: Render Workflows are only supported in Python via `render-sdk` +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support `render.yaml` blueprint configuration - **Service Types**: Workflow service (for tasks) vs Web Service (for API) -- **Task Arguments**: Passed as a dict: `{"arg1": value1, "arg2": value2}` -- **Awaitable Pattern**: Use `await task_run` to wait for completion +- **Task Arguments**: Can be passed as either a list (positional) or a dict (named) +- **Run pattern**: `run_task` already waits for completion and returns `TaskRunDetails` - **Service Slug**: Set correctly in `WORKFLOW_SERVICE_SLUG` environment variable - **API Key**: Required in both services, get from Account Settings diff --git a/file-analyzer/api-service/main.py b/file-analyzer/api-service/main.py index 5a7028b..4adc9dd 100644 --- a/file-analyzer/api-service/main.py +++ b/file-analyzer/api-service/main.py @@ -158,7 +158,7 @@ async def analyze_file(file: UploadFile = File(...)): 1. Read the uploaded file content 2. Get the Client SDK instance 3. Get the task identifier (format: {service-slug}/{task-name}) - 4. Call the task using client.workflows.run_task() + 4. Start the task using client.workflows.start_task() 5. Await the result 6. Return the analysis results diff --git a/file-analyzer/workflow-service/main.py b/file-analyzer/workflow-service/main.py index ec55246..276137b 100644 --- a/file-analyzer/workflow-service/main.py +++ b/file-analyzer/workflow-service/main.py @@ -89,7 +89,7 @@ def calculate_statistics(data: dict) -> dict: """ Calculate statistical metrics from parsed data. - This is called as a SUBTASK by the main analyze_file task. + This task is typically called via task chaining from analyze_file. Args: data: Parsed CSV data from parse_csv_data @@ -152,7 +152,7 @@ def identify_trends(data: dict) -> dict: """ Identify trends and patterns in the data. - This is called as a SUBTASK by the main analyze_file task. + This task is typically called via task chaining from analyze_file. Args: data: Parsed CSV data from parse_csv_data @@ -210,7 +210,7 @@ async def generate_insights(stats: dict, trends: dict, metadata: dict) -> dict: """ Generate final insights report combining statistics and trends. - This is called as a SUBTASK by the main analyze_file task. + This task is typically called via task chaining from analyze_file. Args: stats: Statistics from calculate_statistics @@ -265,14 +265,14 @@ async def analyze_file(file_content: str) -> dict: """ Main orchestrator task for file analysis. - This task coordinates the entire analysis pipeline by calling - other tasks as SUBTASKS. + This task coordinates the entire analysis pipeline by chaining + runs of other tasks. Pipeline: 1. Parse CSV data - 2. Calculate statistics (SUBTASK) - 3. Identify trends (SUBTASK) - 4. Generate insights (SUBTASK) + 2. Calculate statistics (chained run) + 3. Identify trends (chained run) + 4. Generate insights (chained run) Args: file_content: Raw CSV file content as string @@ -284,7 +284,7 @@ async def analyze_file(file_content: str) -> dict: # Stage 1: Parse CSV data logger.info("[ANALYZE_FILE] Stage 1: Parsing CSV data") - # SUBTASK CALL: Parse the CSV content + # TASK CHAINING: Parse the CSV content parsed_data = await parse_csv_data(file_content) if not parsed_data["success"]: @@ -297,19 +297,19 @@ async def analyze_file(file_content: str) -> dict: logger.info(f"[ANALYZE_FILE] Parsed {parsed_data['row_count']} rows") - # Stage 2: Calculate statistics (SUBTASK) + # Stage 2: Calculate statistics (chained run) logger.info("[ANALYZE_FILE] Stage 2: Calculating statistics") - # SUBTASK CALL: Calculate statistical metrics + # TASK CHAINING: Calculate statistical metrics stats = await calculate_statistics(parsed_data) - # Stage 3: Identify trends (SUBTASK) + # Stage 3: Identify trends (chained run) logger.info("[ANALYZE_FILE] Stage 3: Identifying trends") - # SUBTASK CALL: Identify patterns and trends + # TASK CHAINING: Identify patterns and trends trends = await identify_trends(parsed_data) - # Stage 4: Generate insights (SUBTASK) + # Stage 4: Generate insights (chained run) logger.info("[ANALYZE_FILE] Stage 4: Generating insights") - # SUBTASK CALL: Generate final insights report + # TASK CHAINING: Generate final insights report insights = await generate_insights(stats, trends, parsed_data) logger.info("[ANALYZE_FILE] Analysis pipeline completed successfully") diff --git a/file-processing/README.md b/file-processing/README.md index 48674e1..ddb22e0 100644 --- a/file-processing/README.md +++ b/file-processing/README.md @@ -191,26 +191,20 @@ render = Render() # Process a batch of files task_run = await render.workflows.run_task( "file-processing-workflows/process_file_batch", - { - "file_paths": [ - "sample_files/sales_data.csv", - "sample_files/config.json", - "sample_files/report.txt" - ] - } + [ + "sample_files/sales_data.csv", + "sample_files/config.json", + "sample_files/report.txt" + ] ) - -result = await task_run -print(f"Processed {result.results['successful']}/{result.results['total_files']} files") +print(f"Processed {task_run.results['successful']}/{task_run.results['total_files']} files") # Generate consolidated report report_run = await render.workflows.run_task( "file-processing-workflows/generate_consolidated_report", - {"batch_result": result.results} + {"batch_result": task_run.results} ) - -report = await report_run -print(f"Report: {report.results['summary']}") +print(f"Report: {report_run.results['summary']}") ``` ## Sample Files @@ -327,7 +321,7 @@ async def export_to_database(report: dict) -> dict: ## Important Notes -- **Python-only**: Workflows are only supported in Python via render-sdk +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support render.yaml blueprint configuration - **File Access**: In production, integrate with cloud storage (S3, GCS) or databases - **Retry Logic**: All read operations include retry configuration for transient failures diff --git a/file-processing/main.py b/file-processing/main.py index 0a09a66..1d43a1f 100644 --- a/file-processing/main.py +++ b/file-processing/main.py @@ -362,21 +362,21 @@ async def process_single_file(file_path: str) -> dict: extension = path.suffix.lower() # Read file based on type - # SUBTASK PATTERN: Chain multiple subtask calls together + # TASK CHAINING PATTERN: Chain multiple task runs together if extension == '.csv': - # SUBTASK CALL: Read CSV file + # TASK CHAINING: Read CSV file read_result = await read_csv_file(file_path) - # SUBTASK CALL: Analyze the CSV data (if read was successful) + # TASK CHAINING: Analyze CSV data (if read was successful) analysis = await analyze_csv_data(read_result) if read_result.get("success") else {} elif extension == '.json': - # SUBTASK CALL: Read JSON file + # TASK CHAINING: Read JSON file read_result = await read_json_file(file_path) - # SUBTASK CALL: Analyze JSON structure + # TASK CHAINING: Analyze JSON structure analysis = await analyze_json_structure(read_result) if read_result.get("success") else {} elif extension == '.txt': - # SUBTASK CALL: Read text file + # TASK CHAINING: Read text file read_result = await read_text_file(file_path) - # SUBTASK CALL: Analyze text content + # TASK CHAINING: Analyze text content analysis = await analyze_text_content(read_result) if read_result.get("success") else {} else: logger.warning(f"[PROCESS] Unsupported file type: {extension}") @@ -418,7 +418,7 @@ async def process_file_batch(*file_paths: str) -> dict: logger.info("=" * 80) # Process all files in parallel - # SUBTASK PATTERN: Call multiple subtasks concurrently using asyncio.gather() + # TASK CHAINING PATTERN: Chain multiple task runs concurrently via asyncio.gather() logger.info("[BATCH] Launching parallel file processing tasks...") tasks = [process_single_file(fp) for fp in file_paths_list] results = await asyncio.gather(*tasks) diff --git a/hello-world/README.md b/hello-world/README.md index afcc659..5b4e9d9 100644 --- a/hello-world/README.md +++ b/hello-world/README.md @@ -6,7 +6,7 @@ The simplest possible workflow example to help you understand the basics of Rend This example teaches the fundamental concepts: - **What is a Task?** - A function decorated with `@app.task` that can be executed as a workflow -- **What is a Subtask?** - A task called by another task using `await` +- **What is Task Chaining?** - Calling one task from another with `await` - **How to Orchestrate** - Combining multiple tasks to create workflows - **How to Deploy** - Getting your first workflow running on Render @@ -19,19 +19,19 @@ Simple number processing to demonstrate workflow patterns without complex busine ``` calculate_and_process (multi-step orchestrator) ├── add_doubled_numbers - │ ├── double (subtask #1) - │ └── double (subtask #2) + │ ├── double (chained run #1) + │ └── double (chained run #2) └── process_numbers - ├── double (subtask for item 1) - ├── double (subtask for item 2) - └── double (subtask for item N) + ├── double (chained run for item 1) + ├── double (chained run for item 2) + └── double (chained run for item N) ``` -## Understanding Tasks and Subtasks +## Understanding Tasks and Task Chaining ### What is a Task? -A **task** is simply a Python function decorated with `@app.task`. It becomes a workflow step that Render can execute: +A **task** is simply a Python function decorated with `@app.task`. It becomes a unit of workflow execution that Render can run: ```python from render_sdk import Workflows @@ -46,27 +46,27 @@ def double(x: int) -> int: app.start() ``` -### What is a Subtask? +### What is Task Chaining? -A **subtask** is when one task calls another task using `await`. This is how you compose workflows: +Task chaining is when one task calls another task using `await`. This is how you compose runs into a workflow: ```python @app.task async def add_doubled_numbers(a: int, b: int) -> dict: - # Call 'double' as a subtask using await - doubled_a = await double(a) # ← This is a subtask call! - doubled_b = await double(b) # ← This is also a subtask call! + # Chain two runs of 'double' using await + doubled_a = await double(a) # ← Chained run + doubled_b = await double(b) # ← Another chained run return { "sum": doubled_a + doubled_b } ``` -### Why Use Subtasks? +### Why Use Task Chaining? 1. **Reusability**: Write `double` once, use it everywhere 2. **Composition**: Build complex workflows from simple building blocks -3. **Visibility**: Render shows you each subtask execution in the dashboard +3. **Visibility**: Render shows each task run in the dashboard 4. **Testing**: Test individual tasks independently ## Local Development @@ -173,7 +173,7 @@ Expected output: `10` --- -**Test subtask calling:** +**Test task chaining:** Task: `add_doubled_numbers` @@ -192,11 +192,11 @@ Expected output: } ``` -This task calls `double` twice as subtasks! +This task chains two runs of `double`. --- -**Test subtask in a loop:** +**Test task chaining in a loop:** Task: `process_numbers` @@ -211,11 +211,11 @@ Expected output: "original_numbers": [1, 2, 3, 4, 5], "doubled_numbers": [2, 4, 6, 8, 10], "count": 5, - "explanation": "Processed 5 numbers through the double subtask" + "explanation": "Processed 5 numbers by chaining runs of double" } ``` -This calls `double` as a subtask 5 times (once for each number)! +This chains `double` 5 times (once for each number). --- @@ -228,7 +228,7 @@ Input: [2, 3, 10, 20, 30] ``` -This is the most complex example - it calls `add_doubled_numbers` and `process_numbers` as subtasks, which in turn call `double` multiple times. Watch the Render Dashboard to see the entire execution tree! +This is the most complex example: it chains `add_doubled_numbers` and `process_numbers`, which in turn chain `double` multiple times. ## Triggering via SDK @@ -243,18 +243,16 @@ render = Render() # Call the simple double task task_run = await render.workflows.run_task( "hello-world-workflows/double", - {"x": 5} + [5] ) -result = await task_run -print(f"Result: {result.results}") # Output: 10 +print(f"Result: {task_run.results}") # Output: 10 -# Call the subtask orchestration example +# Call a task-chaining example task_run = await render.workflows.run_task( "hello-world-workflows/add_doubled_numbers", - {"a": 3, "b": 4} + [3, 4] ) -result = await task_run -print(f"Sum of doubled: {result.results['sum_of_doubled']}") # Output: 14 +print(f"Sum of doubled: {task_run.results['sum_of_doubled']}") # Output: 14 ``` ## Tasks Explained @@ -265,62 +263,62 @@ The simplest possible task. Takes a number, doubles it, returns the result. **Purpose**: Show what a basic task looks like. -**Can be called as a subtask**: Yes! Other tasks call this. +**Can be chained from other tasks**: Yes. --- ### `add_doubled_numbers(a: int, b: int) -> dict` -Demonstrates the fundamental subtask pattern. +Demonstrates the fundamental task-chaining pattern. **What it does**: -1. Calls `double(a)` as a subtask -2. Calls `double(b)` as a subtask +1. Chains `double(a)` +2. Chains `double(b)` 3. Adds the results together -**Purpose**: Show how to call tasks as subtasks using `await`. +**Purpose**: Show how to chain task runs with `await`. **Key Pattern**: ```python -result = await double(a) # ← Subtask call with await +result = await double(a) # ← Chained run with await ``` --- ### `process_numbers(numbers: list[int]) -> dict` -Demonstrates calling a subtask in a loop. +Demonstrates chaining task runs in a loop. **What it does**: 1. Takes a list of numbers -2. Calls `double` as a subtask for each number +2. Chains `double` for each number 3. Collects all the results -**Purpose**: Show how to process lists/batches using subtasks. +**Purpose**: Show how to process lists/batches with task chaining. **Key Pattern**: ```python for num in numbers: - doubled = await double(num) # ← Subtask call in a loop + doubled = await double(num) # ← Task chaining in a loop ``` --- ### `calculate_and_process(a: int, b: int, more_numbers: list[int]) -> dict` -Demonstrates a multi-step workflow with multiple subtask calls. +Demonstrates a multi-step workflow with multiple chained task runs. **What it does**: -1. Calls `add_doubled_numbers` as a subtask -2. Calls `process_numbers` as a subtask +1. Chains `add_doubled_numbers` +2. Chains `process_numbers` 3. Combines the results -**Purpose**: Show how to chain multiple subtasks to create complex workflows. +**Purpose**: Show how to chain multiple task runs to create complex workflows. **Key Pattern**: ```python -step1 = await add_doubled_numbers(a, b) # ← First subtask -step2 = await process_numbers(numbers) # ← Second subtask +step1 = await add_doubled_numbers(a, b) # ← First chained run +step2 = await process_numbers(numbers) # ← Second chained run # Combine results ``` @@ -344,18 +342,18 @@ app.start() ### The `async` Keyword -Tasks that call other tasks as subtasks must be `async`: +Tasks that chain other tasks must be `async`: ```python @app.task async def orchestrator(): - result = await subtask() # ← Calls another task + result = await task_b() # ← Chains another task return result ``` ### The `await` Keyword -Use `await` to call a task as a subtask: +Use `await` to chain a run of another task: ```python result = await task_name(arguments) @@ -369,9 +367,9 @@ All `@app.task` decorated functions are registered when defined. Call `app.start ## Common Patterns -### Pattern 1: Sequential Subtasks +### Pattern 1: Sequential Task Chaining -Execute subtasks one after another: +Execute chained runs one after another: ```python @app.task @@ -382,9 +380,9 @@ async def sequential(): return step3 ``` -### Pattern 2: Independent Subtasks +### Pattern 2: Independent Chained Runs -Execute subtasks where order doesn't matter: +Execute chained runs where order doesn't matter: ```python @app.task @@ -394,9 +392,9 @@ async def independent(): return combine(result_a, result_b) ``` -### Pattern 3: Subtasks in a Loop +### Pattern 3: Task Chaining in a Loop -Process a list by calling a subtask for each item: +Process a list by chaining a task run for each item: ```python @app.task @@ -408,9 +406,9 @@ async def batch_process(items: list): return results ``` -### Pattern 4: Nested Subtasks +### Pattern 4: Nested Task Chaining -Subtasks can call other subtasks: +Tasks can chain other tasks, which can chain additional tasks: ```python @app.task @@ -452,7 +450,7 @@ Make sure: - Build command is running correctly - Python version is 3.10 or higher -### Subtask calls not working +### Task chaining calls not working Make sure: - Your task function is marked `async` @@ -461,10 +459,10 @@ Make sure: ## Important Notes -- **Python-only**: Workflows are only supported in Python via render-sdk +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support render.yaml blueprint configuration - **Service Type**: Deploy as a Workflow service on Render (not Background Worker or Web Service) -- **Async Functions**: Tasks that call subtasks must be declared as `async` +- **Async Functions**: Tasks that chain other tasks must be declared as `async` ## Resources diff --git a/hello-world/main.py b/hello-world/main.py index c522110..3381a4f 100644 --- a/hello-world/main.py +++ b/hello-world/main.py @@ -4,8 +4,8 @@ This is the simplest possible workflow example to help you understand the basics. It demonstrates: - How to define a task using the @app.task decorator -- How to call a task as a subtask using await -- How to orchestrate multiple subtask calls +- How to chain task runs using await +- How to orchestrate multiple chained runs No complex business logic - just simple number operations to show the patterns clearly. """ @@ -50,19 +50,19 @@ def double(x: int) -> int: # ============================================================================ -# SUBTASK CALLING - Tasks calling other tasks +# TASK CHAINING - Tasks calling other tasks # ============================================================================ @app.task async def add_doubled_numbers(*args: int) -> dict: """ - Demonstrates calling a task as a subtask. + Demonstrates task chaining with await. - This task calls the 'double' task twice as a subtask using await. + This task chains two runs of the 'double' task using await. This is the fundamental pattern in Render Workflows - tasks can call other tasks to break down complex operations into simple, reusable pieces. - KEY PATTERN: Use 'await task_name(args)' to call a task as a subtask. + KEY PATTERN: Use 'await task_name(args)' to chain a task run. Args: *args: Two numbers to process @@ -76,16 +76,16 @@ async def add_doubled_numbers(*args: int) -> dict: a, b = args logger.info(f"[WORKFLOW] Starting: add_doubled_numbers({a}, {b})") - # SUBTASK CALL #1: Call 'double' as a subtask - # The 'await' keyword tells Render to execute this as a subtask - logger.info(f"[WORKFLOW] Calling subtask: double({a})") + # TASK CHAINING #1: Chain a run of 'double' + # The 'await' keyword tells Render to execute this as a chained run + logger.info(f"[WORKFLOW] Chaining run: double({a})") doubled_a = await double(a) - # SUBTASK CALL #2: Call 'double' as a subtask again - logger.info(f"[WORKFLOW] Calling subtask: double({b})") + # TASK CHAINING #2: Chain another run of 'double' + logger.info(f"[WORKFLOW] Chaining run: double({b})") doubled_b = await double(b) - # Now we have the results from both subtasks, let's combine them + # Now we have results from both chained runs, so combine them total = doubled_a + doubled_b result = { @@ -100,18 +100,18 @@ async def add_doubled_numbers(*args: int) -> dict: # ============================================================================ -# SUBTASK IN A LOOP - Processing multiple items +# TASK CHAINING IN A LOOP - Processing multiple items # ============================================================================ @app.task async def process_numbers(*numbers: int) -> dict: """ - Demonstrates calling a subtask in a loop. + Demonstrates chaining task runs in a loop. This pattern is useful when you need to process multiple items, - and each item requires the same operation (calling the same subtask). + and each item requires the same operation (chaining the same task). - KEY PATTERN: Call subtasks in a loop to process lists/batches. + KEY PATTERN: Chain task runs in a loop to process lists/batches. Args: numbers: List of numbers to process @@ -125,11 +125,11 @@ async def process_numbers(*numbers: int) -> dict: doubled_results = [] - # Process each number by calling 'double' as a subtask + # Process each number by chaining a run of 'double' for i, num in enumerate(numbers_list, 1): logger.info(f"[WORKFLOW] Processing item {i}/{len(numbers_list)}: {num}") - # SUBTASK CALL: Call 'double' as a subtask for each number + # TASK CHAINING: Chain 'double' for each number doubled = await double(num) doubled_results.append(doubled) @@ -137,7 +137,7 @@ async def process_numbers(*numbers: int) -> dict: "original_numbers": numbers_list, "doubled_numbers": doubled_results, "count": len(numbers_list), - "explanation": f"Processed {len(numbers_list)} numbers through the double subtask" + "explanation": f"Processed {len(numbers_list)} numbers by chaining runs of double" } logger.info(f"[WORKFLOW] Complete: {result}") @@ -145,19 +145,19 @@ async def process_numbers(*numbers: int) -> dict: # ============================================================================ -# MULTI-STEP WORKFLOW - Chaining multiple subtasks +# MULTI-STEP WORKFLOW - Chaining multiple task runs # ============================================================================ @app.task async def calculate_and_process(a: int, b: int, *more_numbers: int) -> dict: """ - Demonstrates a multi-step workflow that chains multiple subtasks. + Demonstrates a multi-step workflow that chains multiple task runs. This is a more complex example showing how you can build workflows - that call multiple different subtasks in sequence, using the results - from one subtask as input to the next. + that call multiple different tasks in sequence, using the results + from one chained run as input to the next. - KEY PATTERN: Chain multiple subtask calls to create complex workflows. + KEY PATTERN: Chain multiple task runs to create complex workflows. Args: a: First number diff --git a/hello-world/template.yaml b/hello-world/template.yaml index 61c8994..39f5edc 100644 --- a/hello-world/template.yaml +++ b/hello-world/template.yaml @@ -3,7 +3,7 @@ # after scaffolding. You can safely ignore it when using this example directly. name: Hello World -description: Simple task and subtask basics +description: Simple task and task-chaining basics default: true workflowsRoot: . @@ -14,7 +14,7 @@ renderStartCommand: python main.py nextSteps: - label: Enter your project directory command: cd {{dir}} - - label: Start your local task server + - label: Start your local workflow service command: render workflows dev -- {{startCommand}} hint: This runs your workflow service locally, allowing you to view and run tasks without deploying to Render. - label: Run a task locally (in another terminal) diff --git a/openai-agent/README.md b/openai-agent/README.md index b52f75c..e93c8f6 100644 --- a/openai-agent/README.md +++ b/openai-agent/README.md @@ -179,18 +179,16 @@ Task: `multi_turn_conversation` Input: ```json -{ - "messages": [ - "What is your return policy?", - "What is the status of order ORD-001?", - "Can you process a refund for that order? It arrived damaged." - ] -} +[ + "What is your return policy?", + "What is the status of order ORD-001?", + "Can you process a refund for that order? It arrived damaged." +] ``` This will process all messages in sequence, maintaining context between turns. -**Note:** The agent uses OpenAI GPT-4, so you'll see the AI making intelligent decisions about which tools to call based on the user's message. +**Note:** The agent uses the configured OpenAI model, so you'll see the AI decide which tools to call based on the user's message. ## Triggering via SDK @@ -210,24 +208,18 @@ task_run = await render.workflows.run_task( "conversation_history": [] } ) - -result = await task_run -print(f"Agent: {result.results['response']}") +print(f"Agent: {task_run.results['response']}") # Multi-turn conversation task_run = await render.workflows.run_task( "openai-agent-workflows/multi_turn_conversation", - { - "messages": [ - "What is your return policy?", - "What is the status of order ORD-001?", - "Can you process a refund for that order? It arrived damaged." - ] - } + [ + "What is your return policy?", + "What is the status of order ORD-001?", + "Can you process a refund for that order? It arrived damaged." + ] ) - -result = await task_run -for turn in result.results['turns']: +for turn in task_run.results['turns']: print(f"User: {turn['user']}") print(f"Agent: {turn['assistant']}") print(f"Tools used: {[t['tool'] for t in turn['tool_calls']]}") @@ -259,19 +251,19 @@ Searches the knowledge base for information. ## Task Descriptions -### Tool Tasks (Called as Subtasks) +### Tool Tasks (Called via Task Chaining) -**`get_order_status`**: Looks up order status. Called as a subtask when the agent needs order information. +**`get_order_status`**: Looks up order status. Chained when the agent needs order information. -**`process_refund`**: Processes refunds. Called as a subtask when the agent needs to issue a refund. +**`process_refund`**: Processes refunds. Chained when the agent needs to issue a refund. -**`search_knowledge_base`**: Searches for information. Called as a subtask when the agent needs help articles. +**`search_knowledge_base`**: Searches for information. Chained when the agent needs help articles. ### Orchestration Tasks **`call_llm_with_tools`**: Calls OpenAI API with tool definitions. The model decides whether to call tools or respond directly. -**`execute_tool`**: Dynamically executes a tool as a subtask based on the agent's decision: +**`execute_tool`**: Dynamically executes a tool via task chaining based on the agent's decision: ```python @app.task async def execute_tool(tool_name: str, arguments: dict) -> dict: @@ -282,26 +274,26 @@ async def execute_tool(tool_name: str, arguments: dict) -> dict: "search_knowledge_base": search_knowledge_base } - # SUBTASK CALL: Execute the appropriate tool function + # TASK CHAINING: Execute the appropriate tool function result = await tool_map[tool_name](**arguments) return result ``` -**`agent_turn`**: Executes a single conversation turn with nested subtask execution: +**`agent_turn`**: Executes a single conversation turn with nested task chaining: 1. `await call_llm_with_tools(...)` - Call LLM with user message 2. If tools requested: `await execute_tool(...)` for each tool (which then calls the actual tool task) 3. `await call_llm_with_tools(...)` again with tool results to generate final response -This demonstrates **nested subtask calling**: `agent_turn` → `execute_tool` → `get_order_status` (3 levels deep!). +This demonstrates **nested task chaining**: `agent_turn` → `execute_tool` → `get_order_status` (3 levels deep). **`multi_turn_conversation`**: Orchestrates multiple conversation turns: ```python for user_message in messages: - # SUBTASK CALL: Process each message through agent_turn + # TASK CHAINING: Process each message through agent_turn turn_result = await agent_turn(user_message, conversation_history) conversation_history = turn_result["conversation_history"] ``` -This demonstrates **calling subtasks in a loop** to maintain conversation state. +This demonstrates **task chaining in a loop** to maintain conversation state. ## Adding New Tools @@ -372,9 +364,9 @@ Agent: "We offer free shipping on orders over $50. Standard shipping takes 3-5 b ## Important Notes -- **Python-only**: Workflows are only supported in Python via render-sdk +- **SDK languages**: Workflows support Python and TypeScript; this repo's examples are Python. - **No Blueprint Support**: Workflows don't support render.yaml blueprint configuration - **OpenAI Costs**: Be mindful of API costs when running the agent frequently -- **Model Selection**: Currently uses GPT-4; can be changed to GPT-3.5-turbo for cost savings +- **Model Selection**: The default model is configurable in `main.py`; choose based on latency/cost/quality needs. - **Tool Safety**: In production, add authorization checks before executing sensitive tools - **Rate Limiting**: Consider implementing rate limits for production deployments diff --git a/openai-agent/main.py b/openai-agent/main.py index bfad56f..d1ccbb4 100644 --- a/openai-agent/main.py +++ b/openai-agent/main.py @@ -194,7 +194,7 @@ def search_knowledge_base(query: str) -> dict: @app.task async def call_llm_with_tools( - messages: list[dict], tools: list[dict], model: str = "gpt-4" + messages: list[dict], tools: list[dict], model: str = "gpt-5.4" ) -> dict: """ Call OpenAI with function/tool definitions. @@ -252,7 +252,7 @@ async def execute_tool(tool_name: str, arguments: dict) -> dict: """ Execute a tool function by name. - This demonstrates dynamic subtask execution based on agent decisions. + This demonstrates dynamic task chaining based on agent decisions. Args: tool_name: Name of the tool to execute @@ -275,7 +275,7 @@ async def execute_tool(tool_name: str, arguments: dict) -> dict: logger.error(f"[AGENT] Unknown tool: {tool_name}") return {"error": f"Unknown tool: {tool_name}"} - # Execute the appropriate tool as a subtask + # Chain a run of the selected tool task tool_function = tool_map[tool_name] try: diff --git a/openai-agent/template.yaml b/openai-agent/template.yaml index 17f5e0c..d555be1 100644 --- a/openai-agent/template.yaml +++ b/openai-agent/template.yaml @@ -13,12 +13,12 @@ renderStartCommand: python main.py nextSteps: - label: Enter your project directory command: cd {{dir}} - - label: Start your local task server + - label: Start your local workflow service command: render workflows dev -- {{startCommand}} hint: This runs your workflow service locally, allowing you to view and run tasks without deploying to Render. - label: Set your OpenAI API key command: export OPENAI_API_KEY=your-key-here - hint: Required for the agent to call GPT-4. + hint: Required for the agent to call the configured OpenAI model. - label: Run a conversation locally (in another terminal) command: "render workflows tasks start multi_turn_conversation --local --input='[\"What is the status of order ORD-001?\"]'" hint: The agent will use tool calling to look up the order and respond. From eef817374881748d6ac8102f635c1c7be9225b40 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Mon, 30 Mar 2026 14:46:27 -0700 Subject: [PATCH 2/3] Bump SDK min versions --- data-pipeline/requirements.txt | 2 +- etl-job/requirements.txt | 2 +- file-analyzer/api-service/requirements.txt | 2 +- file-analyzer/workflow-service/requirements.txt | 2 +- file-processing/requirements.txt | 2 +- hello-world/requirements.txt | 2 +- openai-agent/requirements.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data-pipeline/requirements.txt b/data-pipeline/requirements.txt index bd3abdf..04d96f4 100644 --- a/data-pipeline/requirements.txt +++ b/data-pipeline/requirements.txt @@ -1,2 +1,2 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 httpx>=0.27.0 diff --git a/etl-job/requirements.txt b/etl-job/requirements.txt index 65b4054..1bab464 100644 --- a/etl-job/requirements.txt +++ b/etl-job/requirements.txt @@ -1 +1 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 diff --git a/file-analyzer/api-service/requirements.txt b/file-analyzer/api-service/requirements.txt index 0e9a32b..09e769b 100644 --- a/file-analyzer/api-service/requirements.txt +++ b/file-analyzer/api-service/requirements.txt @@ -1,4 +1,4 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 fastapi>=0.110.0 uvicorn[standard]>=0.27.0 python-multipart>=0.0.9 diff --git a/file-analyzer/workflow-service/requirements.txt b/file-analyzer/workflow-service/requirements.txt index 65b4054..1bab464 100644 --- a/file-analyzer/workflow-service/requirements.txt +++ b/file-analyzer/workflow-service/requirements.txt @@ -1 +1 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 diff --git a/file-processing/requirements.txt b/file-processing/requirements.txt index 65b4054..1bab464 100644 --- a/file-processing/requirements.txt +++ b/file-processing/requirements.txt @@ -1 +1 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 diff --git a/hello-world/requirements.txt b/hello-world/requirements.txt index 65b4054..1bab464 100644 --- a/hello-world/requirements.txt +++ b/hello-world/requirements.txt @@ -1 +1 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 diff --git a/openai-agent/requirements.txt b/openai-agent/requirements.txt index 26f9bf4..fd02879 100644 --- a/openai-agent/requirements.txt +++ b/openai-agent/requirements.txt @@ -1,2 +1,2 @@ -render-sdk>=0.5.0 +render-sdk>=0.6.0 openai>=1.0.0 From 279e27976bebd66c2850be1d899ea9ce0b327bc5 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Mon, 30 Mar 2026 14:57:56 -0700 Subject: [PATCH 3/3] Two more readme tweaks --- file-analyzer/README.md | 4 +++- hello-world/README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/file-analyzer/README.md b/file-analyzer/README.md index 7a37ac3..9f29f9c 100644 --- a/file-analyzer/README.md +++ b/file-analyzer/README.md @@ -576,7 +576,9 @@ async def analyze_file( @app.post("/analyze") async def analyze_file(file: UploadFile): # Trigger analysis - task_run = await render.workflows.run_task(task_identifier, {"file_content": content}) + task_identifier = f"{os.getenv('WORKFLOW_SERVICE_SLUG')}/analyze_file" + file_content = (await file.read()).decode("utf-8") + task_run = await render.workflows.run_task(task_identifier, {"file_content": file_content}) result = task_run # Store in database diff --git a/hello-world/README.md b/hello-world/README.md index 5b4e9d9..5eb71d2 100644 --- a/hello-world/README.md +++ b/hello-world/README.md @@ -446,7 +446,7 @@ Make sure: ### Import errors Make sure: -- `requirements.txt` includes `render-sdk>=0.5.0` +- `requirements.txt` includes `render-sdk>=0.6.0` - Build command is running correctly - Python version is 3.10 or higher