> ## Documentation Index
> Fetch the complete documentation index at: https://docs.symbolica.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Errors

> Handle custom exceptions raised by agents

export const TypeScript = props => {
  const getLang = () => {
    let lang = null;
    try {
      const raw = window.localStorage.getItem('code');
      lang = raw;
      try {
        lang = JSON.parse(raw);
      } catch (_err) {}
    } catch (_err) {}
    return (lang || '').toLowerCase();
  };
  const lang = getLang();
  return <span class="when-lang when-typescript" style={{
    display: lang.includes('typescript') ? 'inline' : 'none'
  }}>{props.children}</span>;
};

export const Python = props => {
  let lang = null;
  try {
    const raw = window.localStorage.getItem('code');
    lang = raw;
    try {
      lang = JSON.parse(raw);
    } catch (_err) {}
  } catch (_err) {}
  lang = (lang || 'Python').toLowerCase();
  if (!['python', 'typescript'].some(l => lang.includes(l))) {
    lang = 'python';
  }
  return <span class="when-lang when-python" style={{
    display: lang.includes('python') ? 'inline' : 'none'
  }}>{props.children}</span>;
};

## Overview

When working with agents, you can design them to raise exceptions back to you when they encounter specific conditions. This allows you to handle domain-specific error cases gracefully.

**Agent errors** occur when the agent intentionally raises an exception. This could happen if:

* You told the agent to raise an exception in certain situations
* The agent believes your task to be impossible or contradictory
* The tools you provided to the agent are not working as expected

<Tip>
  Agent errors are different from [operational errors](/guides/operational-errors), which are platform-level failures like network issues, API timeouts, or sandbox errors.
</Tip>

## Basic Error Handling

There are three approaches to handling agent failures:

1. **Result types**: Allow the agent to return a union -- either the desired type, or an error type (for example <Python>`None`</Python><TypeScript>`null`</TypeScript>).
2. **Builtin exceptions**: The agent may throw any of the builtin exceptions in your language's runtime (e.g., `ValueError`, `TypeError`).
3. **Custom exceptions**: Define your own exception classes and the agent can raise them based on your documented error conditions.

### Using Result Types

The simplest approach is to allow the agent to return `None`/`null` when it cannot complete the task:

<CodeGroup>
  ```python Python theme={null}
  from agentica import agentic

  @agentic()
  async def extract_date(text: str) -> tuple[int, int, int] | None:
      """
      Extract date in YYYY-MM-DD format.
      Return None if no date found.
      """
      ...

  try:
      date = await extract_date(document)
      if date is None:
          # Handle missing date
          date = "unknown"
  except Exception as e:
      logger.error(f"Failed to extract date: {e}")
      # Fallback logic
  ```

  ```typescript TypeScript theme={null}
  import { agentic } from '@symbolica/agentica';

  async function extractDate(text: string): Promise<string | null> {
    return agentic<string | null>(
      `Extract date in YYYY-MM-DD format.
       Return null if no date found.`,
      { text }
    );
  }

  try {
    let date = await extractDate(document);
    if (date === null) {
      // Handle missing date
      date = "unknown";
    }
  } catch (e) {
    logger.error(`Failed to extract date: ${e}`);
    // Fallback logic
  }
  ```
</CodeGroup>

## Custom Exceptions

You can define your own exception classes and have the agent raise them when specific error conditions occur. This is useful for domain-specific error handling that goes beyond builtin exceptions.

**Best practices for custom exceptions:**

* Pass custom exceptions into the function or agent scope so they are available to raise
* Clearly document when each exception should be raised in your <Python>docstring</Python><TypeScript>JSDoc comments (`/** ... */`)</TypeScript>
* Use descriptive exception names that indicate the error condition
* Provide clear error messages that help diagnose the issue
* The agent can see and understand your documentation to know when to raise each exception

