梦兽编程
AI_SUITE

End-to-End Project: Complete Flow from Requirements to Deployment

Complete project practice through an intelligent customer service system—showing full process from requirements analysis, architecture design, code implementation to production deployment.

Theory ultimately needs to land in production practice. This chapter uses a complete intelligent customer service system project to tie together all the knowledge from the previous nine modules and show the full engineering flow from requirements analysis, architecture design, and code implementation to production deployment. The design of this project is based on real enterprise customer-service system transformation experience; the architectural decisions, technology choices, and pitfall avoidance have all been validated in production.

Project Background and Requirements Analysis

Business Scenario

An e-commerce platform processes 500,000+ orders per day, and its customer service team handles about 80,000 user inquiries daily. Traditional human customer service faces the following pain points:

  1. Response latency: During peak hours, users wait an average of more than 5 minutes, leading to rising complaint rates.
  2. Scattered knowledge: The customer-service knowledge base is spread across five different systems, making manual retrieval inefficient.
  3. Multi-language support: The platform covers Southeast Asian markets and needs to support Chinese, English, Thai, and Vietnamese.
  4. Complex business: Inquiries involve five major business domains—orders, logistics, after-sales, promotions, and payments—each with independent internal systems.
  5. Peak pressure: During big promotions, inquiry volume can reach ten times the usual amount, and manual scaling is costly.

Functional Requirements

Based on the above pain points, the intelligent customer service system must satisfy:

RequirementDescriptionPriority
Automatic Q&AAfter a user asks a question, the Agent understands the intent and answers automaticallyP0
Multi-turn conversationSupports context memory and can ask follow-up questions and clarifyP0
Multi-domain routingRoutes questions to the corresponding business Agent based on typeP0
Internal system integrationReal-time query of orders, logistics, inventory, and other internal dataP0
Streaming outputAnswers appear in a typewriter effect in real time to improve user experienceP1
Human handoffSeamlessly transfer to a human agent when confidence is below a thresholdP1
Multi-language supportAutomatically detect the user’s language and reply in the same languageP1
Data analyticsCollect conversation data for continuous optimization and reportingP2

Non-Functional Requirements

MetricTargetDescription
Availability99.9%Annual downtime < 8.76 hours
P95 response latency< 3 sFrom user sending a message to first token returned
P99 response latency< 8 sTolerate a small amount of long-tail latency
Concurrent throughput5,000 QPSPeak capacity during big promotions
Answer accuracy> 85%Based on manual sampling evaluation
Data securityLevel 3 protectionUser conversation data encrypted at rest

System Architecture Design

Overall Architecture

The system uses a layered + microservices architecture. Core components include:

┌─────────────────────────────────────────────────────────────┐
│                        Access Layer                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │   Web Chat   │  │  Mini-App CS  │  │   API Gateway │    │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘    │
└─────────┼─────────────────┼─────────────────┼──────────────┘
          │                 │                 │
          └─────────────────┼─────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                    Gateway & Routing Layer                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Kong / Nginx (rate limiting, auth)        │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Core Service Layer                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Conversation Orchestration Service       │   │
│  │                    (Go + ADK)                         │   │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │   │
│  │  │ Router   │  │  Memory  │  │  Session State   │  │   │
│  │  │ Agent    │  │  Manager │  │  Machine         │  │   │
│  │  └──────────┘  └──────────┘  └──────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│                            ↓                                │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │  Order Agent │  │ Logistics Agent│  │ After-Sales  │    │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘    │
│         │                 │                 │               │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌──────┴───────┐    │
│  │ Order Service│  │ Logistics Svc│  │ After-Sales  │    │
│  │ API          │  │ API          │  │ API          │    │
│  └──────────────┘  └──────────────┘  └──────────────┘    │
└─────────────────────────────────────────────────────────────┘
          │                 │                 │
          └─────────────────┼─────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Infrastructure Layer                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │  Redis   │  │PostgreSQL│  │  Kafka   │  │Prometheus│   │
│  │(session  │  │(persist) │  │(events)  │  │(monitor) │   │
│  │  cache)  │  │          │  │          │  │          │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────┘

