1. OpenHands 在 Docker 里为什么总说英文OpenHands 是一个能在容器里自主读写代码、跑命令、开浏览器的 AI 开发代理很多人用 Docker 把它跑起来之后第一反应是我明明用中文提问它却经常用英文回我甚至同一轮对话里中英混杂。这个问题在 Docker 部署场景下尤其明显因为容器内的默认提示词模板、环境变量、以及你挂载的配置文件三者会互相覆盖谁生效取决于启动顺序。我实测下来输出语言不稳定通常来自三个层面第一层是容器镜像里内置的user_prompt.j2模板它默认是英文指令第二层是环境变量比如LLM_*系列参数决定了模型走哪个通道、用什么默认行为第三层是你在 Web 界面里输入的提示词它只在单轮对话里起作用不会持久化到系统提示。很多人只改了界面提示词重启容器后又变回英文就是因为模板层没动。这篇内容适合两类人一类是刚用 Docker 跑起 OpenHands、想让对话稳定输出中文的开发者另一类是同时用好几个 AI 编码工具、Key 和 API 地址散落在各处、想统一接入通道的人。下面我会从环境变量、配置文件、提示词模板三个层面给出可复制的配置并用一次真实的中文任务对话验证输出是否稳定最后说明怎么用 TaoToken 把模型通道收敛到一处避免每换一个工具就重新配一遍 Key。2. 前置准备TaoToken 统一模型通道在改 OpenHands 的语言配置之前先把模型接入通道理顺。OpenHands 支持自定义 LLM 的 base_url 和 api_key如果你同时还在用其他编码工具每个工具各配一套 Key时间一长自己都记不清哪个 Key 对应哪个服务。TaoToken 的作用就是提供一个统一的 API 入口OpenHands、其他编码工具、脚本调用都走同一个地址和同一把 Key配置集中在一处排查问题也简单。你需要先拿到两样东西一个 API Key以及确认要用的模型名。API Key 在控制台里创建地址是 https://taotoken.net/api-keys 创建后复制保存后面写进环境变量。API 的基础地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为 base_url 使用。模型名按你实际要用的填比如常见的对话模型或编码模型具体可用列表在文档里能查到接入文档在 https://taotoken.net/doc 。这里有个容易踩的坑OpenHands 的 LLM 配置里base_url 和 api_key 是分开传的base_url 要写到/api这一层不要自己再拼/v1之类的路径否则请求会 404。我试过在 base_url 后面多加一段结果容器日志里一直报连接失败排查了半天才发现是路径拼错了。统一走 TaoToken 之后你只需要记住一个地址和一把 Key换工具时改的只是工具侧的配置通道本身不动。3. 可复制的 Docker 启动参数与 config.toml 骨架先说提示词模板这一层这是让 OpenHands 稳定输出中文最直接的手段。在本地创建一个user_prompt.j2文件内容就一行中文指令echo Always respond in 中文 ./user_prompt.j2然后启动容器时把这个文件挂载到镜像内的模板路径/app/openhands/agenthub/codeact_agent/prompts/user_prompt.j2。这样容器每次启动都会用你的模板覆盖内置英文模板系统提示层就固定成中文了。完整的 Docker 启动命令如下我把关键参数都标出来docker run -it --rm --pullalways \ -e SANDBOX_RUNTIME_CONTAINER_IMAGEdocker.all-hands.dev/all-hands-ai/runtime:0.20-nikolaik \ -e LOG_ALL_EVENTStrue \ -e LLM_BASE_URLhttps://taotoken.net/api \ -e LLM_API_KEY你的TaoToken Key \ -e LLM_MODEL你的模型名 \ -e WORKSPACE_MOUNT_PATH/home/你的用户名/你的工作目录 \ -v /home/你的用户名/你的工作目录:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ./user_prompt.j2:/app/openhands/agenthub/codeact_agent/prompts/user_prompt.j2 \ -p 3000:3000 \ --add-host host.docker.internal:host-gateway \ --name openhands-app \ docker.all-hands.dev/all-hands-ai/openhands:0.20几个参数需要单独说明。--rm表示退出容器后不保留容器本身但要注意这跟对话内容是否保存是两回事OpenHands 在对话里写的代码退出对话后不会自动留在工作目录之外所以写完代码要立刻下载到本地或推到代码仓库别指望容器帮你留着。WORKSPACE_MOUNT_PATH和对应的-v挂载是把你本地目录映射进容器方便它读写你的项目文件如果你只是测试、不需要工作目录可以把这两行连同-e WORKSPACE_MOUNT_PATH一起删掉。关于SANDBOX_USER_ID$(id -u)这一行官方文档里有但我实测时删掉了。原因是它用当前用户的权限标识比如 1000去运行 OpenHands 服务结果容器内/.openhands-state/.jwt_secret因为权限不足写不进去服务直接起不来报错就是PermissionError: [Errno 13] Permission denied: /.openhands-state/.jwt_secret。删掉这行后服务能正常启动代价是容器可能以 root 身份读写你的工作目录导致本地文件权限变化类 Unix 系统下重新chmod一下就行。如果你更习惯用配置文件而不是环境变量可以在工作目录下放一个config.toml骨架如下[core] workspace_base /opt/workspace_base [llm] model 你的模型名 base_url https://taotoken.net/api api_key 你的TaoToken Key环境变量和 config.toml 同时存在时环境变量优先级更高所以建议二选一别两边都写否则改了一处没生效会让人很困惑。4. 验证请求一次中文任务对话配置改完启动容器浏览器打开http://localhost:3000进入 OpenHands 的 Web 界面。验证方法很简单新建一个对话用中文提一个需要它动手的任务比如「在当前工作目录创建一个 hello.py打印一句中文问候然后运行它」。观察三个点。第一它的回复语言是不是中文包括思考过程、工具调用说明、最终总结。第二它执行命令时的注释和输出说明是不是中文。第三连续追问两三轮看语言会不会漂回英文。我实测下来挂载了user_prompt.j2之后整个对话过程基本稳定在中文包括它调用终端、读写文件时的说明文字。如果你在界面里看到它偶尔还是蹦英文单词先别急着改配置检查一下是不是模型本身对中文指令的遵循度问题。有些模型对系统提示的服从性弱这时候可以在对话开头再补一句「请始终用中文回复」作为单轮强化。但要注意这种界面里输入的提示词不会持久化重启容器就没了真正起长期作用的是模板层。验证通过后你可以把这次对话里用到的模型名、base_url 记下来因为后面如果换工具通道配置是复用的。TaoToken 的模型对话入口在 https://taotoken.net/models 想先单独试试模型输出中文的效果可以在那里直接对话确认模型侧没问题再回到 OpenHands 里排查配置。5. 本篇常见错误排查报错一PermissionError: [Errno 13] Permission denied: /.openhands-state/.jwt_secret这是最典型的启动失败。原因就是前面说的SANDBOX_USER_ID$(id -u)导致服务以非 root 身份运行但状态目录权限不够。解决办法是删掉这行环境变量让容器用默认身份启动。删掉后如果本地工作目录出现权限问题用sudo chown -R $(id -u):$(id -g) 你的工作目录修一下。报错二容器起来了但对话一直转圈或报连接错误先检查LLM_BASE_URL是不是写成了https://taotoken.net/api不要多加/v1或其他路径。再检查LLM_API_KEY有没有多余空格或换行。最后确认模型名拼写正确。这三项任意一项错了都会表现为连接失败或鉴权失败。报错三输出还是英文模板没生效确认挂载路径写的是/app/openhands/agenthub/codeact_agent/prompts/user_prompt.j2一个字符都不能差。另外确认本地user_prompt.j2文件确实存在且内容非空。如果用的是相对路径./user_prompt.j2要确保你执行docker run时所在目录就是文件所在目录。报错四退出容器后代码没了这是--rm加对话不持久化共同造成的。--rm删的是容器对话内容不保存是 OpenHands 自身行为。养成习惯写完代码立刻下载到本地或推到代码仓库别等退出。报错五工作目录里的文件读不出来如果你删了SANDBOX_USER_ID容器可能以 root 身份写文件导致本地用户读不了。用chmod或chown把权限改回来即可。这也是删那行参数的副作用权衡一下要么接受偶尔改权限要么保留那行但解决 jwt_secret 的权限问题。6. 把通道收敛到一处长期编码更省心语言配置解决的是输出问题通道配置解决的是接入问题两件事最好一起理顺。如果你只是偶尔跑一次 OpenHands环境变量里写死 Key 就够了但如果你长期用 AI 做编码、还同时跑其他 Agent 工具建议把模型通道统一到 TaoToken所有工具都指向同一个 base_url 和同一把 Key。这样换工具时只改工具侧通道不动排查问题时也能快速定位是工具配置错了还是通道本身有问题。长期编码和 Agent 场景可以看 Coding Plan地址是 https://taotoken.net/coding-plan 它更适合需要持续调用、多工具协同的情况。控制台在 https://taotoken.net/console API Key 管理在 https://taotoken.net/api-keys 接入文档在 https://taotoken.net/doc 。如果你用的是 Claude Code 这类工具对应的接入说明在 https://taotoken.net/claude-code 。回到 OpenHands 本身我的经验是模板层管语言环境变量管通道界面提示词只管单轮。三层各司其职别指望改一处解决所有问题。把user_prompt.j2挂好、把 TaoToken 的 base_url 和 Key 写对重启容器用中文提一个动手任务验证一遍基本就稳了。剩下的就是记得写完代码及时保存别让--rm和对话不持久化坑了你。
