•12 min read

Các AI Agent trong Kỹ thuật Phần mềm: Các Mô hình Kiến trúc Thực sự Hiệu quả

Các AI Agent trong Kỹ thuật Phần mềm: Các Mô hình Kiến trúc Thực sự Hiệu quả

Sự cường điệu xung quanh các tác nhân AI tập trung vào những gì chúng có thể làm trong tương lai. Bài đăng này tập trung vào những gì đang hoạt động hiện nay — các mẫu kiến trúc đằng sau các hệ thống tác nhân sản xuất, các chế độ lỗi mà các nhóm đang gặp phải và các quyết định về cơ sở hạ tầng bạn cần đưa ra trước khi triển khai một tác nhân tự động chống lại một codebase thực.


Audio Briefing
0:00 / 0:00

Điều gì làm cho một tác nhân khác biệt so với một chatbot

Một chatbot phản hồi các lời nhắc. Một tác nhân thực hiện một vòng lặp:

Observe → Think → Act → Observe (loop until goal met or budget exceeded)

Điểm khác biệt quan trọng là sử dụng công cụ có phản hồi. Một tác nhân không chỉ tạo văn bản — nó gọi các công cụ, nhận kết quả và quyết định làm gì tiếp theo dựa trên những kết quả đó. Điều này tạo ra một vòng lặp phản hồi mà một lời gọi LLM một lần không có.

Cụ thể, một tác nhân có thể:

  1. Đọc kết quả kiểm thử thất bại
  2. Tìm kiếm hàm liên quan trong codebase
  3. Đọc triển khai hàm
  4. Sửa đổi tệp
  5. Chạy lại các kiểm thử
  6. Đọc kết quả mới
  7. Lặp lại cho đến khi các kiểm thử vượt qua

Mỗi bước đều sử dụng công cụ. LLM quyết định gọi công cụ nào dựa trên ngữ cảnh tích lũy qua các bước. Đây là mẫu ReAct (Reasoning + Acting), được giới thiệu vào năm 2022, là nền tảng của hầu hết các hệ thống tác nhân sản xuất hiện nay.


Advertisement

Vòng lặp gọi công cụ

Ở cấp độ triển khai, một vòng lặp tác nhân tối thiểu trông như thế này:

import anthropic
import json
from typing import Any

client = anthropic.Anthropic()

# Define the tools the agent can use
TOOLS = [
    {
        "name": "read_file",
        "description": "Read the contents of a file",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path to read"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "run_command",
        "description": "Run a shell command and return stdout/stderr",
        "input_schema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "Shell command to run"}
            },
            "required": ["command"]
        }
    },
    {
        "name": "write_file",
        "description": "Write content to a file",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path"},
                "content": {"type": "string", "description": "Content to write"}
            },
            "required": ["path", "content"]
        }
    }
]

def execute_tool(tool_name: str, tool_input: dict) -> str:
    """Execute a tool and return its output as a string."""
    import subprocess
    from pathlib import Path
    
    if tool_name == "read_file":
        path = Path(tool_input["path"])
        if not path.exists():
            return f"Error: file {path} does not exist"
        return path.read_text()
    
    elif tool_name == "run_command":
        result = subprocess.run(
            tool_input["command"],
            shell=True,
            capture_output=True,
            text=True,
            timeout=30
        )
        output = result.stdout
        if result.stderr:
            output += f"\nSTDERR:\n{result.stderr}"
        return output or "(no output)"
    
    elif tool_name == "write_file":
        path = Path(tool_input["path"])
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(tool_input["content"])
        return f"Written {len(tool_input['content'])} bytes to {path}"
    
    return f"Unknown tool: {tool_name}"

def run_agent(task: str, max_iterations: int = 20) -> str:
    """Run an agent loop until task completion or iteration limit."""
    messages = [{"role": "user", "content": task}]
    
    for iteration in range(max_iterations):
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=4096,
            tools=TOOLS,
            messages=messages,
        )
        
        # Add assistant response to history
        messages.append({"role": "assistant", "content": response.content})
        
        # Check if agent is done
        if response.stop_reason == "end_turn":
            # Extract text response
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text
            return "Task completed"
        
        # Process tool calls
        if response.stop_reason == "tool_use":
            tool_results = []
            
            for block in response.content:
                if block.type == "tool_use":
                    print(f"  → Calling {block.name}({json.dumps(block.input)[:100]})")
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            
            messages.append({"role": "user", "content": tool_results})
    
    return f"Reached iteration limit ({max_iterations})"

Đây là cốt lõi. Mọi thứ khác — bộ nhớ, phối hợp đa tác nhân, các rào cản an toàn — đều được xây dựng dựa trên vòng lặp này.


Giao thức ngữ cảnh mô hình (MCP): Tiêu chuẩn hóa tích hợp công cụ

Vòng lặp gọi công cụ ở trên định nghĩa các công cụ nội tuyến trong lược đồ JSON. Đối với các nhóm xây dựng nhiều tác nhân chống lại nhiều công cụ, điều này nhanh chóng trở nên lộn xộn. Mỗi tác nhân định nghĩa lại các công cụ giống nhau với các lược đồ hơi khác nhau.

Giao thức ngữ cảnh mô hình (MCP), được Anthropic giới thiệu vào cuối năm 2024, tiêu chuẩn hóa cách các tác nhân kết nối với các hệ thống bên ngoài. Thay vì nhúng định nghĩa công cụ vào mỗi lời nhắc tác nhân, các công cụ nằm trong các máy chủ MCP mà bất kỳ tác nhân tương thích nào cũng có thể truy vấn.

Agent ←→ MCP Client ←→ MCP Server (filesystem, GitHub, databases, etc.)

Một máy chủ MCP hiển thị:

  • Công cụ: Các hàm mà tác nhân có thể gọi (ví dụ: create_file, list_issues)
  • Tài nguyên: Dữ liệu mà tác nhân có thể đọc (ví dụ: nội dung tệp, bản ghi cơ sở dữ liệu)
  • Lời nhắc: Các mẫu lời nhắc có tham số
# Example: connecting an agent to an MCP server
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def run_agent_with_mcp(task: str) -> str:
    # Connect to a filesystem MCP server
    server_params = StdioServerParameters(
        command="uvx",
        args=["mcp-server-filesystem", "/workspace"]
    )
    
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            
            # List available tools from the MCP server
            tools_result = await session.list_tools()
            tools = [t.model_dump() for t in tools_result.tools]
            
            # Run agent with MCP tools
            # (same loop as above, but tools come from MCP server)
            print(f"Available tools: {[t['name'] for t in tools]}")
            return "task_result"

MCP hiện được Claude, OpenAI, Gemini và hầu hết các framework tác nhân lớn hỗ trợ. Xây dựng lớp công cụ của bạn dưới dạng máy chủ MCP có nghĩa là nó hoạt động trên các nhà cung cấp mô hình.


Bộ nhớ: Phần khó nhất

Các tác nhân có bốn loại bộ nhớ, mỗi loại có những đánh đổi khác nhau:

LoạiLưu trữTruy xuấtTrường hợp sử dụng
Trong ngữ cảnhCửa sổ ngữ cảnh LLMTự động (mọi thứ trong ngữ cảnh)Các tác vụ ngắn, vừa trong cửa sổ
Bên ngoài (vector)Cơ sở dữ liệu vectorTìm kiếm tương đồng ngữ nghĩaCác cuộc hội thoại dài, cơ sở tri thức
Bên ngoài (có cấu trúc)Kho SQL/KVTra cứu chính xácTùy chọn người dùng, trạng thái tác vụ
Trong trọng sốTrọng số mô hìnhTự động (được tích hợp sẵn)Dữ liệu đào tạo, không thể sửa đổi trong thời gian chạy

Bộ nhớ trong ngữ cảnh

Cách tiếp cận đơn giản nhất: giữ toàn bộ cuộc trò chuyện trong cửa sổ ngữ cảnh. Hoạt động cho đến khi tác vụ của bạn vượt quá giới hạn ngữ cảnh (128K–200K token cho các mô hình hiện tại).

Chiến lược nén: Khi gần đạt giới hạn, tóm tắt các lượt cũ hơn:

def compress_history(messages: list, keep_last_n: int = 10) -> list:
    """Compress old messages when approaching context limit."""
    if len(messages) <= keep_last_n:
        return messages
    
    # Summarize old messages
    old_messages = messages[:-keep_last_n]
    summary_prompt = f"""Summarize these agent actions and their results concisely:
    
{json.dumps(old_messages, indent=2)}

Summary (keep all file paths, error messages, and key decisions):"""
    
    summary_response = client.messages.create(
        model="claude-haiku-4-5",  # cheaper model for summarization
        max_tokens=1000,
        messages=[{"role": "user", "content": summary_prompt}]
    )
    
    summary = summary_response.content[0].text
    return [{"role": "user", "content": f"[Earlier context summary]: {summary}"}] + messages[-keep_last_n:]

Bộ nhớ vector

Đối với các tác vụ kéo dài nhiều phiên hoặc cần nhớ lại kiến thức trước đó, hãy lưu trữ các quan sát của tác nhân trong cơ sở dữ liệu vector:

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import anthropic

embed_client = anthropic.Anthropic()

def store_observation(collection: str, text: str, metadata: dict) -> None:
    """Store an agent observation in vector memory."""
    # Get embedding
    response = embed_client.embeddings.create(
        model="voyage-3",
        input=[text]
    )
    vector = response.embeddings[0]
    
    qdrant = QdrantClient("localhost", port=6333)
    qdrant.upsert(
        collection_name=collection,
        points=[PointStruct(
            id=hash(text) % (2**32),
            vector=vector,
            payload={"text": text, **metadata}
        )]
    )

def recall_relevant(collection: str, query: str, top_k: int = 5) -> list[str]:
    """Retrieve relevant past observations for a query."""
    response = embed_client.embeddings.create(
        model="voyage-3",
        input=[query]
    )
    query_vector = response.embeddings[0]
    
    qdrant = QdrantClient("localhost", port=6333)
    results = qdrant.search(
        collection_name=collection,
        query_vector=query_vector,
        limit=top_k
    )
    
    return [r.payload["text"] for r in results]

Advertisement

Phối hợp đa tác nhân

Các tác nhân đơn lẻ gặp giới hạn: kích thước ngữ cảnh, độ phức tạp của tác vụ, nhu cầu chuyên môn hóa. Các kiến trúc đa tác nhân chia nhỏ công việc thành các tác nhân chuyên biệt phối hợp thông qua một trạng thái chia sẻ.

Các mẫu phổ biến:

Người điều phối → Người thực hiện: Một tác nhân chia nhỏ tác vụ, phân công các tác vụ con cho các người thực hiện chuyên biệt, tổng hợp kết quả:

def orchestrator_loop(high_level_task: str) -> str:
    """Orchestrator that delegates to specialized sub-agents."""
    subtasks = decompose_task(high_level_task)  # LLM call
    
    results = {}
    for subtask in subtasks:
        agent_type = route_to_agent(subtask)  # LLM call
        
        if agent_type == "code_writer":
            results[subtask] = run_code_agent(subtask)
        elif agent_type == "test_runner":
            results[subtask] = run_test_agent(subtask)
        elif agent_type == "reviewer":
            results[subtask] = run_review_agent(subtask)
    
    return aggregate_results(results)  # LLM call

Mẫu phê bình: Một tác nhân tạo ra đầu ra, một tác nhân khác phê bình nó:

def generate_with_critique(task: str, max_rounds: int = 3) -> str:
    """Generate output, critique it, revise until acceptable."""
    content = run_agent(task)  # generator agent
    
    for _ in range(max_rounds):
        critique = run_agent(
            f"Review this output critically:\n\n{content}\n\n"
            f"Original task: {task}\n\n"
            "List specific issues. If acceptable, say 'APPROVED'."
        )
        
        if "APPROVED" in critique:
            return content
        
        content = run_agent(
            f"Revise based on this critique:\n\n{critique}\n\n"
            f"Current content:\n\n{content}"
        )
    
    return content

Ranh giới an toàn: Những gì bạn phải xác định trước khi triển khai

Đây là phần mà hầu hết các hướng dẫn bỏ qua. Các tác nhân có thể sửa đổi tệp, chạy lệnh và gọi các API bên ngoài có thể gây ra thiệt hại nghiêm trọng. Xác định các ràng buộc này trước khi viết mã tác nhân:

1. Ranh giới hệ thống tệp

from pathlib import Path

ALLOWED_WRITE_PATHS = [Path("/workspace"), Path("/tmp/agent")]
FORBIDDEN_PATHS = [Path("/etc"), Path("/root"), Path.home() / ".ssh"]

def safe_write_file(path_str: str, content: str) -> str:
    path = Path(path_str).resolve()
    
    for forbidden in FORBIDDEN_PATHS:
        if path.is_relative_to(forbidden):
            return f"BLOCKED: cannot write to {forbidden}"
    
    if not any(path.is_relative_to(allowed) for allowed in ALLOWED_WRITE_PATHS):
        return f"BLOCKED: {path} is outside allowed write paths"
    
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content)
    return f"Written to {path}"

2. Danh sách cho phép lệnh

import shlex

ALLOWED_COMMANDS = {"pytest", "ruff", "mypy", "npm", "git diff", "git status"}
FORBIDDEN_COMMAND_PREFIXES = ("rm -rf", "git push", "git reset --hard", "sudo", "curl", "wget")

def safe_run_command(command: str) -> str:
    # Check forbidden prefixes
    stripped = command.strip()
    for forbidden in FORBIDDEN_COMMAND_PREFIXES:
        if stripped.startswith(forbidden):
            return f"BLOCKED: command matches forbidden pattern '{forbidden}'"
    
    # Check against allowlist (optional — more restrictive)
    cmd_parts = shlex.split(stripped)
    base_cmd = cmd_parts[0] if cmd_parts else ""
    if base_cmd not in ALLOWED_COMMANDS:
        return f"BLOCKED: '{base_cmd}' not in allowed commands. Allowed: {ALLOWED_COMMANDS}"
    
    # Actually run
    import subprocess
    result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60)
    return result.stdout + (f"\nSTDERR:\n{result.stderr}" if result.stderr else "")

3. Ngân sách token và lặp lại

Luôn xác định mức chi tiêu tối đa cho mỗi tác vụ. Ghi lại tất cả các lệnh gọi công cụ để kiểm tra. Cảnh báo về các mẫu bất thường (cùng một lệnh trong một vòng lặp → vấn đề tiềm ẩn).


Các chế độ lỗi thực tế

Những gì bị hỏng trong thực tế:

  • Ảo giác gọi công cụ: Tác nhân gọi một công cụ với các tham số không tồn tại trong lược đồ. Khắc phục: xác thực đầu vào trước khi thực thi, trả về lỗi có cấu trúc.
  • Ô nhiễm ngữ cảnh: Các thông báo lỗi cũ làm lộn xộn ngữ cảnh liên quan. Khắc phục: nén lịch sử, sử dụng kết quả công cụ có cấu trúc.
  • Vòng lặp vô hạn: Tác nhân liên tục thử cùng một cách tiếp cận thất bại. Khắc phục: theo dõi các hành động đã thử, dừng lại khi có các lệnh gọi giống hệt nhau lặp lại.
  • Tự tin thái quá: Tác nhân báo cáo thành công mà không xác minh. Khắc phục: luôn xác minh — chạy kiểm thử, kiểm tra nội dung tệp, không tin vào báo cáo tự thân của tác nhân.
  • Mở rộng phạm vi: Tác nhân thực hiện các thay đổi "hữu ích" vượt ra ngoài tác vụ ban đầu. Khắc phục: ranh giới tác vụ rõ ràng trong lời nhắc hệ thống, các bản diff được xem xét trước khi commit.

Bạn cũng có thể thích

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
Kỹ thuật Phần mềm Xanh: Tối ưu hóa Tải công việc AI để Tiết kiệm Năng lượng vào năm 2026
ai

Kỹ thuật Phần mềm Xanh: Tối ưu hóa Tải công việc AI để Tiết kiệm Năng lượng vào năm 2026

Các chiến lược khả thi để nhà phát triển lập hồ sơ, đánh giá hiệu năng và giảm lượng khí thải carbon cũng như mức tiêu thụ điện năng trên các tải công việc AI và LLM nặng tính toán — CodeCarbon, lượng tử hóa, phân lô thông minh, dịch chuyển tải công việc theo thời gian và ngân sách carbon CI.

Read more