<CodeGroup>
  ```python Python expandable theme={null}
  from agentica import agentic

  # Define custom exceptions
  class InsufficientDataError(Exception):
      """Raised when the input data is incomplete or insufficient for analysis."""
      pass

  class DataQualityError(Exception):
      """Raised when data quality is too poor for reliable results."""
      pass

  class UnsupportedFormatError(Exception):
      """Raised when the data format is not supported."""
      pass

  @agentic(InsufficientDataError, DataQualityError, UnsupportedFormatError)
  async def analyze_dataset(data: str) -> dict:
      """
      Analyze the dataset and return insights.
      
      Raises:
          InsufficientDataError: If the dataset has fewer than 10 rows
          DataQualityError: If more than 50% of values are missing or invalid
          UnsupportedFormatError: If the data format is not CSV or JSON
          ValueError: If the data cannot be parsed
      
      Returns a dictionary with analysis results.
      """
      ...

  # Use with try/except
  try:
      results = await analyze_dataset(raw_data)
      print(f"Analysis complete: {results}")
  except InsufficientDataError as e:
      logger.warning(f"Not enough data: {e}")
      results = {"status": "insufficient_data", "message": str(e)}
  except DataQualityError as e:
      logger.warning(f"Poor data quality: {e}")
      results = perform_basic_analysis(raw_data)  # Fallback
  except UnsupportedFormatError as e:
      logger.error(f"Format not supported: {e}")
      results = {"status": "error", "message": "Please provide CSV or JSON"}
  except ValueError as e:
      logger.error(f"Parsing failed: {e}")
      raise
  ```

  ```typescript TypeScript expandable theme={null}
  import { agentic } from '@symbolica/agentica';

  // Define custom exceptions
  /**
   * Raised when the input data is incomplete or insufficient for analysis.
   */
  class InsufficientDataError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'InsufficientDataError';
    }
  }

  /**
   * Raised when data quality is too poor for reliable results.
   */
  class DataQualityError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'DataQualityError';
    }
  }

  /**
   * Raised when the data format is not supported.
   */
  class UnsupportedFormatError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'UnsupportedFormatError';
    }
  }

  interface AnalysisResult {
    status: string;
    insights?: string[];
    message?: string;
  }

  /**
   * Analyze the dataset and return insights.
   * 
   * @throws {InsufficientDataError} If the dataset has fewer than 10 rows
   * @throws {DataQualityError} If more than 50% of values are missing or invalid
   * @throws {UnsupportedFormatError} If the data format is not CSV or JSON
   * @throws {Error} If the data cannot be parsed
   */
  async function analyzeDataset(data: string): Promise<AnalysisResult> {
    return agentic<AnalysisResult>(
      `Analyze the dataset and return insights.
       
       Throw InsufficientDataError if the dataset has fewer than 10 rows.
       Throw DataQualityError if more than 50% of values are missing or invalid.
       Throw UnsupportedFormatError if the data format is not CSV or JSON.
       Throw Error if the data cannot be parsed.
       
       Return a dictionary with analysis results.`,
      { data, InsufficientDataError, DataQualityError, UnsupportedFormatError }
    );
  }

  // Use with try/catch
  try {
    const results = await analyzeDataset(rawData);
    console.log(`Analysis complete: ${results}`);
  } catch (e) {
    if (e instanceof InsufficientDataError) {
      logger.warn(`Not enough data: ${e}`);
      results = { status: "insufficient_data", message: e.message };
    } else if (e instanceof DataQualityError) {
      logger.warn(`Poor data quality: ${e}`);
      results = await performBasicAnalysis(rawData);  // Fallback
    } else if (e instanceof UnsupportedFormatError) {
      logger.error(`Format not supported: ${e}`);
      results = { status: "error", message: "Please provide CSV or JSON" };
    } else if (e instanceof Error) {
      logger.error(`Parsing failed: ${e}`);
      throw e;
    }
  }
  ```
</CodeGroup>

<Tip>
  The agent can see your <Python>docstrings</Python><TypeScript>JSDoc comments (`/** ... */`)</TypeScript>! Be specific about the conditions that should trigger each exception. The more precise your documentation, the more reliably the agent will raise the appropriate exception.
</Tip>

## Validation After Invocation

Type annotations help guide agents, and constrain the types the agent is capable of returning, but sometimes you need additional validation logic not expressible in the type system.

### Agent-Visible Validation

Validation logic may be realized in the type itself, such as during initialization of custom classes.
In these cases, the agent can see validation errors and self-correct when returning back to you.

<CodeGroup>
  ```python Python theme={null}
  from dataclasses import dataclass
  from agentica import agentic

  @dataclass
  class Price:
      amount: float
      currency: str

      def __post_init__(self):
          if self.amount < 0:
              raise ValueError("Price must be positive")
          if self.currency not in ['USD', 'EUR', 'GBP']:
              raise ValueError(f"Unsupported currency: {self.currency}")

  @agentic()
  async def extract_price(text: str) -> Price:
      """Extract price from text."""
      ...
  ```

  ```typescript TypeScript theme={null}
  class Price {
    amount: number;
    currency: string;

    constructor(amount: number, currency: string) {
      if (amount < 0)
        throw new Error("Price must be positive");
      if (!['USD', 'EUR', 'GBP'].includes(currency))
        throw new Error(`Unsupported currency: ${currency}`);
      this.amount = amount;
      this.currency = currency;
    }
  }

  async function extractPrice(text: string): Promise<Price> {
    return agentic("Extract price from text.", { text });
  }
  ```
</CodeGroup>

Here `Price` cannot be instantiated without satisfying the validation logic, and therefore cannot be returned by the agent until it is satisfied.

### Fine-Grained Validation

Off-the-shelf validation libraries such as Pydantic (Python) or Zod (TypeScript) may be used to integrate with existing validation logic or describe more complex validation requirements.

<Tabs>
  <Tab title="Python">
    Pydantic provides powerful declarative validation through field constraints and custom validators.

    **Field-level constraints** can specify numeric ranges, string lengths, and other basic requirements:

    ```python Python theme={null}
    from pydantic import BaseModel, Field
    from typing import Literal

    class ProductReview(BaseModel):
        rating: int = Field(ge=1, le=5, description="Rating from 1-5")
        sentiment: Literal["positive", "negative", "neutral"]
        categories: list[str] = Field(min_length=1, max_length=5)
        summary: str = Field(min_length=10, max_length=200)
    ```

    Not only do these fields provide basic validation, but they also provide excellent documentation for the agent.

    **Custom field validators** handle complex logic on individual fields using `@field_validator`:

    ```python Python theme={null}
    from pydantic import field_validator

    class ProductReview(BaseModel):
        # ... fields as above ...

        @field_validator('categories')
        @classmethod
        def validate_categories(cls, v: list[str]) -> list[str]:
            allowed = {'quality', 'price', 'service', 'delivery', 'packaging'}
            for category in v:
                if category not in allowed:
                    raise ValueError(f"Invalid category: {category}")
            return v
    ```

    **Cross-field validation** uses `@model_validator` to validate relationships between fields:

    ```python Python theme={null}
    from pydantic import model_validator

    class ProductReview(BaseModel):
        # ... fields and field_validator as above ...

        @model_validator(mode='after')
        def validate_sentiment_matches_rating(self) -> 'ProductReview':
            if self.rating >= 4 and self.sentiment == 'negative':
                raise ValueError("High rating inconsistent with negative sentiment")
            if self.rating <= 2 and self.sentiment == 'positive':
                raise ValueError("Low rating inconsistent with positive sentiment")
            return self
    ```

    The agent sees Pydantic validation errors and adjusts its output to satisfy all constraints:

    ```python Python theme={null}
    from agentica import agentic

    @agentic()
    async def analyze_review(review_text: str) -> ProductReview:
        """Analyze this product review and extract structured information."""
        ...

    review = await analyze_review("Great product! Fast shipping and excellent quality. 5 stars!")
    # All constraints are guaranteed to be satisfied
    ```
  </Tab>

  <Tab title="TypeScript">
    Zod provides schema-based runtime validation similar to Pydantic.

    **Define the schema** with field types and constraints:

    ```typescript TypeScript theme={null}
    import { z } from 'zod';

    const ProductReviewSchema = z.object({
      rating: z.number().int().min(1).max(5),
      sentiment: z.enum(['positive', 'negative', 'neutral']),
      categories: z.array(
        z.enum(['quality', 'price', 'service', 'delivery', 'packaging'])
      ).min(1).max(5),
      summary: z.string().min(10).max(200)
    });
    ```

    **Cross-field validation** uses `.refine()` to validate relationships:

    ```typescript TypeScript theme={null}
    const ProductReviewSchema = z.object({
      // ... fields as above ...
    }).refine(data => {
      // Validate sentiment matches rating
      if (data.rating >= 4 && data.sentiment === 'negative') return false;
      if (data.rating <= 2 && data.sentiment === 'positive') return false;
      return true;
    }, {
      message: "Sentiment must be consistent with rating"
    });
    ```

    **Type inference and validation** -- Zod infers TypeScript types from schemas and validates at runtime:

    ```typescript TypeScript theme={null}
    import { agentic } from '@symbolica/agentica';

    type ProductReview = z.infer<typeof ProductReviewSchema>;

    async function analyzeReview(reviewText: string): Promise<ProductReview> {
      const result = await agentic<ProductReview>(
        "Analyze this product review and extract structured information.",
        { reviewText }
      );

      // Validate with Zod - throws if validation fails
      return ProductReviewSchema.parse(result);
    }

    const review = await analyzeReview("Great product! Fast shipping and excellent quality. 5 stars!");
    // All constraints are guaranteed to be satisfied
    ```
  </Tab>
