Remarkable Day! 拥抱开源,从OpenHands第一个PR开始

开源之旅的起点

AI Coding的浪潮汹涌不止,OpenHands最是让我爱不释手,虽不及Copilot, Cursor知名,但技术含量感觉只高不低,给玩家了太多想象空间。OpenHands为AI应用构建提供了出色的框架,特别是在LLM调用和Agent开发上极为便捷。昨晚与一朋友Debug个问题到深夜,一筹莫展之际偶然发现了一个关于max_output_tokens参数的bug,借此良机完成了第一个PR。

问题发现与复现

在尝试通过OpenHands调用openrouter/anthropic/claude-3.7-sonnet模型时,我发现当设置max_output_tokens参数且需要生成较长响应(>4096 tokens)时,系统会陷入死循环。通过日志分析,错误表现为file_text error,导致Agent无法正常完成响应生成。

Loop Stuck

日志片段:

LLMSummarizingCondenserConfig(type='llm', llm_config=LLMConfig(model='openrouter/anthropic/claude-3.7-sonnet', 
api_key='******', base_url='', api_version=None, ... max_output_tokens=8192, ... reasoning_effort='high', seed=None), 
keep_first=4, max_size=80, max_event_length=10000)

问题链接: GitHub - OpenHands Issue #8413

技术分析与解决方案

深入源码后,定位到问题出在llm.py文件的第406-421行。OpenHands对max_output_tokens参数有特殊处理逻辑,但这个逻辑只考虑了官方API提供的claude-3-7-sonnet模型,而没有考虑OpenRouter提供的claude-3.7-sonnet模型(注意命名差异)。

问题代码段:

if 'claude-3-7-sonnet' in self.config.model:
    self.config.max_output_tokens = 64000  # litellm set max to 128k, but that requires a header to be set

修复过程:

  1. 分析了参数传递路径,确认问题在模型名称匹配逻辑
  2. 发现通过OpenRouter使用的模型命名与官方API不同(使用.而非-
  3. 扩展了条件判断,同时处理两种命名格式
  4. 编写测试用例验证修复在不同提供商情况下都能正确工作

PR中的修复代码:

if any(
    model in self.config.model
    for model in ['claude-3-7-sonnet', 'claude-3.7-sonnet']
):
    self.config.max_output_tokens = 64000  # litellm set max to 128k, but that requires a header to be set

PR链接: GitHub - OpenHands Pull Request #8415

结语

看到PR被Approved, Merged还是让人难掩兴奋的哈哈,尤其OpenHands社区响应迅速,并提供了有建设性的反馈,无怪乎OpenHands能更新迭代如此迅速。也特别欣赏项目维护者对代码质量的高要求和对细节的关注 - 即使是看似简单的一行修改,也经过了严格的审查流程(顺便学习了下Lint代码语法检测的使用哈哈)。

Remarkable Day, Help Yourself is Help Others, 未来可期!

留言与讨论