Key Architecture Decisions

1. Why Router + Sub-Agent instead of a single large Agent?

Problems with a single large Agent:

  • The context window is squeezed by knowledge from different domains, reducing depth in each domain.
  • When the number of Tools grows too large, the model’s Tool-selection accuracy drops significantly (we found accuracy fell from 92% to 78% when exceeding 15 Tools).
  • It is impossible to optimize prompts and model parameters for each business domain.
  • Single point of failure: a Tool failure in one domain can affect the entire system.

Router + Sub-Agent advantages:

  • Each Sub-Agent focuses on one domain with more precise prompts.
  • Can be deployed and scaled independently.
  • Fault isolation: a logistics Agent failure does not affect order queries.

2. Why Go + ADK instead of the Python ecosystem?

  • Performance: Go’s concurrency model (goroutines) is more resource-efficient in high-concurrency scenarios.
  • Deployment: Go compiles to a single binary, container images are small (usually < 50 MB), and startup is fast (< 100 ms).
  • Type safety: The static type system significantly reduces runtime errors in large projects.
  • Team skill stack: The team already had Go microservices experience; a unified stack lowers maintenance costs.
  • A2A protocol: ADK’s A2A protocol supports cross-language Agent communication, so Python-ecosystem Agents can be integrated when necessary.

3. Session State Management Strategy

type SessionStore interface {
    // Short-term cache: Redis, TTL 30 minutes
    GetShortTerm(ctx context.Context, sessionID string) (*SessionState, error)
    SetShortTerm(ctx context.Context, sessionID string, state *SessionState, ttl time.Duration) error

    // Long-term persistence: PostgreSQL, used for data analytics
    Persist(ctx context.Context, sessionID string, history []Message) error
}

type SessionState struct {
    SessionID    string
    UserID       string
    Language     string
    CurrentAgent string  // currently active Sub-Agent
    Context      map[string]interface{}  // cross-turn context
    CreatedAt    time.Time
    LastActive   time.Time
}

Core Code Implementation

1. Project Initialization and Dependency Management

# Create project
go mod init github.com/yourcompany/customer-service-agent

# Core dependencies
go get google.golang.org/adk@latest
go get github.com/redis/go-redis/v9
go get github.com/lib/pq
go get github.com/prometheus/client_golang/prometheus

2. Internal API Tool Implementation (with Circuit Breaker and Cache)

package tools

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "github.com/sony/gobreaker"
)

// OrderServiceClient wraps order-service API calls
type OrderServiceClient struct {
    baseURL    string
    httpClient *http.Client
    breaker    *gobreaker.CircuitBreaker
    cache      *Cache  // local cache for hot data
}

func NewOrderServiceClient(baseURL string) *OrderServiceClient {
    return &OrderServiceClient{
        baseURL: baseURL,
        httpClient: &http.Client{
            Timeout: 3 * time.Second,
        },
        breaker: gobreaker.NewCircuitBreaker(gobreaker.Settings{
            Name:        "order-service",
            MaxRequests: 100,
            Interval:    10 * time.Second,
            Timeout:     5 * time.Second,
            ReadyToTrip: func(counts gobreaker.Counts) bool {
                failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
                return counts.Requests >= 10 && failureRatio >= 0.5
            },
            OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
                log.Printf("Circuit breaker state changed: %s %s -> %s", name, from, to)
            },
        }),
        cache: NewCache(5 * time.Minute),
    }
}

type OrderInfo struct {
    OrderID   string      `json:"order_id"`
    Status    string      `json:"status"`
    Amount    float64     `json:"amount"`
    CreatedAt time.Time   `json:"created_at"`
    Items     []OrderItem `json:"items"`
}

type OrderItem struct {
    ProductID   string  `json:"product_id"`
    ProductName string  `json:"product_name"`
    Quantity    int     `json:"quantity"`
    Price       float64 `json:"price"`
}

func (c *OrderServiceClient) QueryOrder(ctx context.Context, orderID string) (*OrderInfo, error) {
    // 1. Check cache
    if cached, ok := c.cache.Get(orderID); ok {
        return cached.(*OrderInfo), nil
    }

    // 2. API call protected by circuit breaker
    result, err := c.breaker.Execute(func() (interface{}, error) {
        url := fmt.Sprintf("%s/api/v1/orders/%s", c.baseURL, orderID)
        req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+getServiceToken())
        req.Header.Set("X-Request-ID", getRequestID(ctx))

        resp, err := c.httpClient.Do(req)
        if err != nil {
            return nil, fmt.Errorf("order service request failed: %w", err)
        }
        defer resp.Body.Close()

        if resp.StatusCode != http.StatusOK {
            return nil, fmt.Errorf("order service returned error: %d", resp.StatusCode)
        }

        var order OrderInfo
        if err := json.NewDecoder(resp.Body).Decode(&order); err != nil {
            return nil, fmt.Errorf("failed to decode order data: %w", err)
        }

        return &order, nil
    })

    if err != nil {
        return nil, err
    }

    order := result.(*OrderInfo)

    // 3. Write to cache (completed orders cached 1 hour, in-progress orders cached 30 seconds)
    ttl := 1 * time.Hour
    if order.Status == "processing" || order.Status == "shipped" {
        ttl = 30 * time.Second
    }
    c.cache.Set(orderID, order, ttl)

    return order, nil
}

// Tool interface implementation
func (c *OrderServiceClient) Name() string {
    return "query_order"
}

func (c *OrderServiceClient) Description() string {
    return "Query order details. Parameter: order_id (string) - order number"
}

func (c *OrderServiceClient) Call(ctx context.Context, args map[string]interface{}) (string, error) {
    orderID, ok := args["order_id"].(string)
    if !ok || orderID == "" {
        return "", fmt.Errorf("missing required parameter: order_id")
    }

    // Order ID format validation
    if !isValidOrderID(orderID) {
        return "", fmt.Errorf("invalid order ID format: %s", orderID)
    }

    order, err := c.QueryOrder(ctx, orderID)
    if err != nil {
        // Distinguish error types to give the model different feedback
        if err == gobreaker.ErrOpenState {
            return "Order service is temporarily unavailable, please try again later", nil
        }
        return "", fmt.Errorf("query order failed: %w", err)
    }

    // Format as model-friendly text
    return fmt.Sprintf(`Order information:
- Order ID: %s
- Status: %s
- Amount: $%.2f
- Order time: %s
- Items: %s`,
        order.OrderID,
        order.Status,
        order.Amount,
        order.CreatedAt.Format("2006-01-02 15:04"),
        formatItems(order.Items),
    ), nil
}

3. Multi-Agent Routing Implementation

package agents

import (
    "context"
    "fmt"
    "strings"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/llm"
    "google.golang.org/adk/team"
)

// CustomerServiceRouter implements intelligent routing
type CustomerServiceRouter struct {
    router   *team.Router
    agents   map[string]*agent.Agent
    fallback *agent.Agent  // fallback Agent
}

func NewCustomerServiceRouter(model llm.Model) (*CustomerServiceRouter, error) {
    r := &CustomerServiceRouter{agents: make(map[string]*agent.Agent)}

    // Create business-domain Agents
    orderAgent, err := r.createOrderAgent(model)
    if err != nil {
        return nil, fmt.Errorf("failed to create order Agent: %w", err)
    }
    r.agents["order"] = orderAgent

    logisticsAgent, err := r.createLogisticsAgent(model)
    if err != nil {
        return nil, fmt.Errorf("failed to create logistics Agent: %w", err)
    }
    r.agents["logistics"] = logisticsAgent

    afterSalesAgent, err := r.createAfterSalesAgent(model)
    if err != nil {
        return nil, fmt.Errorf("failed to create after-sales Agent: %w", err)
    }
    r.agents["after_sales"] = afterSalesAgent

    // Fallback Agent for general questions
    fallbackAgent, err := agent.New(agent.Config{
        Name:  "fallback-agent",
        Model: model,
        Instruction: `You are a general customer-service assistant. When the user's question does not belong to order, logistics, or after-sales domains, provide friendly general help. If the question is beyond your ability, honestly tell the user and suggest contacting a human agent.`,
    })
    if err != nil {
        return nil, err
    }
    r.fallback = fallbackAgent

    // Configure routing rules
    r.router = team.NewRouter(
        team.WithClassifier(r.classifyQuery),
        team.WithFallback(fallbackAgent),
        team.WithRoute("order", r.agents["order"]),
        team.WithRoute("logistics", r.agents["logistics"]),
        team.WithRoute("after_sales", r.agents["after_sales"]),
    )

    return r, nil
}

