Skip to content

[问题/Issue] 章节7.4:my_simple_agent.py 中的 MySimpleAgent 类在调用 LLM 后,错误地将 LLMResponse 对象当作字符串处理,导致多个类型错误。 #413

@StudentZhou

Description

@StudentZhou

1. 遇到问题的章节 / Affected Chapter

Chapter7.4

2. 问题类型 / Issue Type

代码错误 / Code Error

3. 具体问题描述 / Problem Description

问题现象

my_simple_agent.py 中的 MySimpleAgent 类在调用 LLM 后,错误地将 LLMResponse 对象当作字符串处理,导致多个类型错误。

错误代码分析

错误位置 1:run 方法(第 51行)

def run(self, input_text: str, max_tool_iterations: int = 3, **kwargs) -> str:
   if not self.enable_tool_calling:
            response = self.llm.invoke(messages, **kwargs)  # ← 返回 LLMResponse 对象
            #print(response.content)
            self.add_message(Message(input_text, "user"))
            # ❌ 错误:直接传递 LLMResponse 对象给 Message
            self.add_message(Message(response, "assistant"))
            print(f"✅ {self.name} 响应完成")
            return response

错误位置 2:_run_with_tools 方法(第 92 行)

def _run_with_tools(self, messages: list, input_text: str, max_tool_iterations: int, **kwargs) -> str:
    while current_iteration < max_tool_iterations:
        response = self.llm.invoke(messages, **kwargs)  # ← 返回 LLMResponse 对象
        
        # ❌ 错误:直接传递 LLMResponse 对象给 _parse_tool_calls
        tool_calls = self._parse_tool_calls(response)

错误位置 3:_run_with_tools 方法(第 98、104 行)

if tool_calls:
    tool_results = []
    clean_response = response  # ❌ 错误:赋值为 LLMResponse 对象,不是字符串

    for call in tool_calls:
        result = self._execute_tool_call(call['tool_name'], call['parameters'])
        tool_results.append(result)
        # ❌ 错误:LLMResponse 对象没有 replace() 方法
        clean_response = clean_response.replace(call['original'], "")

根本原因分析

LLMResponse 类定义(有 __str__() 方法)

# hello_agents/core/llm_response.py
@dataclass
class LLMResponse:
    content: str
    model: str
    # ... 其他字段

    def __str__(self) -> str:
        """向后兼容:直接打印返回content"""
        return self.content  # ✅ 定义了 __str__

为什么 __str__() 没有被自动调用?

调用场景 是否调用 __str__() 原因
print(response) ✅ 是 print 会自动调用 str()
str(response) ✅ 是 显式类型转换
f"{response}" ✅ 是 f-string 自动调用 str()
re.findall(pattern, response) ❌ 否 不会自动转换,直接检查类型

re.findall() 的行为

import re

# re.findall() 会检查参数类型
matches = re.findall(pattern, text)  # text 必须是 str 或 bytes

# 如果传入 LLMResponse 对象
matches = re.findall(pattern, llm_response)  # ❌ TypeError

错误堆栈

####第一个错误(第51行)

