Agentic Design Pattern: Service Mesh Pattern

Written by

in


    """
Pattern 122: Service Mesh Pattern

Description:
    Infrastructure layer for agent-to-agent communication with service
    discovery, load balancing, and observability.

Components:
    - Service registry
    - Load balancer
    - Circuit breaker
    - Observability layer

Use Cases:
    - Large-scale distributed agents
    - Microservices architecture
    - Reliable communication

LangChain Implementation:
    Implements service mesh concepts for agent communication management.
"""

import os
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
import random
from dotenv import load_dotenv

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

load_dotenv()


class ServiceStatus(Enum):
    """Service health status."""
    HEALTHY = "healthy"
    DEGRADED = "degraded"
    UNHEALTHY = "unhealthy"


@dataclass
class ServiceInstance:
    """Represents a service instance."""
    id: str
    name: str
    host: str
    port: int
    status: ServiceStatus = ServiceStatus.HEALTHY
    load: int = 0
    max_capacity: int = 100
    response_times: List[float] = None
    
    def __post_init__(self):
        if self.response_times is None:
            self.response_times = []


class ServiceRegistry:
    """Service discovery and registration."""
    
    def __init__(self):
        self.services: Dict[str, List[ServiceInstance]] = {}
        
    def register(self, service_name: str, instance: ServiceInstance):
        """Register a service instance."""
        if service_name not in self.services:
            self.services[service_name] = []
        self.services[service_name].append(instance)
        print(f"āœ“ Registered {service_name} instance: {instance.id}")
    
    def deregister(self, service_name: str, instance_id: str):
        """Deregister a service instance."""
        if service_name in self.services:
            self.services[service_name] = [
                i for i in self.services[service_name] if i.id != instance_id
            ]
            print(f"āœ“ Deregistered {service_name} instance: {instance_id}")
    
    def discover(self, service_name: str) -> List[ServiceInstance]:
        """Discover healthy instances of a service."""
        instances = self.services.get(service_name, [])
        return [i for i in instances if i.status == ServiceStatus.HEALTHY]
    
    def get_all_services(self) -> List[str]:
        """Get list of all registered services."""
        return list(self.services.keys())


class LoadBalancer:
    """Load balancing for service requests."""
    
    def __init__(self, strategy: str = "least_loaded"):
        self.strategy = strategy
        
    def select_instance(self, instances: List[ServiceInstance]) -> Optional[ServiceInstance]:
        """Select an instance based on load balancing strategy."""
        if not instances:
            return None
        
        if self.strategy == "round_robin":
            return random.choice(instances)
        
        elif self.strategy == "least_loaded":
            return min(instances, key=lambda i: i.load)
        
        elif self.strategy == "best_performance":
            # Choose instance with best average response time
            instances_with_times = [
                i for i in instances if i.response_times
            ]
            if instances_with_times:
                return min(
                    instances_with_times,
                    key=lambda i: sum(i.response_times) / len(i.response_times)
                )
            return instances[0]
        
        return instances[0]


class CircuitBreaker:
    """Circuit breaker for fault tolerance."""
    
    def __init__(self, failure_threshold: int = 3, timeout: int = 60):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.failures: Dict[str, int] = {}
        self.open_circuits: Dict[str, datetime] = {}
        
    def record_failure(self, service_id: str):
        """Record a service failure."""
        self.failures[service_id] = self.failures.get(service_id, 0) + 1
        
        if self.failures[service_id] >= self.failure_threshold:
            self.open_circuits[service_id] = datetime.now()
            print(f"⚠ Circuit opened for {service_id}")
    
    def record_success(self, service_id: str):
        """Record a successful call."""
        self.failures[service_id] = 0
        if service_id in self.open_circuits:
            del self.open_circuits[service_id]
            print(f"āœ“ Circuit closed for {service_id}")
    
    def is_open(self, service_id: str) -> bool:
        """Check if circuit is open."""
        if service_id in self.open_circuits:
            # Check if timeout has passed
            elapsed = (datetime.now() - self.open_circuits[service_id]).seconds
            if elapsed > self.timeout:
                del self.open_circuits[service_id]
                return False
            return True
        return False


