在传统的软件工程(尤其是领域驱动设计 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! 🏆