Traceback (most recent call last):
  File "f:\code\hello-agents\code\chapter7\test_simple_agent.py", line 21, in <module>
    response1 = basic_agent.run("你好,请介绍一下自己")
                ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 51, in run
    self.add_message(Message(response, "assistant"))
                     ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\ProgramData\anaconda3\envs\hello-agent\Lib\site-packages\hello_agents\core\message.py", line 18, in __init__
    super().__init__(
    ~~~~~~~~~~~~~~~~^
        content=content,
        ^^^^^^^^^^^^^^^^
    ...<2 lines>...
        metadata=kwargs.get('metadata', {})
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "D:\ProgramData\anaconda3\envs\hello-agent\Lib\site-packages\pydantic\main.py", line 250, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Message
content
  Input should be a valid string [type=string_type, input_value=LLMResponse(model=deepsee...210, content_length=353), input_type=LLMResponse]
    For further information visit https://errors.pydantic.dev/2.12/v/string_type

第二个错误(第 92 行)

Traceback (most recent call last):
  File "f:\code\hello-agents\code\chapter7\test_simple_agent.py", line 38, in <module>
    response2 = enhanced_agent.run("请帮我计算 15 * 8 + 32")
                ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 56, in run
    return self._run_with_tools(messages, input_text, max_tool_iterations, **kwargs)
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 92, in _run_with_tools
    tool_calls = self._parse_tool_calls(response)
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 134, in _parse_tool_calls
    matches = re.findall(pattern, text)
  File "D:\ProgramData\anaconda3\envs\hello-agent\Lib\re\__init__.py", line 278, in findall
    return _compile(pattern, flags).findall(string)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
TypeError: expected string or bytes-like object, got 'LLMResponse'

第三个错误(第 104 行,修复第一个后出现)

Traceback (most recent call last):
  File "f:\code\hello-agents\code\chapter7\test_simple_agent.py", line 38, in <module>
    response2 = enhanced_agent.run("请帮我计算 15 * 8 + 32")
                ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 56, in run
    return self._run_with_tools(messages, input_text, max_tool_iterations, **kwargs)
  File "f:\code\hello-agents\code\chapter7\my_simple_agent.py", line 104, in _run_with_tools
    clean_response = clean_response.replace(call['original'], "")
                     ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'LLMResponse' object has no attribute 'replace'

### 4. 问题重现材料 / Reproduction Materials

### 错误文件路径
`F:\code\hello-agents\code\chapter7\my_simple_agent.py` 


### 测试代码
```python
# test_simple_agent.py (第 24-39 行)
print("=== 测试2:工具增强对话 ===")
tool_registry = ToolRegistry()
calculator = CalculatorTool()
tool_registry.register_tool(calculator)

enhanced_agent = MySimpleAgent(
    name="增强助手",
    llm=llm,
    system_prompt="你是一个智能助手,可以使用工具来帮助用户。",
    tool_registry=tool_registry,
    enable_tool_calling=True
)

response2 = enhanced_agent.run("请帮我计算 15 * 8 + 32")  # ← 触发错误
print(f"工具增强响应: {response2}\n")

预期行为 vs 实际行为

预期行为

  • response.content 被正确提取并传递给 _parse_tool_calls()
  • 正则表达式解析成功,找到工具调用标记
  • 字符串替换操作成功执行
  • 工具正常调用并返回结果

实际行为

  • LLMResponse 对象被直接传递,触发 TypeError
  • 或者字符串操作失败,触发 AttributeError

环境信息

  • Python 版本: 3.13.11
  • hello-agents 版本: 1.0.0
  • 操作系统: Windows 11 (Version 10.0.26200)

5. 补充信息 / Additional Information

为什么 LLMResponse 定义了 __str__() 但不起作用?

Python 的 __str__() 调用规则

class LLMResponse:
    def __str__(self):
        return self.content

# ✅ 会调用 __str__()
print(response)         # → "response.content"
str(response)         # → "response.content"
f"{response}"          # → "response.content"

# ❌ 不会调用 __str__()
len(response)          # → TypeError
re.findall(pattern, response)  # → TypeError (直接检查类型)
response.replace(...)  # → AttributeError (直接查找方法)

关键点: __str__() 只在 Python 需要字符串表示时被调用,但不会在期望特定类型时自动转换。

正确的修复方案

直接使用 response.content 属性(推荐)

# 修复位置 1:第 92 行
self.add_message(Message(response.content, "assistant"))  # ✅ 使用 content 属性


# 修复位置 1:第 92 行
tool_calls = self._parse_tool_calls(response.content)  # ✅ 使用 content 属性

# 修复位置 2:第 98 行
clean_response = response.content  # ✅ 使用 content 属性

### 确认事项 / Verification

- [x] 我已阅读过相关章节的文档 / I have read the relevant chapter documentation
- [x] 我已搜索过现有的Issues,确认此问题未被报告 / I have searched existing Issues and confirmed this hasn't been reported
- [x] 我已尝试过基本的故障排除(如重启重新安装依赖等) / I have tried basic troubleshooting (restart, reinstall dependencies, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions