资讯动态

用GitHub Actions跑开源AI Agent框架:设计、实战与避坑

发布时间:2026/10/18 7:46:51 来源:尧图企业网站定制
如果你关注过GitHub上的自动化玩法应该已经注意到一个很有意思的趋势越来越多的开源AI Agent框架直接把运行时环境塞进了GitHub Actions里。GitHub Actions本来是干CI/CD的跑编译、跑测试、发版但现在有人用它当成“免费的主机”来跑“懂得自己决定下一步干什么”的AI Agent而且跑得还挺顺。这个项目标题我在社区里看到不止一次今天专门拆开讲讲用GitHub Actions当底座来跑开源AI Agent框架到底图什么、怎么设计、怎么落地、坑在哪里。这个话题适合三类人第一类是自己有开源仓库想用AI自动答复Issue、自动更新文档、自动发周报的第二类是手里没有服务器但想低成本跑一个定时Agent做信息收集或内容生成又不想掏钱买云主机的第三类是研究Agent框架设计想搞清楚“短生命周期内无状态Agent怎么做任务闭环”的。无论哪一类这篇文章里都会有不少能直接抄作业的东西。1. 为什么要把Agent塞进GitHub Actions1.1 Actions天然就是一台“事件驱动的小机器”先说清楚GitHub Actions是什么。它是Github自带的持续集成/持续部署平台你只需要在仓库里放一个.github/workflows/xxx.yml文件它就能在特定事件发生时拉起一台干净的Linux虚拟机按你写的步骤执行命令。这些“特定事件”本身就很有意思push代码、Pull Request创建、Issue被评论、定时器到点、手动点击运行全都支持。这意味着Agent的“苏醒方式”不用自己实现Github已经帮你做好了整个事件驱动框架。对Agent来说这等于你不需要自己维护消息队列、不搞监听服务、不用处理Webhook回调只要写好工作流触发条件Agent到点自动跑。我最早接触这个思路时最直观的感受是以前写自动化脚本还要配crontab、配守护进程、配日志轮转现在全部省掉平台层全管了。1.2 免费额度对于轻量Agent来说够用公共仓库的Actions免费额度对于跑轻量Agent任务来说是足够充裕的不同账号计划的额度有差异以GitHub官方当前说明为准。比如你写一个Agent每天定时跑一次执行时间大概两三分钟每个月的消耗其实很少。即便你触发频繁一点公共仓库也有相当可观的免费分钟数。这点很关键。不是所有人都有云服务器也不是所有Agent任务都值得单独开一台机器。像“每天抓一次行情数据生成摘要”“每周汇总一次仓库Issue并草拟回复”“每次Push后让Agent检查一下代码里的敏感信息”这种轻量任务专门买服务器是浪费但丢到Actions里跑就是零成本。当然免费额度要节制使用。我自己制定过一个简单的用量策略Agent单次执行时间控制在10分钟以内每天不超过两次。碰到需要跑批量任务时宁可拆成多个工作流分开跑也不让单次运行时间失控。1.3 隔离环境是好事也是约束GitHub Actions的每个job都运行在一个全新的一次性虚拟机上跑完销毁下回再启动又是一台干净机器。这个设计对Agent有一个很大的好处不管Agent在里面怎么折腾装了什么依赖、改了什么文件、执行了什么危险命令跑完就全没了不会污染你的本地环境。反过来这种“无状态”也带来约束。Agent在两次运行之间如果想保留记忆、保存历史数据不能依赖本地磁盘必须主动把数据写回仓库、上传Artifact或者存到外部存储。我在设计框架时干脆把“无状态”当作第一性原则来对待每一轮Agent运行都是一次全新的任务任务开始前从仓库拉取持久化数据任务结束后把新结果提交回去或上传产物。想明白这一点很多设计决策就顺了。2. 这个Agent框架的核心设计2.1 整体架构事件触发、Agent循环、工具调用、产物沉淀我把整个框架拆成四层层与层之间职责很干净第一层是触发层负责决定Agent什么时候醒来。用的是GitHub Actions的on字段比如schedule定时、issue_comment评论触发、workflow_dispatch手动触发。这一层不需要写代码完全靠YAML配置。第二层是Agent运行时负责“思考与决策循环”。它的核心逻辑是把System Prompt、用户任务和历史消息组装成上下文发给大模型API模型决定是直接输出最终结果还是调用某个工具获取信息如果调工具Agent把工具执行结果作为新的消息再塞给模型继续下一轮。这就是目前主流的“ReAct循环”或“Function Calling循环”实现起来不算复杂核心代码也就一二百行。第三层是工具层负责让Agent能对世界产生影响。我给Agent暴露的工具不多通常就三个执行Shell命令、读取仓库文件、写文件并提交。看起来简单但覆盖面很广。因为Shell命令本身是万能的Agent只要会构造命令就能做很多事情比如抓网页、解析JSON、算数据、生成图表。第四层是产物层负责保存Agent的执行成果。要么通过git commit把生成的文件推回仓库要么用upload-artifact把文件上传到Actions的Artifact区域要么调用外部API存到对象存储。我建议小任务提交回仓库因为后续查阅最方便。2.2 工具即函数给模型一把受控的Shell工具层的设计是整个框架的灵魂。我给大模型暴露的工具定义大概是Shell工具接收一个字符串类型的参数值是命令本身返回stdout、stderr和退出码。模型看到这样的工具定义之后会自己去组装命令比如要查日期就执行date要下载网页就用curl要统计代码行数就用find和wc。有人会担心让大模型自由执行Shell命令是不是太危险这个担心不无道理但放在GitHub Actions的隔离环境里风险是可控的。毕竟这个环境本身就是一次性VM里面没有任何生产数据也不存在连接内网的可能性。就算Agent把整个磁盘格式化了损失也就是这一轮任务重跑一遍而已。不过我仍然建议在工具层做三道防护。第一道默认禁掉rm -rf /这类危险命令和git push以外的网络写操作用命令黑名单过滤第二道每个工具调用都要有超时时间防止模型生成一个死循环命令把工作流跑死第三道给工具增加一个最大调用次数限制默认10次防止模型在一个任务里无限循环消耗API费用。2.3 上下文与记忆管理的取舍无状态环境下Agent的上下文管理要特别讲究。模型API的输入长度有限制而Agent一轮任务中可能会积累大量工具返回内容。比如让Agent抓10个网页每个网页几十KB如果你把完整内容全部塞进messages里第二轮请求可能就直接超出上下文长度上限。我的做法是两层过滤第一层是每轮工具调用后只保留工具返回内容的截断版本默认只留1000字符超出部分提示模型“内容过长已截断如有需要请用更精确的命令缩小范围”第二层是每轮对话结束前做一轮摘要压缩把前面所有对话浓缩成一个新的system消息再清空历史。这个小技巧非常实用能让一个只有128K上下文的小模型也能完成多步长任务。2.4 为什么不用别人封装好的全家桶社区里已经有不少开源Agent框架但我没有直接套别人的全家桶而是写了一个极简内核。原因有两条。第一条GitHub Actions执行环境本质上很朴素不需要分布式调度、不需要复杂的状态管理、不需要多Agent通信总线。引入这些重量级依赖只会让工作流启动变慢、出错概率变大对跑轻量任务毫无帮助。第二条极简内核出问题时好排查。代码就只依赖一个requests库安装依赖只要两三秒逻辑链路一眼能看到头。真出了问题看日志直接定位到是模型没返回格式、还是工具执行报错、还是Promote没写清楚。这对后续长期维护来说比一个黑盒框架有价值得多。3. 实操从零搭一个能跑通的Agent工作流3.1 仓库结构准备我建议整个仓库只保留必要的文件不要塞一堆无关代码这样工作流启动快模型看到上下文也干净。一个完整的最小仓库结构是这样agent-runner/ ├── .github/ │ └── workflows/ │ ├── agent-scheduled.yml # 定时任务 │ └── agent-manual.yml # 手动触发任务 ├── agent/ │ ├── __init__.py │ ├── core.py # Agent循环核心逻辑 │ ├── tools.py # 工具定义与执行器 │ └── prompts.py # System Prompt 配置 ├── data/ # Agent读写的数据目录 │ └── .gitkeep ├── requirements.txt # 依赖只有requests └── README.md注意data目录要放一个.gitkeep文件并且确保它被git跟踪。原因是Actions checkout代码时空目录会被自动跳过如果data目录不存在Agent运行时写文件就会报目录不存在。3.2 工作流配置示例先看定时触发的工作流这段配置我实测下来很稳name: scheduled-agent on: schedule: - cron: 30 8 * * * # 每天 UTC 8:30 执行 workflow_dispatch: # 支持手动触发 permissions: contents: write # 允许Agent提交文件回仓库 jobs: run-agent: runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: 拉取仓库代码 uses: actions/checkoutv4 - name: 安装Python uses: actions/setup-pythonv5 with: python-version: 3.12 - name: 安装依赖 run: pip install -r requirements.txt - name: 运行Agent env: LLM_API_KEY: ${{ secrets.LLM_API_KEY }} LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }} LLM_MODEL_NAME: ${{ secrets.LLM_MODEL_NAME }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: python -m agent.core - name: 上传输出产物 if: always() uses: actions/upload-artifactv4 with: name: agent-output path: data/这里有两个细节值得说明。第一个是permissions字段如果你不显式写contents: write默认的GITHUB_TOKEN只有只读权限Agent想git commit推回仓库时会被拒绝报错信息是“Permission denied”。当时浪费了我不少时间排查。第二个是timeout-minutes我建议给每个跑Agent的job都设置一个上限。没有这个字段时GitHub Actions允许任务跑很长时间一旦Agent进入死循环或者外部API迟迟不响应整个job会一直挂着消耗你的Actions额度。实测把超时卡在15分钟是合理的选择即使是最复杂的任务都足够跑完了。3.3 Agent核心循环代码实现Agent的运行时核心是一个最多迭代10次的循环。每次循环做三件事请求模型得到回复判断回复里是否带工具调用请求有就执行工具并继续下一轮没有就输出最终结果并跳出循环。核心代码加注释也就八十行import json import logging import os import requests from .prompts import SYSTEM_PROMPT from .tools import TOOL_SCHEMAS, execute_tool logger logging.getLogger(agent) def run_once(task_message: str) - str: messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: task_message}, ] endpoint os.environ[LLM_ENDPOINT] api_key os.environ[LLM_API_KEY] model os.environ[LLM_MODEL_NAME] headers { Authorization: fBearer {api_key}, Content-Type: application/json, } for step in range(10): logger.info(agent step %d start, message count %d, step 1, len(messages)) payload { model: model, messages: messages, tools: TOOL_SCHEMAS, tool_choice: auto, } try: resp requests.post(endpoint, headersheaders, jsonpayload, timeout60) except requests.exceptions.Timeout: logger.error(model request timeout in step %d, step) return 模型请求超时任务中止 if resp.status_code ! 200: logger.error(model status %s, body %s, resp.status_code, resp.text[:500]) return f模型接口异常状态码{resp.status_code} model_message resp.json()[choices][0][message] messages.append(model_message) tool_calls model_message.get(tool_calls) if not tool_calls: return model_message.get(content, ) for call in tool_calls: fn_name call[function][name] fn_args json.loads(call[function][arguments]) logger.info(tool call %s, args %s, fn_name, fn_args) output execute_tool(fn_name, fn_args) messages.append({ role: tool, tool_call_id: call[id], content: json.dumps(output, ensure_asciiFalse), }) # 每轮工具调用后做一次上下文截断避免消息太长 messages _truncate_context_if_needed(messages) return 达到最大迭代次数任务自动结束 def _truncate_context_if_needed(messages): total_chars sum(len(m.get(content, )) for m in messages) if total_chars 8000: summarized {role: system, content: 前序上下文已截断请基于剩余信息继续任务} return [summarized] messages[-6:] return messages如果不上代码块这段逻辑也挺好解释的模型返回来的消息里有tool_calls字段就说明它想调用工具每个tool call都有一个id模型后续会通过这个id匹配工具执行结果。执行完工具之后把结果按roletool塞回messages再发给模型它就会基于工具结果继续思考。3.4 工具执行器让Shell成为Agent的手脚工具定义部分我直接用的OpenAI兼容格式主流模型服务商基本都支持这套协议不用额外引进依赖。一个Shell工具的schema长这样TOOL_SCHEMAS [ { type: function, function: { name: run_shell, description: 在当前执行环境执行一条shell命令返回stdout、stderr和退出码。适用于查询信息、下载文件、运行脚本等。, parameters: { type: object, properties: { command: { type: string, description: 要执行的完整shell命令, } }, required: [command], }, }, } ]对应的执行函数import shlex import subprocess BLOCKED_PATTERNS [rm -rf /, mkfs, :(){ :|: };:] # 非常基础的危险命令黑名单 def execute_tool(fn_name, args): if fn_name run_shell: command args[command] for pattern in BLOCKED_PATTERNS: if pattern in command: return {error: command blocked by policy} try: proc subprocess.run( command, shellTrue, capture_outputTrue, textTrue, timeout60, cwddata, # 强制在data目录下运行减少误伤仓库文件 ) return { stdout: proc.stdout[-2000:], stderr: proc.stderr[-1000:], exit_code: proc.returncode, } except subprocess.TimeoutExpired: return {error: tool timeout, exit_code: -1} return {error: funknown tool: {fn_name}}这里有一行代码值得单独说cwddata。把工具的默认工作目录设置为data目录意味着Agent想下载文件、生成报告、创建临时脚本时都会落在data文件夹里不会弄乱仓库根目录。后续提交改动时可以很安全地只git add data目录而不担心提交到乱七八糟的东西。3.5 密钥配置与日志观察大模型的API密钥不能写死在代码或工作流文件里必须配置到仓库的Secrets中。路径是仓库Settings - Secrets and variables - Actions - New repository secret。需要配置的密钥包括API Key、接口地址、模型名。运行Agent时这些密钥会通过env字段注入为环境变量。这里有个安全细节GitHub Actions会把Secrets的值自动做日志脱敏即使你的Agent代码意外把密钥打印到stdoutActions的日志界面显示出来也是***。但不代表可以肆无忌惮地打日志只是说明平台层有一道兜底。看日志是排查Agent行为的最直观手段。Actions运行记录页面的每个step旁边有一个日志按钮点开能看到完整输出。我给自己的框架加的比较实用的小功能是让每个步骤打印消息数量和Token用量估计值一眼就能看出Agent有没有陷入“来回调用工具却不产出结果”的循环。4. 常见问题与排查技巧实录4.1 schedule定时任务不触发先查这两件事定时任务不触发是玩Actions最经典的坑通常原因有两个。第一schedule的cron表达式跑的是UTC时间而不是本地时间。如果你本来想每天早上八点跑写了cron: 0 8 * * *实际触发时间是北京时间下午四点。解决方案是先搞清楚当前UTC时间再逆推写表达式或者干脆依赖workflow_dispatch手动触发来测试。第二GitHub官方对schedule触发有一个“最短间隔限制”和“延迟容忍机制”。如果两个任务之间的触发间隔小于最短限制后一个会被跳过当仓库活跃度低时定时任务也可能被延后执行最长可能晚十几分钟甚至更久。所以不要在定时Agent上等待严格准时的行为它更适合“每天大概跑一次”这种容错任务。我实测一个稳定跑了好几个月的定时任务触发的实际时间是计划时间的前后半小时内波动这对于生成日报、汇总信息的场景完全够用。4.2 模型API超时与限流处理GitHub Actions runner的网络环境偶尔会出现连接模型API超时的情况特别是当模型服务商在海外的网络节点抽风时。如果你直接把超时异常抛出来工作流会直接失败而Agent任务明明还没有开始执行非常浪费。我的处理方式是给每个模型请求设置一个相对短的超时时间60秒但对整个任务做重试机制把run_once函数包在一个最多重试3次的循环里每次重试之间sleep 30秒。实测这个策略把API偶发抖动导致的整体失败率降到了接近于零。另外一个比较容易被忽略的点是rate limit。有些模型API按账号维度限流如果同一个Key在多个仓库的Actions任务里并发调用很容易触发限流。这种情况可以错开定时任务的执行时间或者在代码里做简单的并发锁比如在data目录下放一个lock文件抢不到锁就退出。4.3 密钥泄露比想象中更容易发生很多人以为密钥存在Secrets里就万事大吉但泄露路径仍然存在。最常见的一条是Agent自己打印环境变量或者调用工具时把env输出到stdout。虽然Actions会对Secrets值做mask但如果你把Secret拼进一个更长的字符串里比如打印“api key is xxx”脱敏就在某些情况下失效。我踩过一次真实的坑Agent在执行shell命令时运行了env命令把所有环境变量打到了stdout。当时因为Actions的脱敏机制API Key本身被打成了***但拼接了其他字符的部分拼串泄露了一段中间字符足以让人拼出完整Key。后来我在框架里加了两层防护第一层在Agent代码里禁止打印任何环境变量第二层在模型工具描述里明确加上一条系统提示“不要向用户展示环境变量内容”。虽然这种约束不是百分百可靠但在AI Agent场景下提醒模型注意安全边界本身就是一种有效的防线。4.4 工具执行失败导致整个工作流崩溃当Agent调用的shell命令返回非零退出码时框架默认会把它当作工具结果返回给模型而不是直接抛异常。这个设计很关键。因为模型收到“命令失败了退出码是127”这类结果后通常会自动修正命令再试一次。如果框架在命令失败时直接抛异常终止Agent就失去了“自我修正”的机会。只有一种情况例外工具本身超时。超时通常意味着命令陷入了死循环或者在做无意义的等待继续让模型重试也是浪费时间。这种情况我会让工具返回error字段并在Agent循环逻辑里增加一个“连续两轮工具调用都报错则提前终止”的保护机制。5. 进阶玩法扩展5.1 让Agent直接回复Issue评论在基础的Agent框架上接入issue_comment事件就能做到“用户评论触发Agent回复”。工作流的触发方式改为on: issue_comment: types: [created]然后在Agent启动时通过GITHUB_TOKEN调用GitHub API获取当前Issue的内容和评论上下文把评论内容组装成任务消息Agent生成回复后再调用API把回复发布为新的评论。这个过程充分体现了Actions事件驱动Agent的优势用户问问题Agent自动应答全程无需人工干预。需要控制的是触发条件避免每条评论都触发Agent跑一轮。比较稳妥的做法是在Agent端解析评论内容只有包含特定关键字比如“agent”或“/ai”前缀时才真正执行任务其余评论直接忽略。5.2 定时生成报告并自动提交PR这个玩法我一直在用效果很好。工作流每天定时触发AgentAgent执行逻辑是读取仓库里的数据、生成一份Markdown格式报告、把报告写入data/report目录、然后用git commit和git push把改动推送到一个新分支最后调用GitHub API创建Pull Request。这个过程完全由Agent自主调用工具完成。模型需要连续执行多个shell命令git checkout -b、git add、git commit、git push、curl创建PR。因为每个命令的输出都会反馈给模型它能根据输出判断下一步动作。实测下来只要System Prompt里写清楚了执行顺序和PR模板格式Agent的完成率非常高。比较有意思的是这种“Agent生成PR、人工review后合并”的流程和现代软件开发里的人为评审流程正好契合。AI负责批量产出人类负责关键决策。5.3 多Agent协作的轻量实现在GitHub Actions上实现多Agent协作不需要分布式通信中间件。我的做法是用文件系统作为“共享黑板”一个工作流里跑两个步骤第一步运行Agent A负责数据收集并写入data/collection.json第二步运行Agent B读取这个文件负责分析并生成最终报告。Agent A的输出就是Agent B的输入。这种“顺序流水线”模式的好处是每步职责单一出问题好排查。如果哪天任务量大到单台runner跑不完也可以把两个Agent拆成两个独立工作流通过创建Issue传递数据不过那就有点绕了暂时用不上。6. 一些个人经验总结跑GitHub Actions上的Agent框架这件事我断断续续玩了挺长时间最大的体会是“限制反而催生简洁设计”。因为环境无状态、执行有时长限制、API调用有成本所以你不能像在自己服务器上那样写一个常驻进程让它慢慢思考必须把事情拆小、把任务目标写清楚、把工具边界设明确。给新手的建议很直接不要一开始就追求复杂的多Agent编排框架先让一个Agent跑通一条最简单的定时任务比如“每天早上抓一次某个API的数据整理成表格推回仓库”。链路通了之后再慢慢加工具、加事件触发、加多步协作。层层叠加踩坑一次解决一次这套体系就会越来越顺手。如果你也在GitHub Actions上折腾AI Agent希望这篇内容能帮你少走点弯路。框架的骨架就摆在那里剩下的具体玩法完全可以按自己的需求往上长。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