// classifyQuery implements query classification logic
func (r *CustomerServiceRouter) classifyQuery(ctx context.Context, query string) (string, float64) {
    query = strings.ToLower(query)

    // Fast keyword-based classification (O(1) latency)
    orderKeywords := []string{"order", "place order", "purchase", "payment", "cancel", "price"}
    logisticsKeywords := []string{"logistics", "express", "ship", "delivery", "where", "signed", "station"}
    afterSalesKeywords := []string{"return", "exchange", "repair", "warranty", "after-sales", "complaint", "compensation"}

    for _, kw := range orderKeywords {
        if strings.Contains(query, kw) {
            return "order", 0.9
        }
    }
    for _, kw := range logisticsKeywords {
        if strings.Contains(query, kw) {
            return "logistics", 0.9
        }
    }
    for _, kw := range afterSalesKeywords {
        if strings.Contains(query, kw) {
            return "after_sales", 0.9
        }
    }

    // If keywords miss, use model-based semantic classification (higher latency but more accurate)
    // In production, cache classification results for common queries
    return r.semanticClassify(ctx, query)
}

func (r *CustomerServiceRouter) semanticClassify(ctx context.Context, query string) (string, float64) {
    // Use a lightweight classifier or let an LLM classify
    // To save tokens, use a simplified few-shot prompt
    prompt := fmt.Sprintf(`Classify the following user query into one of: order, logistics, after_sales, other.
Output only the label.

Examples:
Query: Where is my package?
Label: logistics

Query: %s
Label:`, query)

    // Call a lightweight model for classification
    // ...
    return "other", 0.5
}

func (r *CustomerServiceRouter) Run(ctx context.Context, sessionID, query string) (*agent.Response, error) {
    return r.router.Run(ctx, sessionID, query)
}

func (r *CustomerServiceRouter) createOrderAgent(model llm.Model) (*agent.Agent, error) {
    return agent.New(agent.Config{
        Name:  "order-agent",
        Model: model,
        Instruction: `You are an e-commerce order-specialist customer-service agent. Your responsibilities are:
1. Help users query order status and details
2. Handle order modification requests (e.g., change address, cancel order)
3. Explain order-related policies and rules
4. Handle payment-related questions

Notes:
- For operations involving funds (refunds, compensation), you must transfer to a human agent
- Do not promise a specific processing time; use wording such as "usually takes X-Y business days"
- When the user is emotional, calm them first before solving the problem`,
        Tools: []tool.Tool{
            tools.NewOrderServiceClient(os.Getenv("ORDER_SERVICE_URL")),
            tools.NewPaymentServiceClient(os.Getenv("PAYMENT_SERVICE_URL")),
        },
    })
}

4. Streaming Output and WebSocket Integration

package server

import (
    "context"
    "net/http"
    "time"

    "github.com/gorilla/websocket"
)

type WebSocketServer struct {
    router   *agents.CustomerServiceRouter
    upgrader websocket.Upgrader
    sessions *SessionManager
}

func (s *WebSocketServer) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := s.upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Printf("WebSocket upgrade failed: %v", err)
        return
    }
    defer conn.Close()

    sessionID := r.URL.Query().Get("session_id")
    if sessionID == "" {
        sessionID = generateSessionID()
    }

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Printf("WebSocket unexpected close: %v", err)
            }
            break
        }

        userMsg := string(message)

        // Streaming processing
        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)

        go func() {
            defer cancel()

            stream, err := s.router.RunStream(ctx, sessionID, userMsg)
            if err != nil {
                conn.WriteJSON(map[string]string{
                    "type": "error",
                    "content": "Service temporarily unavailable, please try again later",
                })
                return
            }

            for chunk := range stream {
                if err := conn.WriteJSON(map[string]string{
                    "type": "chunk",
                    "content": chunk.Text,
                }); err != nil {
                    log.Printf("Failed to send streaming message: %v", err)
                    return
                }
            }

            // Stream end marker
            conn.WriteJSON(map[string]string{
                "type": "done",
            })
        }()
    }
}