</Tabs>

## Graceful Degradation

You may encounter edge cases where a task is genuinely impossible (missing required data, contradictory constraints, etc.). In these cases, you can design your application to degrade gracefully, maintaining basic functionality even when an agent cannot complete the full task.

<Tip>
  Frequent fallbacks indicate an opportunity to refine your approach -- adjusting prompts, choosing a different model, or providing more context. Use fallback patterns to handle genuine edge cases.
</Tip>

### Fallback to Simpler Logic

If a complex agent operation fails, fall back to simpler approaches. This example shows agents generating database migrations, with fallbacks to safer manual approaches.

First, define your agent-backed function that attempts the complex task:

<CodeGroup>
  ```python Python theme={null}
  from agentica import agentic

  @agentic()
  async def generate_migration(schema_old: dict, schema_new: dict) -> str:
      """
      Generate a SQL migration script to transform the old schema to the new one.
      Handle complex cases like:
      - Column renames (detect via similarity, not just adds/drops)
      - Data type changes with appropriate conversions
      - Foreign key updates
      - Index optimizations
      Return valid SQL that preserves data.
      """
      ...
  ```

  ```typescript TypeScript theme={null}
  import { agentic } from '@symbolica/agentica';

  async function generateMigration(schemaOld: object, schemaNew: object): Promise<string> {
    return agentic<string>(
      `Generate a SQL migration script to transform the old schema to the new one.
       Handle column renames, type changes, foreign keys, and index optimizations.
       Return valid SQL that preserves data.`,
      { schemaOld, schemaNew }
    );
  }
  ```
</CodeGroup>

Then create a simpler, safer fallback that generates a basic migration:

<CodeGroup>
  ```python Python theme={null}
  def generate_basic_migration(schema_old: dict, schema_new: dict) -> str:
      """Generate simple ADD/DROP column migration without smart renames."""
      old_cols = set(schema_old.get('columns', []))
      new_cols = set(schema_new.get('columns', []))

      added = new_cols - old_cols
      dropped = old_cols - new_cols

      sql_lines = []
      table = schema_new.get('table_name', 'table')

      for col in dropped:
          sql_lines.append(f"ALTER TABLE {table} DROP COLUMN {col};")
      for col in added:
          sql_lines.append(f"ALTER TABLE {table} ADD COLUMN {col} VARCHAR(255);")

      return "\n".join(sql_lines) if sql_lines else "-- No changes detected"
  ```

  ```typescript TypeScript theme={null}
  function generateBasicMigration(schemaOld: any, schemaNew: any): string {
    const oldCols = new Set(schemaOld.columns || []);
    const newCols = new Set(schemaNew.columns || []);

    const added = [...newCols].filter(col => !oldCols.has(col));
    const dropped = [...oldCols].filter(col => !newCols.has(col));

    const sqlLines: string[] = [];
    const table = schemaNew.tableName || 'table';

    for (const col of dropped) {
      sqlLines.push(`ALTER TABLE ${table} DROP COLUMN ${col};`);
    }
    for (const col of added) {
      sqlLines.push(`ALTER TABLE ${table} ADD COLUMN ${col} VARCHAR(255);`);
    }

    return sqlLines.length > 0 ? sqlLines.join('\n') : '-- No changes detected';
  }
  ```
</CodeGroup>

Attempt the smart migration first, falling back to basic if it fails:

<CodeGroup>
  ```python Python theme={null}
  async def create_migration(schema_old: dict, schema_new: dict) -> str:
      """Generate agent-backed migration, fallback to basic diff."""
      try:
          migration = await generate_migration(schema_old, schema_new)
          logger.info("Generated smart migration with an agent")
          return migration
      except Exception as e:
          logger.warning(f"Agent-backed migration generation failed: {e}, using basic diff")
          return generate_basic_migration(schema_old, schema_new)
  ```

  ```typescript TypeScript theme={null}
  async function createMigration(schemaOld: object, schemaNew: object): Promise<string> {
    try {
      const migration = await generateMigration(schemaOld, schemaNew);
      logger.info("Generated smart migration with an agent");
      return migration;
    } catch (e) {
      logger.warn(`Agent-backed migration generation failed: ${e}, using basic diff`);
      return generateBasicMigration(schemaOld, schemaNew);
    }
  }
  ```
</CodeGroup>

### Partial Success Handling

Sometimes an agent-backed operation can partially succeed. Instead of treating this as complete failure, design your workflow to continue with whatever succeeded. This example shows an agent refactoring code across multiple files.

Define a workflow where agents process multiple items, tracking successes and failures:

<CodeGroup>
  ```python Python expandable theme={null}
  from dataclasses import dataclass
  from agentica import agentic

  @dataclass
  class RefactorResult:
      file_path: str
      success: bool
      updated_code: str | None
      error: str | None

  @agentic()
  async def refactor_file(code: str, instruction: str) -> str:
      """
      Refactor the given code according to the instruction.
      Preserve functionality while improving code quality.
      """
      ...

  async def refactor_codebase(files: dict[str, str], instruction: str) -> list[RefactorResult]:
      """Refactor multiple files, continuing even if some fail."""
      results = []

      for file_path, code in files.items():
          try:
              updated = await refactor_file(code, instruction)
              results.append(RefactorResult(
                  file_path=file_path,
                  success=True,
                  updated_code=updated,
                  error=None
              ))
              logger.info(f"Successfully refactored {file_path}")
          except Exception as e:
              results.append(RefactorResult(
                  file_path=file_path,
                  success=False,
                  updated_code=None,
                  error=str(e)
              ))
              logger.warning(f"Failed to refactor {file_path}: {e}")

      return results
  ```

  ```typescript TypeScript expandable theme={null}
  import { agentic } from '@symbolica/agentica';

  interface RefactorResult {
    filePath: string;
    success: boolean;
    updatedCode?: string;
    error?: string;
  }

  async function refactorFile(code: string, instruction: string): Promise<string> {
    return agentic<string>(
      `Refactor the given code according to the instruction.
       Preserve functionality while improving code quality.`,
      { code, instruction }
    );
  }

  async function refactorCodebase(
    files: Record<string, string>,
    instruction: string
  ): Promise<RefactorResult[]> {
    const results: RefactorResult[] = [];

    for (const [filePath, code] of Object.entries(files)) {
      try {
        const updated = await refactorFile(code, instruction);
        results.push({
          filePath,
          success: true,
          updatedCode: updated
        });
        logger.info(`Successfully refactored ${filePath}`);
      } catch (e) {
        results.push({
          filePath,
          success: false,
          error: String(e)
        });
        logger.warn(`Failed to refactor ${filePath}: ${e}`);
      }
    }

    return results;
  }
  ```
