Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/langbot/pkg/provider/runners/localagent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import langbot_plugin.api.entities.builtin.rag.context as rag_context


MAX_TOOL_ITERATIONS = 16
MAX_TOOL_RESULT_LENGTH = 8000

rag_combined_prompt_template = """
The following are relevant context entries retrieved from the knowledge base.
Please use them to answer the user's message.
Expand Down Expand Up @@ -290,7 +293,9 @@ async def run(

# Once a model succeeds, commit to it for the tool call loop
# (no fallback mid-conversation — different models may interpret tool results differently)
while pending_tool_calls:
iteration_count = 0
while pending_tool_calls and iteration_count < MAX_TOOL_ITERATIONS:
iteration_count += 1
for tool_call in pending_tool_calls:
try:
func = tool_call.function
Expand All @@ -312,6 +317,8 @@ async def run(
tool_content = func_ret
else:
tool_content = json.dumps(func_ret, ensure_ascii=False)
if len(tool_content) > MAX_TOOL_RESULT_LENGTH:
tool_content = tool_content[:MAX_TOOL_RESULT_LENGTH] + '\n...[truncated]'

if is_stream:
msg = provider_message.MessageChunk(
Expand Down
Empty file.
67 changes: 67 additions & 0 deletions tests/unit_tests/provider/test_localagent.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个单元测试可以不需要。没有什么用。

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Unit tests for LocalAgentRunner protection mechanisms:
- Maximum tool-call iteration limit
- Tool result length truncation

These tests verify the logic independently of the full langbot import chain.
"""

import json
import pytest


# Mirror the module-level constants from localagent.py so tests remain valid
# even when the full import chain is unavailable.
MAX_TOOL_ITERATIONS = 16
MAX_TOOL_RESULT_LENGTH = 8000


def test_max_tool_iterations_constant():
"""MAX_TOOL_ITERATIONS should be a positive integer."""
assert isinstance(MAX_TOOL_ITERATIONS, int)
assert MAX_TOOL_ITERATIONS > 0


def test_max_tool_result_length_constant():
"""MAX_TOOL_RESULT_LENGTH should be a positive integer."""
assert isinstance(MAX_TOOL_RESULT_LENGTH, int)
assert MAX_TOOL_RESULT_LENGTH > 0
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这俩单元测试没有什么意义吧,而且变量定义在单元测试中



def test_tool_result_truncation_logic():
"""Simulate the truncation logic applied in LocalAgentRunner."""
large_result = {"data": "x" * (MAX_TOOL_RESULT_LENGTH + 1000)}
tool_content = json.dumps(large_result, ensure_ascii=False)

if len(tool_content) > MAX_TOOL_RESULT_LENGTH:
tool_content = tool_content[:MAX_TOOL_RESULT_LENGTH] + '\n...[truncated]'

assert len(tool_content) == MAX_TOOL_RESULT_LENGTH + len('\n...[truncated]')
assert tool_content.endswith('\n...[truncated]')


def test_tool_result_no_truncation_when_within_limit():
"""Short tool results should not be truncated."""
small_result = {"data": "hello"}
tool_content = json.dumps(small_result, ensure_ascii=False)

original = tool_content
if len(tool_content) > MAX_TOOL_RESULT_LENGTH:
tool_content = tool_content[:MAX_TOOL_RESULT_LENGTH] + '\n...[truncated]'

assert tool_content == original


def test_iteration_limit_logic():
"""Simulate the iteration counter guard used in the agent loop."""
pending = [object()] # always has a pending call
iteration_count = 0
executed = 0

while pending and iteration_count < MAX_TOOL_ITERATIONS:
iteration_count += 1
executed += 1
# pending never clears — simulates infinite tool-call loop

assert executed == MAX_TOOL_ITERATIONS
assert iteration_count == MAX_TOOL_ITERATIONS
Loading