research-assistant 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-ZH.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/research_assistant/assets/skills/research-assistant/SKILL.md +5 -2
- package/src/research_assistant/commands/doctor.py +15 -0
- package/src/research_assistant/providers/openai_compat.py +26 -2
package/README-ZH.md
CHANGED
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ base_url = "https://api.exa.ai"
|
|
|
93
93
|
|
|
94
94
|
[[provider]]
|
|
95
95
|
type = "openai_compat" # drives `ask`
|
|
96
|
-
base_url = "https://api.
|
|
96
|
+
base_url = "https://api.openai.com/v1" # default; for a compatible gateway, use its URL with /v1
|
|
97
97
|
api_key = "..."
|
|
98
98
|
model = "grok-..."
|
|
99
99
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "research-assistant",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Search/research sub-agent + concurrent search/fetch/locate CLI atomic tools for AI agents (Exa, Tavily, Firecrawl, Context7, OpenAI-compatible).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/arsonist-g/research-assistant-cli#readme",
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "research-assistant"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "Search/research sub-agent + a set of concurrent search/fetch/locate CLI atomic tools for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -97,7 +97,8 @@ Notes:
|
|
|
97
97
|
|
|
98
98
|
`ctx7 library "<name>" "<question>"` resolves a library id. `ctx7 docs /org/repo
|
|
99
99
|
"<question>"` returns up-to-date official docs as markdown. It hits the authoritative source
|
|
100
|
-
directly, so it is fast and accurate.
|
|
100
|
+
directly, so it is fast and accurate. On Windows git bash / WSL, MSYS rewrites the leading `/`
|
|
101
|
+
of `/org/repo` into a Windows path; write `//org/repo` there (PowerShell and cmd are unaffected).
|
|
101
102
|
|
|
102
103
|
**For any library, framework, SDK, CLI, or cloud-service question, prefer `ctx7` over web
|
|
103
104
|
search.** Do not grep the open web for API syntax, config keys, or version-migration notes
|
|
@@ -168,6 +169,8 @@ research-assistant tavily map <url> [--max-depth N] [--max-breadth N] [--limit N
|
|
|
168
169
|
research-assistant tavily crawl <url> [--max-depth N] [--limit N] [--instructions TEXT] [--extract-depth basic|advanced] [--no-external] [--timeout N] # recursive crawl, markdown per page
|
|
169
170
|
|
|
170
171
|
# Tier 2: Firecrawl. Residential bandwidth. scrape and search are keyless; the rest need a key.
|
|
172
|
+
# Keyless endpoints are sensitive to IP quality: residential/home IPs work; datacenter IPs may
|
|
173
|
+
# hit 403 ("IP looks suspicious") and need a home-bandwidth proxy.
|
|
171
174
|
research-assistant firecrawl scrape <url>... [--format markdown|html] [--only-main-content] [--wait-for MS] # batch markdown, keyless
|
|
172
175
|
research-assistant firecrawl search "<query>" [--limit N] [--sources web|news] [--scrape] # keyless; --scrape returns full markdown per result
|
|
173
176
|
research-assistant firecrawl map <url> [--limit N] [--include-subdomains] # needs key
|
|
@@ -194,7 +197,7 @@ research-assistant search "<query>" [--providers exa,tavily,firecrawl,browser] [
|
|
|
194
197
|
|
|
195
198
|
```sh
|
|
196
199
|
research-assistant ctx7 library "<name>" "<question>" # resolve a library id
|
|
197
|
-
research-assistant ctx7 docs /org/repo "<question>" # up-to-date official docs
|
|
200
|
+
research-assistant ctx7 docs /org/repo "<question>" # up-to-date official docs (git bash/WSL: use //org/repo)
|
|
198
201
|
```
|
|
199
202
|
|
|
200
203
|
### C. Ask a web-enabled LLM in natural language (`ask`)
|
|
@@ -143,6 +143,21 @@ def _minimal_namespace(cap_name: str) -> argparse.Namespace:
|
|
|
143
143
|
"ids": [],
|
|
144
144
|
"url": "https://example.com",
|
|
145
145
|
"urls": ["https://example.com"],
|
|
146
|
+
# search provider 完整字段:exa/tavily 等 handler 直接访问 ns.<attr>,缺就 AttributeError。
|
|
147
|
+
# 这里给全 superset,doctor 测任何 provider 的 search 能力都不缺字段。
|
|
148
|
+
"end_date": None,
|
|
149
|
+
"include_text": None,
|
|
150
|
+
"exclude_text": None,
|
|
151
|
+
"include_raw_content": None,
|
|
152
|
+
"chunks_per_source": None,
|
|
153
|
+
"country": None,
|
|
154
|
+
"days": None,
|
|
155
|
+
"include_images": False,
|
|
156
|
+
"include_image_descriptions": False,
|
|
157
|
+
"include_favicon": False,
|
|
158
|
+
"auto_parameters": False,
|
|
159
|
+
"engine": "bing-intl",
|
|
160
|
+
"max_pages": 10,
|
|
146
161
|
}
|
|
147
162
|
return argparse.Namespace(**base)
|
|
148
163
|
|
|
@@ -42,8 +42,9 @@ def resolve_cfg(config: Config, provider_type: str = "openai_compat") -> Provide
|
|
|
42
42
|
cfg = config.require_provider(provider_type)
|
|
43
43
|
if not cfg.api_key:
|
|
44
44
|
raise ArgsError(f"{provider_type}: 缺少 api_key", provider=provider_type)
|
|
45
|
+
# base_url 默认 OpenAI 官方(含 /v1/);用 OpenAI 兼容渠道时 base_url 需自带 /v1/(如 https://host/v1)
|
|
45
46
|
if not cfg.base_url:
|
|
46
|
-
|
|
47
|
+
cfg.base_url = "https://api.openai.com/v1"
|
|
47
48
|
if not cfg.model:
|
|
48
49
|
raise ArgsError(f"{provider_type}: 缺少 model", provider=provider_type)
|
|
49
50
|
return cfg
|
|
@@ -107,8 +108,12 @@ async def _stream_chat(client: httpx.AsyncClient, body: dict[str, Any], provider
|
|
|
107
108
|
search_results: list[Any] = []
|
|
108
109
|
usage: dict[str, Any] | None = None
|
|
109
110
|
role = "assistant"
|
|
111
|
+
raw_non_sse: list[str] = [] # 非 data: 行:网关可能 HTTP 200 却返回 body JSON error(非 SSE)
|
|
110
112
|
async for line in resp.aiter_lines():
|
|
111
|
-
if not line
|
|
113
|
+
if not line:
|
|
114
|
+
continue
|
|
115
|
+
if not line.startswith("data:"):
|
|
116
|
+
raw_non_sse.append(line)
|
|
112
117
|
continue
|
|
113
118
|
data_str = line[5:].strip()
|
|
114
119
|
if data_str == "[DONE]":
|
|
@@ -141,6 +146,25 @@ async def _stream_chat(client: httpx.AsyncClient, body: dict[str, Any], provider
|
|
|
141
146
|
|
|
142
147
|
raise wrap_provider_http_error(provider_type, e) from e
|
|
143
148
|
|
|
149
|
+
# 网关 HTTP 200 却没给 SSE chunk:body 可能是 JSON error 或 HTML 错误页(上游不可用等)。
|
|
150
|
+
# 别静默返回空 content(否则用户以为 research-assistant 坏,实际是 model 上游)。
|
|
151
|
+
if not content_parts and raw_non_sse:
|
|
152
|
+
raw = "\n".join(raw_non_sse)
|
|
153
|
+
msg: str | None = None
|
|
154
|
+
try:
|
|
155
|
+
err_body = json.loads(raw)
|
|
156
|
+
if isinstance(err_body, dict) and err_body.get("error"):
|
|
157
|
+
err = err_body["error"]
|
|
158
|
+
msg = err.get("message") if isinstance(err, dict) else str(err)
|
|
159
|
+
except json.JSONDecodeError:
|
|
160
|
+
pass
|
|
161
|
+
if msg:
|
|
162
|
+
raise ProviderError(f"{provider_type}: 上游返回错误 — {msg}", provider=provider_type)
|
|
163
|
+
raise ProviderError(
|
|
164
|
+
f"{provider_type}: 上游返回非 SSE 响应(HTTP 200,可能错误页/上游不可用): {raw[:120]}",
|
|
165
|
+
provider=provider_type,
|
|
166
|
+
)
|
|
167
|
+
|
|
144
168
|
message: dict[str, Any] = {"role": role, "content": "".join(content_parts)}
|
|
145
169
|
if citations:
|
|
146
170
|
message["citations"] = citations
|