5. Production Deployment Configuration

Dockerfile:

# Build stage
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o customer-service-agent ./cmd/server

# Run stage
FROM gcr.io/distroless/static-debian12
WORKDIR /app
COPY --from=builder /app/customer-service-agent .
COPY --from=builder /app/configs ./configs

EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["./customer-service-agent"]

Kubernetes Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: customer-service-agent
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: customer-service-agent
  template:
    metadata:
      labels:
        app: customer-service-agent
    spec:
      containers:
      - name: agent
        image: your-registry/customer-service-agent:v1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: GOOGLE_API_KEY
          valueFrom:
            secretKeyRef:
              name: agent-secrets
              key: google-api-key
        - name: REDIS_URL
          valueFrom:
            configMapKeyRef:
              name: agent-config
              key: redis-url
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

Complete Code Structure

customer-service-agent/
├── cmd/
│   └── server/
│       └── main.go                 # service entry
├── internal/
│   ├── agents/
│   │   ├── router.go               # router Agent
│   │   ├── order_agent.go          # order Agent
│   │   ├── logistics_agent.go      # logistics Agent
│   │   └── after_sales_agent.go    # after-sales Agent
│   ├── tools/
│   │   ├── order.go                # order-service Tool
│   │   ├── logistics.go            # logistics-service Tool
│   │   ├── payment.go              # payment-service Tool
│   │   └── base.go                 # Tool base class and common logic
│   ├── server/
│   │   ├── websocket.go            # WebSocket service
│   │   ├── http.go                 # HTTP API
│   │   └── middleware.go           # middleware (auth, rate limiting, logging)
│   ├── session/
│   │   ├── manager.go              # session management
│   │   ├── redis_store.go          # Redis storage implementation
│   │   └── postgres_store.go       # PostgreSQL persistence
│   ├── config/
│   │   └── config.go               # configuration management
│   └── metrics/
│       └── metrics.go              # Prometheus metrics
├── configs/
│   ├── production.yaml
│   └── staging.yaml
├── deployments/
│   ├── Dockerfile
│   ├── k8s-deployment.yaml
│   └── docker-compose.yml
├── go.mod
├── go.sum
└── README.md

Summary

The end-to-end project is complete. This project synthesizes knowledge from the following modules:

  • Module 2: Basic Agent configuration and initialization
  • Module 3: Tool implementation, including external API integration, circuit breaker, and caching
  • Module 4: Session management and memory persistence
  • Module 5: Multi-Agent collaboration and routing
  • Module 6: Streaming output and WebSocket real-time communication
  • Module 7: Docker containerization and Kubernetes deployment
  • Module 8: A2A protocol (reserved extension point for integrating Python-ecosystem Agents)
  • Module 9: Callback monitoring and Plugin observability integration

Next we move on to the pitfall notes—summarizing real-world lessons learned.

Callbacks & Plugins | Debugging & Tuning →


Want to learn more Go ADK hands-on? Follow the “Full-Stack Peak — Mengshou Programming” WeChat account for weekly Go / AI programming practice updates.

Frequently Asked Questions

What project is covered in this chapter?

It walks through an end-to-end intelligent customer service system for an e-commerce platform.

What pain points does it address?

High peak response latency, scattered knowledge bases, multi-language needs, complex business domains, and peak-traffic pressure.

What requirements must the system meet?

Both functional requirements (multi-domain query, multi-language customer service) and non-functional requirements (latency, availability, and cost).

What are the key architecture decisions?

Multi-Agent collaboration, knowledge-base integration, caching strategy, streaming responses, and production deployment topology.

What stages are included in the end-to-end flow?

Requirements analysis, architecture design, code implementation, testing, production deployment, and ongoing operations.