class ServiceMesh:
    """Service mesh for agent communication."""
    
    def __init__(self, model_name: str = "gpt-4"):
        self.registry = ServiceRegistry()
        self.load_balancer = LoadBalancer(strategy="least_loaded")
        self.circuit_breaker = CircuitBreaker()
        self.llm = ChatOpenAI(model=model_name, temperature=0.3)
        self.request_log: List[Dict[str, Any]] = []
        
    def call_service(self, service_name: str, request_data: Dict[str, Any]) -> Dict[str, Any]:
        """Make a service call through the mesh."""
        print(f"\nšŸ”„ Calling service: {service_name}")
        
        # Discover instances
        instances = self.registry.discover(service_name)
        
        if not instances:
            print(f"  āœ— No healthy instances found")
            return {"error": "ServiceUnavailable"}
        
        # Select instance via load balancer
        instance = self.load_balancer.select_instance(instances)
        
        if not instance:
            return {"error": "NoInstanceAvailable"}
        
        # Check circuit breaker
        if self.circuit_breaker.is_open(instance.id):
            print(f"  ⚠ Circuit open for {instance.id}, trying another instance")
            # Try another instance
            instances.remove(instance)
            if instances:
                instance = self.load_balancer.select_instance(instances)
            else:
                return {"error": "AllCircuitsOpen"}
        
        print(f"  → Routing to instance: {instance.id} (load: {instance.load})")
        
        # Simulate service call
        try:
            start_time = datetime.now()
            
            # Simulate processing
            instance.load += 10
            response_time = random.uniform(0.1, 0.5)
            instance.response_times.append(response_time)
            
            # Simulate occasional failures
            if random.random()  str:
        """Generate observability report."""
        report_prompt = ChatPromptTemplate.from_messages([
            ("system", "Analyze service mesh metrics and provide insights."),
            ("user", """Request Log:
{log}

Service Registry:
{registry}

Provide:
1. Service health summary
2. Performance metrics
3. Bottlenecks identified
4. Recommendations""")
        ])
        
        log_summary = "\n".join([
            f"{r['service']}: {r['status']} ({r.get('response_time', 'N/A')}s)"
            for r in self.request_log[-20:]
        ])
        
        registry_summary = "\n".join([
            f"{name}: {len(instances)} instances"
            for name, instances in self.registry.services.items()
        ])
        
        chain = report_prompt | self.llm | StrOutputParser()
        return chain.invoke({
            "log": log_summary,
            "registry": registry_summary
        })


def demonstrate_service_mesh():
    """Demonstrate service mesh pattern."""
    print("=== Service Mesh Pattern ===\n")
    
    mesh = ServiceMesh()
    
    # Register services
    print("1. Registering Service Instances")
    print("-" * 50)
    
    # Data processing service
    mesh.registry.register("data-processor", ServiceInstance(
        id="dp-1", name="data-processor", host="10.0.1.1", port=8001
    ))
    mesh.registry.register("data-processor", ServiceInstance(
        id="dp-2", name="data-processor", host="10.0.1.2", port=8001
    ))
    mesh.registry.register("data-processor", ServiceInstance(
        id="dp-3", name="data-processor", host="10.0.1.3", port=8001
    ))
    
    # Analytics service
    mesh.registry.register("analytics", ServiceInstance(
        id="an-1", name="analytics", host="10.0.2.1", port=8002
    ))
    mesh.registry.register("analytics", ServiceInstance(
        id="an-2", name="analytics", host="10.0.2.2", port=8002
    ))
    
    print()
    
    # Make service calls
    print("2. Making Service Calls Through Mesh")
    print("-" * 50)
    
    for i in range(10):
        result = mesh.call_service("data-processor", {"data": f"request_{i}"})
    
    for i in range(5):
        result = mesh.call_service("analytics", {"query": f"query_{i}"})
    
    # Observability
    print("\n3. Service Mesh Observability")
    print("-" * 50)
    
    print(f"Total requests: {len(mesh.request_log)}")
    successful = sum(1 for r in mesh.request_log if r['status'] == 'success')
    print(f"Successful: {successful}")
    print(f"Failed: {len(mesh.request_log) - successful}")
    print(f"Success rate: {successful/len(mesh.request_log)*100:.1f}%")
    
    # Generate report
    print("\n4. Detailed Observability Report")
    print("-" * 50)
    report = mesh.get_observability_report()
    print(report)
    
    print("\n=== Summary ===")
    print("Service mesh demonstrated with:")
    print("- Service discovery and registration")
    print("- Load balancing (least loaded strategy)")
    print("- Circuit breaker pattern")
    print("- Request routing")
    print("- Health monitoring")
    print("- Observability and metrics")


if __name__ == "__main__":
    demonstrate_service_mesh()

    

Agentic Design Pattern: Service Mesh Pattern

Pattern 122: Service Mesh Pattern – Line-by-Line Explanation

