fix(agent/tools): surface github search error responses (#17064)

### Summary

`agent/tools/github.py` indexes `response["items"]` directly after
`requests.get(...).json()`. the github search api returns `{"message":
...}` **without** an `items` key on realistic conditions — a rate limit
(403/429; this tool sends no auth token, so the unauthenticated ~10
req/min search limit is easy to hit) or an invalid query (422). that
raised `KeyError('items')`, which the tool's retry loop then surfaced to
the model as the opaque `"GitHub error: 'items'"` instead of the real
reason.

this guards the missing key and raises the api's actual `message` (with
a clear fallback) into the existing retry/`_ERROR` path, so the model
sees e.g. `"GitHub error: API rate limit exceeded ..."`. valid responses
are unchanged.

adds `test/unit_test/agent/tools/test_github_unit.py` covering the
rate-limit response (asserts the real message is surfaced, no
`KeyError`) and the normal result path.

Co-authored-by: Yaroslav98214 <diakovichyaroslav30@gmail.com>
Co-authored-by: Haruko386 <tryeverypossible@163.com>
This commit is contained in:
Yurii214
2026-07-20 11:53:53 +02:00
committed by GitHub
parent 75a8228d33
commit aff9ff6d0a
2 changed files with 87 additions and 2 deletions

View File

@@ -0,0 +1,78 @@
#
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import agent.tools.github as gh_module
from agent.tools.github import GitHub, GitHubParam
class _FakeResp:
"""Stands in for the requests.Response; json() returns a canned payload."""
def __init__(self, payload):
self._payload = payload
def json(self):
return self._payload
def _make_tool():
# Bypass the canvas-bound __init__ (mirrors test_googlescholar.py) and stub the
# canvas-touching helpers so we can exercise _invoke's response handling. Zero the
# error delay so the failing path doesn't sleep.
g = GitHub.__new__(GitHub)
param = GitHubParam()
param.top_n = 10
param.max_retries = 0
param.delay_after_error = 0
g._param = param
g.check_if_canceled = lambda *a, **k: False
captured = {}
out = {}
def fake_retrieve(res_list, **_kw):
captured["chunks"] = list(res_list)
out["formalized_content"] = "FC"
g._retrieve_chunks = fake_retrieve
g.set_output = lambda k, v: out.__setitem__(k, v)
g.output = lambda k=None: out.get(k) if k else out
return g, captured, out
def test_rate_limit_response_surfaces_message(monkeypatch):
# Regression: the github search api returns {"message": ...} with no "items" on a
# rate limit / invalid query. The tool used to raise KeyError('items'), reported to
# the model as the opaque "GitHub error: 'items'". It must surface the real message.
monkeypatch.setattr(
gh_module.requests,
"get",
lambda *a, **k: _FakeResp({"message": "API rate limit exceeded for x.", "documentation_url": "https://docs.github.com/rest"}),
)
g, _, out = _make_tool()
result = g._invoke(query="anything")
assert "API rate limit exceeded for x." in result
assert "'items'" not in result
assert out.get("_ERROR") == "API rate limit exceeded for x."
def test_valid_response_returns_items(monkeypatch):
items = [{"name": "n", "html_url": "u", "description": "d", "watchers": 3}]
monkeypatch.setattr(gh_module.requests, "get", lambda *a, **k: _FakeResp({"items": items}))
g, captured, out = _make_tool()
g._invoke(query="anything")
assert captured["chunks"] == items
assert out["json"] == items