本地35B模型53轮对话实战:从零开发五子棋AI
发布时间:2026/9/29 17:36:06来源:尧图网络
1. 为什么我决定用本地35B模型来写五子棋AI先说结论我用一个本地部署的35B参数大模型通过53轮对话、大约40分钟从零写出了一套能跑、能下、能赢的五子棋AI。整个过程没有联网查资料没有手写一行核心算法全部靠对话驱动完成。这件事的背景是这样的。最近半年AI Coding 这个话题被讨论得非常多各种辅助编程工具层出不穷。但大多数人的用法还停留在“帮我补全一个函数”“帮我写个正则”这种碎片化层面。我一直在想一个问题如果我把一个完整的、有明确规则和胜负判定的项目从头到尾交给本地模型来做它到底能做到什么程度选五子棋不是随便拍的。它有几个非常适合做实验的特性规则简单但策略空间大有明确的胜负判定标准涉及数据结构、搜索算法、评估函数、界面交互等多个模块而且最终产物可以立刻验证——下一盘就知道行不行。换句话说它是一个“麻雀虽小五脏俱全”的项目既能考验模型的代码生成能力又能考验它在多轮对话中保持上下文一致性的能力。为什么强调“本地”和“35B”因为这里面有两个核心考量。第一本地部署意味着数据不出机器对于很多有代码保密需求的团队来说这是硬性要求。第二35B这个参数量级很有意思——它比7B、13B明显聪明但又不像70B以上那样对硬件要求苛刻。实测下来一张24G显存的消费级显卡用4-bit量化就能跑得比较流畅。这个门槛很多个人开发者和小团队是够得着的。适合谁来参考这篇内容如果你是对 AI Coding 感兴趣的开发者想了解本地模型在实际项目中的真实表现如果你手头有本地部署的模型但不知道怎么把它用到完整项目开发里如果你想知道多轮对话驱动开发的边界在哪里、坑在哪里——那这篇内容应该能给你一些直接可用的经验。我用的模型是通过 LM Studio 加载的本地模型量化版本上下文窗口设的32K。对话轮次统计是53轮实际耗时大约40分钟中间包括了几次调试和返工。下面我把整个过程拆开来讲包括我怎么设计对话流程、模型在哪些地方翻车、我是怎么把它拉回来的以及最终产出的代码结构长什么样。2. 整体开发思路与对话流程设计2.1 为什么选择“对话驱动”而不是“一次性生成”很多人用 AI 写代码的习惯是把需求一次性描述清楚让模型直接吐出一个完整项目。我试过这种方式对于五子棋这种规模的项目一次性生成的结果往往是“看起来能跑实际上到处是坑”。比如棋盘状态更新逻辑和胜负判定逻辑对不上或者搜索算法的剪枝条件写错了导致AI下出莫名其妙的棋。对话驱动的核心优势在于每一轮只聚焦一个模块生成后立刻验证发现问题马上在下一轮修正。这样模型始终在一个可控的上下文范围内工作不会因为一次性要处理太多信息而顾此失彼。我的对话流程大致分成了六个阶段确定技术栈和项目结构实现棋盘数据结构和基础操作实现胜负判定逻辑实现AI搜索算法和评估函数实现交互界面联调测试和优化每个阶段平均用了8到10轮对话其中调试和修正占了大约三分之一。2.2 技术栈选型为什么用Python而不是C这里有一个关键决策。五子棋AI的核心是搜索算法理论上C的性能会好很多。但我最终选了Python原因有三个第一本地35B模型对Python代码的生成质量明显更高。训练数据里Python代码的占比大模型对Python的语法习惯、常用库、代码风格都更熟悉生成的代码bug率更低。第二Python的开发迭代速度快。在对话驱动的模式下我需要频繁地运行、测试、修改Python的“改完直接跑”特性让这个循环非常顺畅。第三对于五子棋这个规模的问题Python的性能完全够用。我用的是带Alpha-Beta剪枝的极小化极大搜索搜索深度设到4层在普通笔记本上每步棋的计算时间在0.5秒以内完全不影响体验。如果你后续想把这个AI部署到对性能要求更高的场景可以把核心搜索算法用C重写Python只做界面和调度。但在开发阶段Python是更明智的选择。2.3 项目结构设计让模型有章可循在第二轮对话里我让模型先给出了项目结构。这一步很关键因为清晰的结构能让后续每一轮对话都有明确的“落点”。最终的项目结构是这样的gomoku_ai/ ├── board.py # 棋盘数据结构和基础操作 ├── rules.py # 胜负判定逻辑 ├── evaluator.py # 棋型评估函数 ├── search.py # Alpha-Beta搜索算法 ├── ai_player.py # AI玩家接口 ├── game.py # 游戏主循环 └── ui.py # 命令行交互界面这个结构的好处是每个模块职责单一模型在生成每个文件时只需要关注当前模块的逻辑不需要同时考虑其他模块的实现细节。而且模块之间的接口在早期就定义好了后续修改不会引起连锁反应。实操心得在让模型生成代码之前先让它输出项目结构和模块接口定义这一步花5分钟能省后面至少半小时的返工时间。3. 核心模块实现与关键细节解析3.1 棋盘数据结构为什么用二维列表而不是位棋盘棋盘模块是整个项目的基础。我让模型用二维列表来表示棋盘15x15的网格0表示空位1表示黑棋2表示白棋。class Board: def __init__(self, size15): self.size size self.grid [[0] * size for _ in range(size)] self.move_history [] def place(self, row, col, player): if self.grid[row][col] ! 0: return False self.grid[row][col] player self.move_history.append((row, col, player)) return True def undo(self): if self.move_history: row, col, player self.move_history.pop() self.grid[row][col] 0 return True return False这里有一个值得说的点为什么不用位棋盘位棋盘用两个整数黑棋一个、白棋一个来表示整个棋盘状态在C里是标准做法因为位运算极快。但在Python里位运算的开销反而比列表索引大而且位棋盘的代码可读性差模型生成时容易出错。实测下来二维列表在Python里的性能完全够用而且调试方便得多。move_history这个设计是为了支持悔棋和搜索时的状态回滚。在Alpha-Beta搜索中我们需要频繁地“落子-评估-撤销”有了这个历史记录撤销操作就是O(1)的。3.2 胜负判定四个方向的检查逻辑胜负判定看起来简单但实际写起来有几个容易翻车的地方。我让模型实现的逻辑是每次落子后只检查经过这个落子点的四条线横、竖、两条对角线看是否有连续五个同色棋子。def check_win(board, row, col, player): directions [(0, 1), (1, 0), (1, 1), (1, -1)] for dr, dc in directions: count 1 # 正向检查 for step in range(1, 5): r, c row dr * step, col dc * step if 0 r board.size and 0 c board.size and board.grid[r][c] player: count 1 else: break # 反向检查 for step in range(1, 5): r, c row - dr * step, col - dc * step if 0 r board.size and 0 c board.size and board.grid[r][c] player: count 1 else: break if count 5: return True return False这段代码模型一次就写对了但有一个细节需要注意边界检查必须同时检查行和列不能只检查一个方向。我在第三轮对话时特意让模型确认了这一点它确实处理正确了。注意事项胜负判定只需要检查最后落子的位置不需要全盘扫描。全盘扫描的时间复杂度是O(n²)而局部检查是O(1)在搜索算法中这个差异会被放大很多倍。3.3 评估函数五子棋AI的“大脑”评估函数是整个AI最核心的部分它决定了AI“觉得”哪个局面好、哪个局面差。我让模型实现了一个基于棋型识别的评估函数核心思路是扫描棋盘上所有可能的五连位置统计各种棋型的数量然后加权求和。棋型分为以下几种棋型说明分值五连已经连成五个100000活四两端都空的四连10000冲四一端被堵的四连1000活三两端都空的三连1000眠三一端被堵的三连100活二两端都空的二连100眠二一端被堵的二连10这个分值设计是有讲究的。活四的分值是冲四的10倍因为活四必胜而冲四可以被对方堵住。活三的分值和冲四一样因为活三下一步可以变成活四威胁程度相当。模型在生成这个评估函数时第一版漏掉了“跳三”中间隔一个空位的三连的情况。我在测试时发现AI对跳三的威胁不敏感于是在下一轮对话中让模型补充了这个棋型的识别。def evaluate_pattern(line, player): 评估一条线上的棋型 score 0 opponent 3 - player # 检查五连 if line.count(player) 5 and line.count(opponent) 0: return 100000 # 检查活四 if line.count(player) 4 and line.count(opponent) 0: # 判断两端是否为空 ... # 更多棋型判断... return score评估函数的性能直接影响搜索速度。我让模型用了一个优化技巧预先计算所有可能的五连位置横、竖、斜共约572个每次评估只扫描这些位置而不是全盘扫描。这个优化让评估速度提升了大约3倍。3.4 Alpha-Beta搜索让AI“想”得更深搜索算法我选了Alpha-Beta剪枝的极小化极大搜索。这是五子棋AI的经典方案实现难度适中效果也足够好。核心参数有两个搜索深度和候选着法数量。搜索深度我设的是4层AI走一步、对手走一步、AI再走一步、对手再走一步候选着法只考虑已有棋子周围2格范围内的空位这样每层的分支因子大约在20到30之间。def alphabeta(board, depth, alpha, beta, maximizing, player): if depth 0 or check_win(board, last_row, last_col, 3 - player): return evaluate(board, player) candidates generate_candidates(board) if maximizing: max_eval float(-inf) for row, col in candidates: board.place(row, col, player) eval_score alphabeta(board, depth - 1, alpha, beta, False, player) board.undo() max_eval max(max_eval, eval_score) alpha max(alpha, eval_score) if beta alpha: break return max_eval else: min_eval float(inf) for row, col in candidates: board.place(row, col, 3 - player) eval_score alphabeta(board, depth - 1, alpha, beta, True, player) board.undo() min_eval min(min_eval, eval_score) beta min(beta, eval_score) if beta alpha: break return min_eval这里有一个模型一开始写错的地方在递归调用时它把maximizing参数传错了导致搜索逻辑混乱。我在第五轮对话时通过一个简单的测试用例发现了这个问题——让AI执黑先手它居然下在了棋盘角落。修正后AI的第一手就正常下在了天元附近。实操心得测试搜索算法时先用一个已知答案的简单局面验证。比如摆一个“AI下一步就能赢”的局面看它能不能找到必胜着法。如果找不到说明搜索逻辑有问题。4. 完整实操过程与关键环节记录4.1 环境准备与模型加载我用的本地模型是通过 LM Studio 加载的量化版本是Q4_K_M这个量化级别在精度和速度之间取得了比较好的平衡。加载时把上下文窗口设成了32K因为多轮对话加上代码内容上下文消耗比较快。加载模型时需要注意几个参数上下文长度建议至少16K32K更稳妥。五子棋项目的代码总量大约2000行加上对话历史16K勉强够用但32K更从容。GPU层数如果显存够尽量全部加载到GPU。35B模型Q4量化后大约20G24G显存的卡可以全量加载。温度参数代码生成建议用较低的温度我设的是0.2这样生成的代码更稳定、更符合规范。注意事项不同版本的 LM Studio 对模型格式的支持不一样加载前确认模型格式是GGUF。如果加载失败先检查模型文件是否完整再检查 LM Studio 版本是否支持该量化格式。4.2 第一轮对话明确需求和约束我的第一轮对话是这样写的“我要用Python写一个五子棋AI15x15棋盘人机对战。AI用Alpha-Beta搜索搜索深度4层。项目分模块棋盘、规则、评估、搜索、AI玩家、游戏主循环、界面。先给我项目结构和每个模块的接口定义。”这一轮的目的是让模型建立全局观。模型返回的结构和我预期的基本一致接口定义也合理。我确认后进入下一轮。4.3 第二到十轮逐个模块生成和验证接下来我按照项目结构一个模块一个模块地让模型生成代码。每生成一个模块我立刻在本地运行测试。棋盘模块和规则模块比较顺利模型基本一次写对。评估模块改了两次第一次是补充跳三的识别第二次是优化扫描性能。搜索模块改了三次主要是参数传递和剪枝条件的bug。这里有一个提高效率的技巧每次让模型生成代码时把相关模块的接口定义一起贴给它。比如生成搜索模块时我把棋盘和评估模块的接口贴过去这样模型生成的代码能直接对接不需要我再手动调整。4.4 第十一到二十轮界面和联调界面我选的是命令行版本用简单的文本输出显示棋盘。模型生成的界面代码基本可用但我手动调整了棋盘的显示格式让它更直观。联调阶段发现了一个典型问题AI在搜索时修改了棋盘状态但搜索结束后没有正确恢复。这是因为undo操作在递归的某些分支上没有执行到。我在第二十轮对话时让模型检查了所有place和undo的配对情况找到了两处遗漏。4.5 第二十一到五十三轮优化和打磨后面的三十多轮对话主要是优化。包括增加开局库让AI的前几步更快更合理优化候选着法生成减少不必要的搜索分支增加难度等级通过调整搜索深度实现修复一些边界情况比如棋盘下满时的平局判定最终版本的AI在搜索深度4层的情况下每步棋的计算时间大约0.3到0.8秒棋力大概相当于业余初段水平。对于40分钟的开发时间来说这个结果我是满意的。5. 常见问题与排查技巧实录5.1 模型生成代码的典型问题在53轮对话中我总结了模型生成代码时最容易出现的几类问题问题类型具体表现排查方法解决方式参数传递错误递归函数中布尔参数传反用简单测试用例验证明确指出参数含义让模型重新生成边界条件遗漏棋盘边缘的胜负判定出错在边缘位置摆棋测试补充边界检查代码状态恢复不完整搜索后棋盘状态未还原搜索前后打印棋盘对比检查所有place/undo配对性能问题评估函数全盘扫描太慢计时测试改为预计算五连位置逻辑不一致评估函数和搜索算法对棋型理解不同构造特定局面验证统一棋型定义5.2 对话卡住了怎么办有几次模型生成的代码怎么改都不对我换了一个策略不再让它改代码而是让它“解释这段代码的执行流程”。通过让它一步步描述代码在某个具体局面下的执行过程往往能发现逻辑上的矛盾点。找到矛盾点后再针对性地让它修改成功率会高很多。另一个技巧是“降维”如果某个模块太复杂模型搞不定就把它拆成更小的函数一个一个生成。比如搜索模块一开始总是出错我把它拆成了“生成候选着法”“评估局面”“递归搜索”三个独立函数分别生成后再组装一次就通过了。5.3 本地模型的性能调优本地跑35B模型性能调优很重要。我试过几个参数调整效果比较明显批处理大小设成512比默认的512效果更好生成速度提升约20%GPU层数全部加载到GPU比部分加载快3倍以上上下文长度不要盲目设太大32K够用就行设成64K反而会拖慢速度实操心得如果发现模型生成速度突然变慢先检查上下文是不是快满了。上下文接近上限时模型的生成速度会明显下降。这时候可以开一个新对话把关键信息摘要后带过去。5.4 代码质量保障AI生成的代码质量参差不齐。我的做法是每生成一个模块立刻做三件事——运行测试、检查边界、对比接口。运行测试是基本操作检查边界是看有没有处理空棋盘、满棋盘、边缘落子等情况对比接口是确认模块之间的调用关系是否正确。另外我建议在关键模块加上类型注解和文档字符串。模型生成代码时如果要求它加类型注解代码的可读性和正确性都会提升。因为类型注解强迫模型明确每个参数和返回值的类型减少了类型相关的bug。6. 最终成果与可复用的经验总结最终跑起来的五子棋AI我让它自己和自己下了几盘棋局质量还不错有攻有守偶尔能走出一些有想法的棋。当然它离真正的棋手水平还有距离但对于一个40分钟开发出来的项目来说已经超出了我的预期。如果你也想用本地模型做类似的项目我建议从这几个方面入手选一个规则明确、模块清晰的小项目先把项目结构和接口定义好然后一个模块一个模块地生成和验证。不要贪快每一步都确认无误后再进入下一步。本地模型的上下文是有限的保持对话聚焦及时清理无关信息。另外35B这个参数量级确实是一个甜点。它足够聪明能理解复杂的算法逻辑又不会对硬件提出过分要求。如果你手头有24G显存的显卡强烈建议试试用本地35B模型来做完整的项目开发。那种“数据不出机器、全程可控”的感觉是用云端服务替代不了的。
网站建设高端定制企业官网