Overview

This document provides a detailed line-by-line explanation of the Service Mesh Pattern pattern implementation.


Pattern Description

Pattern 122: Service Mesh Pattern

Description: Infrastructure layer for agent-to-agent communication with service discovery, load balancing, and observability.

Components: – Service registry – Load balancer – Circuit breaker – Observability layer

Use Cases: – Large-scale distributed agents – Microservices architecture – Reliable communication

LangChain Implementation: Implements service mesh concepts for agent communication management.


File Statistics

  • Total Lines: 355
  • Classes: 6
  • Functions: 1
  • Constants: 0

Module Documentation

"""
Pattern 122: Service Mesh Pattern

Description:
    Infrastructure layer for agent-to-agent communication with service
    discovery, load balancing, and observability.

Components:
    - Service registry
    - Load balancer
    - Circuit breaker
    - Observability layer

Use Cases:
    - Large-scale distributed agents
    - Microservices architecture
    - Reliable communication

LangChain Implementation:
    Implements service mesh concepts for agent communication management.
"""

Purpose: This module implements the Service Mesh Pattern pattern, providing a structured approach to agent design and implementation.


Import Statements

Standard Library Imports

import dataclasses.dataclass
import datetime.datetime
import enum.Enum
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import os
import random
import typing.Any
import typing.Dict
import typing.List
import typing.Optional

Purpose: Core Python functionality: – dataclasses.dataclass: Simplified class definitions with @dataclass – datetime.datetime: Date and time handling – enum.Enum: Enumeration support – langchain_core.output_parsers.StrOutputParser: Regular expression operations – langchain_core.prompts.ChatPromptTemplate: Regular expression operations – os: Environment variable access and system operations – random: Random number generation – typing.Any: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional) – typing.List: Type hints for better code documentation (List, Dict, Any, Optional) – typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)

LangChain Imports

from langchain_openai.ChatOpenAI

Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows

Other Imports

from dotenv.load_dotenv

Purpose: Additional dependencies for specialized functionality.


Classes

Class: ServiceStatus

Lines 38-42

Inherits from: Enum

Purpose:

Service health status.

Class: ServiceInstance

Lines 46-59

Purpose:

Represents a service instance.

Methods:

__post_init__(self)

Lines 57-59

Purpose: Post Init operation.

Parameters:


Class: ServiceRegistry

Lines 62-90

Purpose:

Service discovery and registration.

Methods:

__init__(self)

Lines 65-66

Purpose: Initialize the class instance with required parameters.

Parameters:

register(self, service_name, instance)

Lines 68-73

Purpose:

Register a service instance.

Parameters: – service_name: Service name – instance: Instance

deregister(self, service_name, instance_id)

Lines 75-81

Purpose:

Deregister a service instance.

Parameters: – service_name: Service name – instance_id: Instance id

discover(self, service_name)

Lines 83-86

Purpose:

Discover healthy instances of a service.

Parameters: – service_name: Service name

Returns: <ast.Subscript object at 0x000001A06D718710>

get_all_services(self)

Lines 88-90

Purpose:

Get list of all registered services.

Parameters:

Returns: <ast.Subscript object at 0x000001A06D71B2D0>


Class: LoadBalancer

Lines 93-122

Purpose:

Load balancing for service requests.

Methods:

__init__(self, strategy)

Lines 96-97

Purpose: Initialize the class instance with required parameters.

Parameters: – strategy: Strategy

select_instance(self, instances)

Lines 99-122

Purpose:

Select an instance based on load balancing strategy.

Parameters: – instances: Instances

Returns: <ast.Subscript object at 0x000001A06D8271D0>


Class: CircuitBreaker

Lines 125-158

Purpose:

Circuit breaker for fault tolerance.

Methods:

__init__(self, failure_threshold, timeout)

Lines 128-132

Purpose: Initialize the class instance with required parameters.

Parameters: – failure_threshold: Failure threshold – timeout: Timeout

record_failure(self, service_id)

Lines 134-140

Purpose:

Record a service failure.

Parameters: – service_id: Service id

record_success(self, service_id)

Lines 142-147

Purpose:

Record a successful call.

Parameters: – service_id: Service id

is_open(self, service_id)

Lines 149-158

Purpose:

Check if circuit is open.

Parameters: – service_id: Service id

Returns: bool


Class: ServiceMesh

Lines 161-283

Purpose:

Service mesh for agent communication.

Methods:

__init__(self, model_name)

Lines 164-169

Purpose: Initialize the class instance with required parameters.

Parameters: – model_name: Language model instance

call_service(self, service_name, request_data)

Lines 171-250

Purpose:

Make a service call through the mesh.

Parameters: – service_name: Service name – request_data: Request data

Returns: <ast.Subscript object at 0x000001A06D64B650>

get_observability_report(self)

Lines 252-283

Purpose:

Generate observability report.

Parameters:

Returns: str


Functions

demonstrate_service_mesh()

Lines 286-350

Purpose:

Demonstrate service mesh pattern.

Key Concepts Summary

Pattern Overview

The Service Mesh Pattern pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.

Core Components

  1. Agent Class: Main implementation of the pattern logic
  2. Prompt Engineering: Carefully crafted prompts to guide LLM behavior
  3. Tool Integration: Optional tools for extended functionality
  4. Memory Management: State handling across interactions
  5. Output Parsing: Structured response processing

When to Use

āœ… Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability

āŒ Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)


Best Practices Demonstrated

1. Environment Configuration

  • āœ… Use .env files for API keys
  • āœ… Never hardcode credentials
  • āœ… Validate environment variables on startup

2. Error Handling

  • āœ… Graceful degradation on failures
  • āœ… Informative error messages
  • āœ… Logging for debugging

3. Prompt Engineering

  • āœ… Clear, specific instructions
  • āœ… Structured output formats
  • āœ… Few-shot examples when beneficial

4. Code Organization

  • āœ… Separation of concerns (class-based design)
  • āœ… Type hints for clarity
  • āœ… Docstrings for documentation

5. Performance Optimization

  • āœ… Appropriate temperature settings
  • āœ… Token budget management
  • āœ… Caching where applicable

LangChain Components Used

  • ChatOpenAI: OpenAI model interface
  • Chains: Sequential component composition

Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development


Execution Flow

1. Initialize agent with configuration
   ↓
2. Load environment and dependencies
   ↓
3. Prepare prompt with input data
   ↓
4. Invoke LLM through LangChain
   ↓
5. Parse and validate response
   ↓
6. Return formatted result

Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation


Performance Considerations

Computational Cost

  • LLM Calls: Primary cost factor (based on tokens)
  • Latency: Depends on model and input size
  • Memory: Minimal for most patterns

Optimization Strategies

  1. Prompt Optimization: Minimize tokens while maintaining clarity
  2. Caching: Cache repeated queries/responses
  3. Model Selection: Use smaller models when appropriate
  4. Batch Processing: Group similar requests
  5. Streaming: For long responses, stream tokens

Cost Estimates (Approximate)

  • GPT-3.5-turbo: ~$0.002 per request
  • GPT-4: ~$0.03 per request
  • GPT-4-turbo: ~$0.01 per request

Actual costs vary based on input/output token counts


Common Pitfalls to Avoid

1. Prompt Design Issues

āŒ Vague or ambiguous instructions
āœ… Clear, specific, testable prompts

2. Error Handling

āŒ Ignoring API errors or rate limits
āœ… Implement retry logic and graceful degradation

3. Token Management

āŒ Exceeding model token limits
āœ… Truncate inputs, chunk processing

4. Security

āŒ Exposing API keys in code
āœ… Use environment variables

5. Testing

āŒ Only testing happy paths
āœ… Test edge cases, failures, malformed inputs

6. Cost Management

āŒ Unmonitored API usage
āœ… Implement budgets, alerts, and monitoring


Further Improvements

Production Enhancements

  1. Monitoring: Add logging, metrics, tracing
  2. Validation: Input/output schema validation
  3. Rate Limiting: Implement request throttling
  4. Caching: Redis or in-memory cache for responses
  5. Async Support: Non-blocking operations for scalability

Feature Extensions

  1. Multi-Model Support: Switch between providers
  2. Fallback Models: Graceful degradation strategy
  3. Custom Tools: Domain-specific function calling
  4. Advanced Memory: Vector stores for long-term memory
  5. Evaluation: Automated testing and benchmarking

Code Quality

  1. Testing: Unit tests, integration tests
  2. Documentation: API docs, usage examples
  3. Type Safety: Stricter type hints
  4. Configuration: Externalized config files

Related Patterns

  • Pattern 121
  • Pattern 123

Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…


Summary

The Service Mesh Pattern pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:

āœ… Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns

šŸŽÆ Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability

šŸ“š Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/


File: 122_service_mesh_pattern.py
Total Lines: 355
Pattern Category: AI Agent Design Pattern #122


Generated from actual implementation code
For educational and reference purposes

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *