Featured image of post Agentic 编程革命:程序员不再需要成为“领域专家”

Agentic 编程革命:程序员不再需要成为“领域专家”

在传统的软件工程(尤其是领域驱动设计 DDD)中,有一条金科玉律:程序员必须深入理解业务领域。

如果我们要写一个会计软件,程序员必须懂复式记账;如果要写一个围棋游戏,程序员必须懂“气”和“眼”的规则,甚至必须精通 Minimax 算法。在这个过程中,程序员实际上充当了“翻译官”的角色——将领域专家的知识,翻译成计算机能读懂的 if/else 和逻辑判断。

但是,Agentic(代理/智能体)编程正在打破这一壁垒。

在这个新时代,我认为:程序员无需再为了开发软件而苦读领域知识,因为 AI (LLM) 已经扮演了完美的领域专家角色。 我们的工作从“翻译规则”变成了“编排专家”。

从“硬编码规则”到“咨询专家”

在传统开发中,逻辑是静态的、硬编码的。 而在 Agentic 开发中,逻辑是动态的、推断的。

通过引入 LLM,我们将最复杂的**业务逻辑(Business Logic)**外包给了模型。模型内置了海量的书籍、维基百科、论文和代码库。它懂法律、懂医学、懂游戏规则。

这意味着,作为一个开发者,你只需要懂得如何提问(Prompting)和如何处理工具调用(Function Calling),就可以构建任何行业的应用。

举个例子:井字棋(Tic-Tac-Toe)

来看一个最简单的例子:井字棋游戏。

使用传统写法,你需要写一大堆 if 语句来检查行、列、对角线是否连成三子。你还需要写一个算法(如 Minimax)来让电脑知道如何在这一步阻挡玩家,或者如何获胜。作为开发者,你必须完全掌握游戏规则,理解游戏规则,才能将这些规则翻译成编程语言。

但是, 在Agentic编程中,你不需要写任何一行代码来判断输赢,也不需要写算法来决定电脑怎么走。你只需要把棋盘扔给 LLM,问它:“你是井字棋专家,现在的局面是谁赢了?”或者“你是井字棋专家,下一步你觉得走哪里最好?”

LLM 就是那个看过几十万局棋谱的“领域专家”。

完整代码展示

下面我使用Python+Langgraph+Deepseek来实现这个游戏,游戏中有两个Agent玩家,他们都是Deepseek为大脑,来思考棋局并决定下一步走法,我(作为开发者)完全没有参与玩法规则的翻译,我只是把流程串起来,所以我只做了编排的事情。

import os
import operator
from typing import Annotated, List, Literal, TypedDict, Union

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END, START

# --- 1. SETUP DEEPSEEK LLM ---
# Replace 'YOUR_DEEPSEEK_API_KEY' with your actual key if not in env vars
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "YOUR_DEEPSEEK_API_KEY")

# DeepSeek is OpenAI-compatible. We use the ChatOpenAI client but point it to DeepSeek's URL.
llm = ChatOpenAI(
    model="deepseek-chat",  # or "deepseek-reasoner" if you want R1 reasoning
    openai_api_key=DEEPSEEK_API_KEY,
    openai_api_base="https://api.deepseek.com",
    temperature=0.2, # Low temp for more strategic/deterministic play
    max_tokens=100
)

# --- 2. DEFINE GAME STATE ---
class GameState(TypedDict):
    board: List[str]          # The 3x3 board as a list of 9 strings (" " or "X" or "O")
    current_player: str       # "X" or "O"
    winner: Union[str, None]  # "X", "O", "Draw", or None (game continues)
    messages: List[str]       # Log of moves for debugging/display

# --- 3. HELPER FUNCTIONS ---

def print_board(board):
    """Visualizes the board in the console."""
    print("\nCurrent Board:")
    print(f" {board[0]} | {board[1]} | {board[2]} ")
    print("---+---+---")
    print(f" {board[3]} | {board[4]} | {board[5]} ")
    print("---+---+---")
    print(f" {board[6]} | {board[7]} | {board[8]} \n")

def check_winner(board):
    """Checks for a win or draw."""
    winning_combos = [
        (0, 1, 2), (3, 4, 5), (6, 7, 8), # Rows
        (0, 3, 6), (1, 4, 7), (2, 5, 8), # Cols
        (0, 4, 8), (2, 4, 6)             # Diagonals
    ]
    
    for a, b, c in winning_combos:
        if board[a] == board[b] == board[c] and board[a] != " ":
            return board[a] # Return "X" or "O"
            
    if " " not in board:
        return "Draw"
        
    return None

def get_valid_moves(board):
    return [i for i, cell in enumerate(board) if cell == " "]

# --- 4. DEFINE AGENT NODE LOGIC ---

def get_line_analysis(board):
    """
    Returns a text summary of all rows, cols, and diagonals
    to help the LLM 'see' threats and opportunities clearly.
    """
    lines = [
        ("Row 1", [0, 1, 2]), ("Row 2", [3, 4, 5]), ("Row 3", [6, 7, 8]),
        ("Col 1", [0, 3, 6]), ("Col 2", [1, 4, 7]), ("Col 3", [2, 5, 8]),
        ("Diag 1", [0, 4, 8]), ("Diag 2", [2, 4, 6])
    ]
    
    analysis = []
    for name, indices in lines:
        values = [board[i] for i in indices]
        # formatted like: "Row 1 (Indices 0,1,2): ['X', ' ', 'O']"
        analysis.append(f"{name} {indices}: {values}")
    return "\n".join(analysis)

import sys
import re

# ... [Keep your existing imports and GameState definition] ...

# --- IMPROVED SENSORY FUNCTIONS ---

def get_tactical_intel(board, player):
    """
    Scans the board for critical 'Threats' (opponent about to win) 
    and 'Opportunities' (we are about to win).
    Returns a text summary to feed into the LLM.
    """
    opponent = "O" if player == "X" else "X"
    winning_combos = [
        (0, 1, 2), (3, 4, 5), (6, 7, 8), # Rows
        (0, 3, 6), (1, 4, 7), (2, 5, 8), # Cols
        (0, 4, 8), (2, 4, 6)             # Diagonals
    ]
    
    intel = []
    
    # 1. Check for WINNING MOVES (My winning spots)
    for a, b, c in winning_combos:
        line = [board[a], board[b], board[c]]
        if line.count(player) == 2 and line.count(" ") == 1:
            empty_idx = [i for i in [a, b, c] if board[i] == " "][0]
            intel.append(f"WINNING OPPORTUNITY found at index {empty_idx}!")

    # 2. Check for THREATS (Opponent winning spots)
    for a, b, c in winning_combos:
        line = [board[a], board[b], board[c]]
        if line.count(opponent) == 2 and line.count(" ") == 1:
            empty_idx = [i for i in [a, b, c] if board[i] == " "][0]
            intel.append(f"CRITICAL THREAT detected at index {empty_idx}! (Opponent is about to win)")

    if not intel:
        intel.append("No immediate threats or winning moves. Play strategically (Center > Corners).")
        
    return "\n".join(intel)

# --- OPTIMIZED AGENT NODE ---

def agent_move(state: GameState):
    player = state["current_player"]
    opponent = "O" if player == "X" else "X"
    board = state["board"]
    valid_moves = get_valid_moves(board)
    
    # Sensory Augmentation: Pre-calculate the tactical situation
    tactical_intel = get_tactical_intel(board, player)
    
    board_visual = (
        f" {board[0]} | {board[1]} | {board[2]} \n"
        f"---+---+---\n"
        f" {board[3]} | {board[4]} | {board[5]} \n"
        f"---+---+---\n"
        f" {board[6]} | {board[7]} | {board[8]} "
    )

    # Prompt: Now concise because the "Hard Work" of scanning is done
    prompt = f"""
    You are Agent {player}. Opponent is {opponent}.
    
    CURRENT BOARD:
    {board_visual}
    
    TACTICAL INTEL (TRUST THIS):
    {tactical_intel}
    
    VALID MOVES: {valid_moves}
    
    INSTRUCTIONS:
    1. If the "TACTICAL INTEL" says there is a WINNING OPPORTUNITY, take it.
    2. If the "TACTICAL INTEL" says there is a CRITICAL THREAT, block it.
    3. Otherwise, pick the Center (4) or a Corner.
    
    OUTPUT FORMAT:
    Move: [index]
    """

    print(f"\n🤖 Agent {player} sees: ", end="", flush=True)

    full_response = ""
    move_idx = valid_moves[0] # Default fallback

    try:
        # Fast streaming
        for chunk in llm.stream([HumanMessage(content=prompt)]):
            content = chunk.content
            print(content, end="", flush=True)
            full_response += content
        print() 

        # Parsing
        numbers = re.findall(r'\d+', full_response)
        if numbers:
            move_idx = int(numbers[-1])
            
        if move_idx not in valid_moves:
            # Fallback logic if LLM hallucinates despite intel
            # If we know there is a threat/win, we can force it here if you want to be 100% safe
            # But per your rules, we let the LLM decide.
            print(f"⚠️ Agent {player} tried illegal move {move_idx}. Random fallback.")
            move_idx = valid_moves[0]

    except Exception as e:
        print(f"\nError: {e}")
        move_idx = valid_moves[0]

    new_board = board.copy()
    new_board[move_idx] = player
    
    print_board(new_board)

    result = check_winner(new_board)
    next_player = "O" if player == "X" else "X"
    
    return {
        "board": new_board,
        "current_player": next_player,
        "winner": result,
        "messages": [f"Player {player} picked {move_idx}"]
    }