</CodeGroup>

Then act on partial results, applying successful changes while reporting failures:

<CodeGroup>
  ```python Python expandable theme={null}
  async def apply_refactoring(files: dict[str, str], instruction: str) -> dict:
      """Apply refactoring and report on partial success."""
      results = await refactor_codebase(files, instruction)

      successful = [r for r in results if r.success]
      failed = [r for r in results if not r.success]

      # Write successful refactorings
      for result in successful:
          with open(result.file_path, 'w') as f:
              f.write(result.updated_code)

      # Log summary
      if len(successful) == len(results):
          logger.info(f"All {len(results)} files refactored successfully")
      elif len(successful) > 0:
          logger.warning(
              f"Partial success: {len(successful)}/{len(results)} files refactored. "
              f"Failed: {[r.file_path for r in failed]}"
          )
      else:
          logger.error("All refactoring attempts failed")

      return {
          "total": len(results),
          "successful": len(successful),
          "failed": len(failed),
          "failed_files": [r.file_path for r in failed]
      }
  ```

  ```typescript TypeScript expandable theme={null}
  import { writeFile } from 'fs/promises';

  async function applyRefactoring(
    files: Record<string, string>,
    instruction: string
  ): Promise<{
    total: number;
    successful: number;
    failed: number;
    failedFiles: string[];
  }> {
    const results = await refactorCodebase(files, instruction);

    const successful = results.filter(r => r.success);
    const failed = results.filter(r => !r.success);

    // Write successful refactorings
    await Promise.all(
      successful.map(result =>
        writeFile(result.filePath, result.updatedCode!)
      )
    );

    // Log summary
    if (successful.length === results.length) {
      logger.info(`All ${results.length} files refactored successfully`);
    } else if (successful.length > 0) {
      logger.warn(
        `Partial success: ${successful.length}/${results.length} files refactored. ` +
        `Failed: ${failed.map(r => r.filePath).join(', ')}`
      );
    } else {
      logger.error("All refactoring attempts failed");
    }

    return {
      total: results.length,
      successful: successful.length,
      failed: failed.length,
      failedFiles: failed.map(r => r.filePath)
    };
  }
  ```
</CodeGroup>

### Multi-Level Fallback Chain

For critical operations, implement progressively simpler agentic tasks as fallbacks. When a task requires data that isn't available or constraints that can't be met, agents may raise an error. Simpler fallback tasks with relaxed requirements are more likely to succeed.

Define multiple agentic approaches with decreasing strictness:

<CodeGroup>
  ```python Python theme={null}
  from dataclasses import dataclass
  from agentica import agentic

  @dataclass
  class ShippingAddress:
      name: str
      street: str
      city: str
      state: str
      zip_code: str
      country: str

  @agentic()
  async def extract_validated_address(text: str) -> ShippingAddress:
      """
      Extract complete shipping address with ALL required fields.
      Fields: name, street, city, state, zip_code, country
      Raise an error if ANY field is missing from the text.
      """
      ...

  @agentic()
  async def extract_partial_address(text: str) -> ShippingAddress | None:
      """
      Extract shipping address. Return None if no address is found.
      Fill in 'unknown' for any missing fields.
      """
      ...

  @agentic()
  async def extract_location_mentions(text: str) -> str:
      """
      Extract any location information mentioned (city, state, country, etc).
      Return as a simple string description of what was found.
      """
      ...
  ```

  ```typescript TypeScript theme={null}
  import { agentic } from '@symbolica/agentica';

  interface ShippingAddress {
    name: string;
    street: string;
    city: string;
    state: string;
    zipCode: string;
    country: string;
  }

  async function extractValidatedAddress(text: string): Promise<ShippingAddress> {
    return agentic<ShippingAddress>(
      `Extract complete shipping address with ALL required fields:
       name, street, city, state, zipCode, country.
       Raise an error if ANY field is missing from the text.`,
      { text }
    );
  }

  async function extractPartialAddress(text: string): Promise<ShippingAddress | null> {
    return agentic<ShippingAddress | null>(
      `Extract shipping address. Return null if no address is found.
       Fill in 'unknown' for any missing fields.`,
      { text }
    );
  }

  async function extractLocationMentions(text: string): Promise<string> {
    return agentic<string>(
      `Extract any location information mentioned (city, state, country, etc).
       Return as a simple string description of what was found.`,
      { text }
    );
  }
  ```
