mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
fix(skills): exclude pull requests from GitHub issue research (#7516)
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
30b3a012cb
commit
5ca17829be
@ -1729,6 +1729,8 @@ File reads accept raw repository-relative paths and preserve spaces, Unicode,
|
||||
`#`, `?`, and literal percent signs with either HTTP client.
|
||||
Directory listings ask for the repository's own default branch, so a repository that
|
||||
uses neither `main` nor `master` reports its current tree instead of a stale branch.
|
||||
The `issues` command excludes pull requests and follows additional pages to fill
|
||||
its requested issue limit; the `prs` command remains available for pull requests.
|
||||
|
||||
Users can explicitly activate an enabled skill for a single turn by starting the request with `/skill-name`, for example `/data-analysis analyze uploads/foo.csv`. DeerFlow loads that skill's `SKILL.md` as hidden current-turn context while leaving the base prompt limited to skill metadata. Slash activation respects disabled skills, custom-agent skill whitelists, and existing channel commands such as `/new` and `/help`.
|
||||
|
||||
|
||||
@ -56,6 +56,8 @@ python /path/to/skill/scripts/github_api.py <owner> <repo> tree
|
||||
- prs
|
||||
- releases
|
||||
|
||||
The `issues` command returns repository issues only, excluding pull requests. It follows additional pages when needed to fill the requested issue limit; use `prs` to research pull requests separately.
|
||||
|
||||
**Round 2 - Discovery (3-5 web_search)**
|
||||
- Get overview and identify key terms
|
||||
- Find official website/repo
|
||||
|
||||
@ -172,16 +172,28 @@ class GitHubAPI:
|
||||
labels: Optional[str] = None,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get repository issues.
|
||||
Get repository issues, excluding pull requests.
|
||||
|
||||
Args:
|
||||
state: 'open', 'closed', or 'all'
|
||||
labels: Comma-separated label names
|
||||
"""
|
||||
params = {"state": state, "per_page": min(limit, 100)}
|
||||
limit = min(limit, 100)
|
||||
if limit <= 0:
|
||||
return []
|
||||
params = {"state": state, "per_page": limit}
|
||||
if labels:
|
||||
params["labels"] = labels
|
||||
return self._get(f"/repos/{owner}/{repo}/issues", params)
|
||||
issues = []
|
||||
page = 1
|
||||
while len(issues) < limit:
|
||||
items = self._get(f"/repos/{owner}/{repo}/issues", params)
|
||||
issues.extend(item for item in items if "pull_request" not in item)
|
||||
if len(items) < limit:
|
||||
break
|
||||
page += 1
|
||||
params["page"] = page
|
||||
return issues[:limit]
|
||||
|
||||
def get_pull_requests(
|
||||
self, owner: str, repo: str, state: str = "all", limit: int = 30
|
||||
|
||||
@ -29,7 +29,7 @@ def fallback_client(monkeypatch):
|
||||
finally:
|
||||
connection.close()
|
||||
captured.update(url=request.full_url, timeout=timeout, headers=request.headers)
|
||||
response = io.BytesIO(b'{"ok": true}')
|
||||
response = io.BytesIO(captured.get("body", b'{"ok": true}'))
|
||||
response.status = 200
|
||||
return response
|
||||
|
||||
@ -102,10 +102,11 @@ def test_fallback_search_preserves_query(fallback_client, query):
|
||||
|
||||
def test_fallback_issues_preserves_filters(fallback_client):
|
||||
client, captured = fallback_client
|
||||
captured["body"] = b'[{"number": 1}]'
|
||||
|
||||
assert client.get_issues(
|
||||
"owner", "repo", state="open", limit=10, labels="bug,help wanted"
|
||||
) == {"ok": True}
|
||||
) == [{"number": 1}]
|
||||
|
||||
assert parse_qs(urlsplit(captured["url"]).query) == {
|
||||
"state": ["open"],
|
||||
|
||||
127
tests/skills/test_github_research_issues.py
Normal file
127
tests/skills/test_github_research_issues.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""Keep pull requests out of repository issue research, across both transports."""
|
||||
|
||||
import io
|
||||
import json
|
||||
import runpy
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(params=["requests", "urllib"])
|
||||
def issue_client(request, monkeypatch):
|
||||
if request.param == "urllib":
|
||||
monkeypatch.setitem(sys.modules, "requests", None)
|
||||
script = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "skills/public/github-deep-research/scripts/github_api.py"
|
||||
)
|
||||
module = runpy.run_path(str(script))
|
||||
pages = []
|
||||
calls = []
|
||||
|
||||
if request.param == "urllib":
|
||||
|
||||
def fake_urlopen(prepared, timeout):
|
||||
calls.append(prepared.full_url)
|
||||
payload, status = pages.pop(0)
|
||||
data = json.dumps(payload).encode()
|
||||
if status >= 400:
|
||||
raise urllib.error.HTTPError(prepared.full_url, status, "error", {}, io.BytesIO(data))
|
||||
response = io.BytesIO(data)
|
||||
response.status = status
|
||||
return response
|
||||
|
||||
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
|
||||
else:
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
def fake_send(session, prepared, **kwargs):
|
||||
calls.append(prepared.url)
|
||||
payload, status = pages.pop(0)
|
||||
response = requests.Response()
|
||||
response.status_code = status
|
||||
response._content = json.dumps(payload).encode()
|
||||
return response
|
||||
|
||||
monkeypatch.setattr(requests.Session, "send", fake_send)
|
||||
|
||||
return module["GitHubAPI"](), pages, calls
|
||||
|
||||
|
||||
def test_issue_limit_counts_issues_instead_of_pull_requests(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.extend([
|
||||
([{"number": 10, "pull_request": {}}, {"number": 9}], 200),
|
||||
([{"number": 8, "pull_request": {}}, {"number": 7}], 200),
|
||||
])
|
||||
|
||||
assert client.get_issues("owner", "repo", state="open", limit=2, labels="bug,help wanted") == [{"number": 9}, {"number": 7}]
|
||||
assert len(calls) == 2
|
||||
for index, url in enumerate(calls):
|
||||
assert urlsplit(url).path == "/repos/owner/repo/issues"
|
||||
parameters = parse_qs(urlsplit(url).query)
|
||||
assert parameters["state"] == ["open"]
|
||||
assert parameters["labels"] == ["bug,help wanted"]
|
||||
assert parameters["per_page"] == ["2"]
|
||||
assert parameters.get("page", ["1"]) == [str(index + 1)]
|
||||
|
||||
|
||||
def test_issue_search_continues_after_a_page_of_only_pull_requests(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.extend([
|
||||
([{"number": 5, "pull_request": {}}, {"number": 4, "pull_request": {}}], 200),
|
||||
([{"number": 3}], 200),
|
||||
])
|
||||
|
||||
assert client.get_issues("owner", "repo", limit=2) == [{"number": 3}]
|
||||
assert len(calls) == 2
|
||||
|
||||
|
||||
def test_issue_search_stops_at_an_empty_page(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.extend([([{"number": 2, "pull_request": {}}], 200), ([], 200)])
|
||||
|
||||
assert client.get_issues("owner", "repo", limit=1) == []
|
||||
assert len(calls) == 2
|
||||
|
||||
|
||||
def test_issue_search_does_not_fetch_an_unneeded_page(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.append(([{"number": 2}, {"number": 1}], 200))
|
||||
|
||||
assert client.get_issues("owner", "repo", limit=2) == [{"number": 2}, {"number": 1}]
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_last_page_does_not_exceed_the_requested_issue_limit(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.extend([
|
||||
([{"number": 4}, {"number": 3, "pull_request": {}}], 200),
|
||||
([{"number": 2}, {"number": 1}], 200),
|
||||
])
|
||||
|
||||
assert client.get_issues("owner", "repo", limit=2) == [{"number": 4}, {"number": 2}]
|
||||
assert len(calls) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit", [0, -1])
|
||||
def test_nonpositive_issue_limit_does_not_make_a_request(issue_client, limit):
|
||||
client, pages, calls = issue_client
|
||||
pages.append(([], 200))
|
||||
|
||||
assert client.get_issues("owner", "repo", limit=limit) == []
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_later_page_failure_is_not_returned_as_partial_research(issue_client):
|
||||
client, pages, calls = issue_client
|
||||
pages.extend([([{"number": 3}, {"number": 2, "pull_request": {}}], 200), ({"message": "rate limited"}, 403)])
|
||||
|
||||
with pytest.raises(Exception, match="403"):
|
||||
client.get_issues("owner", "repo", limit=2)
|
||||
assert len(calls) == 2
|
||||
Loading…
x
Reference in New Issue
Block a user