# --- 5. DEFINE GRAPH NODES ---

def player_x_node(state: GameState):
    return agent_move(state)

def player_o_node(state: GameState):
    return agent_move(state)

# --- 6. BUILD THE LANGGRAPH ---

workflow = StateGraph(GameState)

# Add nodes
workflow.add_node("agent_x", player_x_node)
workflow.add_node("agent_o", player_o_node)

# Add conditional logic (Router)
def turn_router(state: GameState):
    if state["winner"]:
        return "game_over"
    
    if state["current_player"] == "X":
        return "agent_x"
    else:
        return "agent_o"

# Set entry point
workflow.add_conditional_edges(
    START,
    turn_router,
    {
        "agent_x": "agent_x",
        "agent_o": "agent_o",
        "game_over": END
    }
)

# Edges from agents back to the router
workflow.add_conditional_edges(
    "agent_x",
    turn_router,
    {
        "agent_x": "agent_x", # Should not happen based on logic, but required for graph safety
        "agent_o": "agent_o",
        "game_over": END
    }
)

workflow.add_conditional_edges(
    "agent_o",
    turn_router,
    {
        "agent_x": "agent_x",
        "agent_o": "agent_o", # Should not happen
        "game_over": END
    }
)

# Compile the graph
app = workflow.compile()

# --- 7. RUN THE GAME ---

if __name__ == "__main__":
    print("❌ vs ⭕ - AI Tic-Tac-Toe Battle (Powered by DeepSeek)")
    
    # Initial State
    initial_state = {
        "board": [" "] * 9,
        "current_player": "X",
        "winner": None,
        "messages": []
    }
    
    # Run the graph
    final_state = app.invoke(initial_state)
    
    print("--- GAME OVER ---")
    if final_state["winner"] == "Draw":
        print("It's a Draw! 🤝")
    else:
        print(f"The Winner is Agent {final_state['winner']}! 🏆")

界面展示

uv run main.py
❌ vs ⭕ - AI Tic-Tac-Toe Battle (Powered by DeepSeek)

🤖 Agent X sees: The board is empty and there are no immediate threats or winning moves.  
Following the strategic priority (Center > Corners), I’ll take the center.  

Move: [4]

Current Board:
   |   |   
---+---+---
   | X |   
---+---+---
   |   |   


🤖 Agent O sees: The center (position 4) is taken by X.  
The tactical intel says no immediate threats or winning moves, and suggests playing strategically with priority: Center > Corners.  

Since center is unavailable, I should pick a corner.  
Available corners are: 0, 2, 6, 8.  

I’ll choose the top-left corner.  

Move: [0]

Current Board:
 O |   |   
---+---+---
   | X |   
---+---+---
   |   |   


🤖 Agent X sees: The center (position 4) is already taken by X.  
The best strategic move from the valid list is a corner.  

From the available corners (1, 3, 7, 9), only 1 and 3 are currently valid.  
I’ll pick the first available corner.  

Move: [1]

Current Board:
 O | X |   
---+---+---
   | X |   
---+---+---
   |   |   


🤖 Agent O sees: The intel says there is a **CRITICAL THREAT at index 7** (opponent is about to win).  
Rule 2 says to block it.  

**Move: [7]**

Current Board:
 O | X |   
---+---+---
   | X |   
---+---+---
   | O |   


🤖 Agent X sees: The tactical intel says there are no immediate threats or winning moves, so I should follow the strategic priority: Center > Corners.  

The center (position 4) is already taken by X.  
Valid corners available are positions 0, 2, 6, and 8.  
From the valid moves list [2, 3, 5, 6, 8], the corners available are 2, 6, and 8.  

I’ll pick the first corner in the valid moves list: position 2.  

Move: [2]

Current Board:
 O | X | X 
---+---+---
   | X |   
---+---+---
   | O |   


🤖 Agent O sees: The intel says there is a **CRITICAL THREAT at index 6**.  
Rule 2 says to block it.  

**Move: [6]**

Current Board:
 O | X | X 
---+---+---
   | X |   
---+---+---
 O | O |   


🤖 Agent X sees: The intel shows two critical threats at indices 3 and 8.  
Blocking either one stops the opponent from winning immediately.  

From the valid moves list, both 3 and 8 are available.  
I’ll choose the first one in the list to block.  

Move: [3]

Current Board:
 O | X | X 
---+---+---
 X | X |   
---+---+---
 O | O |   


🤖 Agent O sees: The intel shows a winning opportunity at index 8 and a critical threat at index 5.  
Rule 1 says to take the winning move if available.  

**Move: [8]**

Current Board:
 O | X | X 
---+---+---
 X | X |   
---+---+---
 O | O | O 

--- GAME OVER ---
The Winner is Agent O! 🏆
By 大可出奇迹