让大模型下一步五子棋看起来只需要把棋盘发给模型再让它返回一个坐标。但如果直接把模型输出当作游戏指令系统很快会遇到几个工程问题模型可能返回棋盘外坐标、落在已有棋子的位置、夹带解释文字甚至错误判断胜负。棋盘变大或对局变长后模型还可能遗漏历史状态。根本原因是大模型擅长根据上下文生成候选方案却不是天然可靠的规则执行器。五子棋中的坐标边界、位置占用、连续棋子计数和回合切换都属于确定性逻辑应由普通程序负责。模型更适合承担策略层任务观察当前局面提出一个值得尝试的落子。因此一个可运行的五子棋 AI 不应设计成“模型直接控制游戏”而应拆成以下链路本地程序维护唯一可信的棋盘状态。本地规则引擎生成合法落子集合并判断胜负。模型只返回结构化候选坐标。程序校验候选坐标失败时有限重试。模型不可用或连续输出非法结果时由本地策略降级。这种分工也适用于棋类游戏、审批 Agent、自动化运维和代码修改工具凡是能够用确定性代码表达的约束都不应仅靠提示词保证。规则引擎先建立可信状态下面使用二维数组表示 15×15 棋盘0为空位1为黑棋2为白棋。示例只实现自由规则下的五子连线判断不包含禁手、交换规则或特定赛事规则如果产品需要这些规则应在本地引擎中明确实现不能让模型临时解释。fromdataclassesimportdataclass,field EMPTY0BLACK1WHITE2dataclassclassBoard:size:int15cells:list[list[int]]field(initFalse)def__post_init__(self):self.cells[[EMPTYfor_inrange(self.size)]for_inrange(self.size)]defis_legal(self,row:int,col:int)-bool:return(0rowself.sizeand0colself.sizeandself.cells[row][col]EMPTY)defplace(self,row:int,col:int,player:int)-None:ifplayernotin(BLACK,WHITE):raiseValueError(invalid player)ifnotself.is_legal(row,col):raiseValueError(illegal move)self.cells[row][col]playerdeflegal_moves(self)-list[tuple[int,int]]:return[(r,c)forrinrange(self.size)forcinrange(self.size)ifself.cells[r][c]EMPTY]胜负判断只需检查横向、纵向和两条对角线。落子后从新棋子向相反方向计数可以避免每回合扫描整个棋盘。DIRECTIONS((1,0),(0,1),(1,1),(1,-1))defcount_direction(board:Board,row:int,col:int,dr:int,dc:int,player:int)-int:count0r,crowdr,coldcwhile(0rboard.sizeand0cboard.sizeandboard.cells[r][c]player):count1rdr cdcreturncountdefis_winner(board:Board,row:int,col:int,player:int)-bool:fordr,dcinDIRECTIONS:total1totalcount_direction(board,row,col,dr,dc,player)totalcount_direction(board,row,col,-dr,-dc,player)iftotal5:returnTruereturnFalse这里的返回结果才是游戏状态的事实来源。即使模型声称“已经获胜”程序也只接受规则引擎的判断。压缩棋盘上下文最直观的序列化方式是把每行转换为字符串.表示空位X表示黑棋O表示白棋。行列统一使用从零开始的整数坐标以减少字母坐标与数组下标之间的转换错误。SYMBOLS{EMPTY:.,BLACK:X,WHITE:O}defrender_board(board:Board)-str:lines[]forrow_index,rowinenumerate(board.cells):content.join(SYMBOLS[cell]forcellinrow)lines.append(f{row_index:02d}{content})return\n.join(lines)对于稀疏开局还可以只发送已有棋子的坐标列表进入中后盘后完整棋盘通常更容易让模型理解。无论采用哪种格式都要在提示中明确棋盘尺寸、棋子含义、当前执子方和坐标基准不要假定模型会自动推断。接入模型 API 并约束输出以下示例假设所接入服务支持与 OpenAI Python SDK 相容的聊天接口。具体路径、参数和结构化输出能力应以服务当前文档为准。可以把MODEL_BASE_URL、MODEL_API_KEY和MODEL_NAME配置为实际供应方提供的值例如评估 HaerAPI 等模型接口时也应先确认协议兼容性、可用模型、限流和数据处理规则。安装依赖pipinstallopenai pydantic密钥必须通过环境变量注入不要写进代码、镜像或版本库exportMODEL_BASE_URLhttps://your-api-endpoint.example/v1exportMODEL_API_KEYyour-key-from-secret-managerexportMODEL_NAMEyour-configured-model定义模型返回结构并将温度设为较低值以减少格式漂移。低温不能保证输出正确因此后续校验仍然不可省略。importjsonimportosfrompydanticimportBaseModel,ValidationErrorfromopenaiimportOpenAIclassMove(BaseModel):row:intcol:intreason:strclientOpenAI(api_keyos.environ[MODEL_API_KEY],base_urlos.environ[MODEL_BASE_URL],)defrequest_candidate(board:Board,player:int)-Move:player_nameXifplayerBLACKelseOpromptf你是五子棋策略模块不负责执行规则。 棋盘大小{board.size}×{board.size}坐标范围row 和 col 均为 0 到{board.size-1}当前执子{player_name}. 为空位X 为黑棋O 为白棋。 棋盘{render_board(board)}只返回 JSON 对象不要返回 Markdown {{row: 整数, col: 整数, reason: 简短理由}} responseclient.chat.completions.create(modelos.environ[MODEL_NAME],temperature0.2,messages[{role:user,content:prompt}],)contentresponse.choices[0].message.contentorreturnMove.model_validate(json.loads(content))如果接口原生支持 JSON Schema 或结构化输出应该优先使用该能力但仍需执行棋盘合法性校验。结构正确只代表字段可解析不代表坐标符合当前状态。有限重试与确定性降级不能使用无限重试。模型持续返回非法落子、接口超时或账户触发限流时无限循环会拖住整个游戏进程并放大调用成本。更稳妥的方案是最多尝试两次每次失败都把明确原因反馈给模型之后转入本地策略。下面的降级策略优先选择棋盘中心附近的空位。它不保证棋力但能够保证游戏继续运行并且结果可预测。deffallback_move(board:Board)-tuple[int,int]:center(board.size-1)/2movesboard.legal_moves()ifnotmoves:raiseRuntimeError(board is full)returnmin(moves,keylambdapos:(pos[0]-center)**2(pos[1]-center)**2,)defchoose_ai_move(board:Board,player:int)-tuple[int,int]:forattemptinrange(2):try:moverequest_candidate(board,player)ifboard.is_legal(move.row,move.col):returnmove.row,move.colprint(fcandidate rejected: occupied or out of range, fattempt{attempt1})except(json.JSONDecodeError,ValidationError,KeyError,IndexError)asexc:print(fcandidate parse failed:{type(exc).__name__})exceptExceptionasexc:print(fmodel request failed:{type(exc).__name__})returnfallback_move(board)生产环境中不应直接打印完整提示词或响应因为棋盘之外的应用上下文可能包含用户数据。建议记录请求 ID、耗时、重试次数、错误类型、候选坐标是否合法以及是否触发降级而不是无条件保存原始内容。串起完整回合游戏控制器负责检查终局、调用策略模块和切换玩家。所有状态变更都必须经过Board.place避免界面层或模型调用层直接修改二维数组。defplay_ai_turn(board:Board,player:int)-bool:ifnotboard.legal_moves():print(draw)returnTruerow,colchoose_ai_move(board,player)board.place(row,col,player)print(fplayer{player}, move({row},{col}))ifis_winner(board,row,col,player):print(fwinner{player})returnTruereturnFalse接入网页或桌面界面时还需要防止用户在模型请求尚未完成时重复落子。常见做法是给每局分配game_id和递增的revision发起请求时记录版本响应返回后只有版本仍一致才允许提交。这样即使旧请求晚到也不能覆盖新棋盘。可执行的验证方法不要只通过“和 AI 下几盘”判断系统是否正确。规则层至少应覆盖以下测试deftest_horizontal_win():boardBoard()forcolinrange(5):board.place(7,col,BLACK)assertis_winner(board,7,4,BLACK)deftest_reject_occupied_position():boardBoard()board.place(7,7,BLACK)assertnotboard.is_legal(7,7)deftest_fallback_is_legal():boardBoard()row,colfallback_move(board)assertboard.is_legal(row,col)模型层则适合使用固定棋盘样例做契约测试检查响应能否解析、坐标是否合法、失败时是否在规定次数内降级。由于远程模型和服务配置可能变化测试不应断言模型每次返回同一个坐标而应断言系统级不变量例如“最终一定得到合法空位”以及“接口失败不会破坏棋盘”。常见问题模型为什么总是把行列写反提示中同时给出一个坐标示例例如“row3,col5表示第 3 行第 5 列”并让界面、日志和后端统一使用同一种顺序。不要在不同模块中混用(x, y)与(row, col)。是否应该把全部合法坐标都发给模型空棋盘有 225 个合法位置完整发送会增加上下文。通常只需发送棋盘并由本地校验。若希望彻底约束候选范围可以先用本地启发式筛出靠近已有棋子的若干位置再把候选集合交给模型选择但这可能限制模型策略。模型能够直接判断禁手吗它可以提供参考不能作为最终裁决。禁手涉及明确且可能因规则体系而异的模式识别应由经过测试的规则模块处理并在产品中标明采用的规则版本。接口超时后是否应该继续等待原请求应给调用设置明确超时并通过回合版本阻止迟到响应生效。是否主动取消请求取决于 SDK 和服务端能力但业务层必须能够忽略过期结果。如何提高棋力先在本地加入一步制胜、一步防守和候选点生成再让模型在缩小后的候选集合中做策略选择。若需要稳定的竞技强度传统搜索、威胁空间搜索或经过验证的专用棋类算法通常比完全依赖通用模型更可控。总结一个可靠的五子棋 AI关键不在于写出更长的提示词而在于划清模型与程序的职责模型负责提出策略候选本地引擎负责规则、状态和最终裁决。通过结构化输出、合法性校验、有限重试、版本控制与确定性降级即使模型返回异常或远程接口暂时不可用游戏仍能保持一致状态并继续运行。这套架构也能迁移到更多 Agent 场景让概率性模型参与决策但不让它绕过可验证的业务边界。