</CodeGroup>

Attempt each approach, falling back when required data is missing:

<CodeGroup>
  ```python Python theme={null}
  async def process_shipping_info(text: str) -> dict:
      """Extract shipping information with fallback levels."""

      # Try complete validated extraction
      try:
          address = await extract_validated_address(text)
          logger.info("Complete shipping address extracted")
          return {"address": address, "completeness": "complete"}
      except Exception as e:
          logger.warning(f"Complete address extraction failed: {e}")

      # Try partial extraction
      try:
          address = await extract_partial_address(text)
          if address:
              logger.warning("Partial address extracted, manual review needed")
              return {"address": address, "completeness": "partial"}
          else:
              logger.warning("No structured address found")
      except Exception as e:
          logger.error(f"Partial address extraction failed: {e}")

      # Final fallback - just get location mentions
      location_text = await extract_location_mentions(text)
      logger.error("Could not extract structured address, only location mentions")
      return {"address": None, "location_text": location_text, "completeness": "minimal"}
  ```

  ```typescript TypeScript theme={null}
  async function processShippingInfo(text: string): Promise<{
    address?: ShippingAddress | null;
    locationText?: string;
    completeness: string;
  }> {
    // Try complete validated extraction
    try {
      const address = await extractValidatedAddress(text);
      logger.info("Complete shipping address extracted");
      return { address, completeness: "complete" };
    } catch (e) {
      logger.warn(`Complete address extraction failed: ${e}`);
    }

    // Try partial extraction
    try {
      const address = await extractPartialAddress(text);
      if (address) {
        logger.warn("Partial address extracted, manual review needed");
        return { address, completeness: "partial" };
      } else {
        logger.warn("No structured address found");
      }
    } catch (e) {
      logger.error(`Partial address extraction failed: ${e}`);
    }

    // Final fallback - just get location mentions
    const locationText = await extractLocationMentions(text);
    logger.error("Could not extract structured address, only location mentions");
    return { address: null, locationText, completeness: "minimal" };
  }
  ```
</CodeGroup>

When the text only mentions "Send it to John in Seattle", the validated extraction fails (missing street, state, zip, country), but the minimal extraction can still return "Seattle" as the location.

## Custom Exceptions

You can define your own exception classes and have the agent raise them when specific error conditions occur. The agent can raise these exceptions from within its execution environment, and they are automatically bubbled back up to your code.

### Defining Custom Exceptions

Custom exceptions are useful for domain-specific error handling. To use them:

1. Define your custom exception classes
2. Pass them into the <Python>`@agentic()` decorator</Python><TypeScript>agentic function's scope object</TypeScript>
3. Document when each exception should be raised so the agent knows when to use them

<CodeGroup>
  ```python Python expandable theme={null}
  from dataclasses import dataclass, field
  from enum import Enum
  from time import time

  from agentica import agentic

  class TaskCategory(Enum):
      BUSINESS = "business"
      PERSONAL = "personal"
      FREELANCE = "freelance"

  @dataclass
  class Task:
      user: str
      category: TaskCategory
      description: str
      time_created: float = field(default_factory=time)

  class TaskTooComplicatedError(Exception):
      """Raised when a task is too complex to complete automatically."""
      pass

  class InsufficientPermissionsError(Exception):
      """Raised when the user lacks permissions for the requested task."""
      pass

  @agentic(TaskTooComplicatedError, InsufficientPermissionsError)
  async def perform_task(task: Task) -> str:
      """
      Perform the task and return the result.

      Raises:
          TaskTooComplicatedError: If the task requires human intervention
          InsufficientPermissionsError: If the user lacks necessary permissions
          ValueError: If the task description is empty or invalid

      Returns:
          A description of the completed task.
      """
      ...

  # Usage with error handling
  try:
      result = await perform_task(task)
      print(f"Task completed: {result}")
  except TaskTooComplicatedError as e:
      print(f"Manual intervention required: {e}")
      # Escalate to human
      assign_to_human(task)
  except InsufficientPermissionsError as e:
      print(f"Permission denied: {e}")
      # Request additional permissions
      request_permissions(task.user, task.category)
  except ValueError as e:
      print(f"Invalid task: {e}")
  ```

  ```typescript TypeScript expandable theme={null}
  import { agentic } from '@symbolica/agentica';

  enum TaskCategory {
    BUSINESS = "business",
    PERSONAL = "personal",
    FREELANCE = "freelance"
  }

  interface Task {
    user: string;
    category: TaskCategory;
    description: string;
    timeCreated: number;
  }

  /**
   * Raised when a task is too complex to complete automatically.
   */
  class TaskTooComplicatedError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'TaskTooComplicatedError';
    }
  }

  /**
   * Raised when the user lacks permissions for the requested task.
   */
  class InsufficientPermissionsError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'InsufficientPermissionsError';
    }
  }

  /**
   * Perform the task and return the result.
   *
   * @throws {TaskTooComplicatedError} If the task requires human intervention
   * @throws {InsufficientPermissionsError} If the user lacks necessary permissions
   * @throws {Error} If the task description is empty or invalid
   */
  async function performTask(task: Task): Promise<string> {
    return agentic<string>(
      `Perform the task and return the result.

       Throw TaskTooComplicatedError if the task requires human intervention.
       Throw InsufficientPermissionsError if the user lacks necessary permissions.
       Throw Error if the task description is empty or invalid.

       Return a description of the completed task.`,
      { task, TaskTooComplicatedError, InsufficientPermissionsError }
    );
  }

  // Usage with error handling
  try {
    const result = await performTask(task);
    console.log(`Task completed: ${result}`);
  } catch (e) {
    if (e instanceof TaskTooComplicatedError) {
      console.log(`Manual intervention required: ${e}`);
      // Escalate to human
      assignToHuman(task);
    } else if (e instanceof InsufficientPermissionsError) {
      console.log(`Permission denied: ${e}`);
      // Request additional permissions
      requestPermissions(task.user, task.category);
    } else if (e instanceof Error) {
      console.log(`Invalid task: ${e}`);
    }
  }
  ```
</CodeGroup>

<Tip>
  The agent can see your <Python>docstrings</Python><TypeScript>JSDoc comments (`/** ... */`)</TypeScript>! Be specific about when each exception should be raised. The agent uses this documentation to understand when to throw each exception type.
</Tip>

For comprehensive error handling patterns and best practices, see the [Error Handling Guide](/guides/operational-errors).

## Next Steps

<CardGroup cols={2}>
  <Card title="Operational Errors" icon="triangle-exclamation" href="/guides/operational-errors">
    Handle platform-level errors (network, API, sandbox)
  </Card>

  <Card title="Best Practices" icon="star" href="/guides/best-practices">
    Production deployment best practices
  </Card>

  <Card title="Human-in-the-Loop" icon="hand" href="/guides/human-in-the-loop">
    Add human oversight to your agents
  </Card>

  <Card title="Advanced" icon="hat-wizard" href="/guides/prompting">
    Custom system prompts and templating
  </Card>
</CardGroup>
