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 CHANGED
@@ -86,7 +86,7 @@ base_url = "https://api.exa.ai"
86
86
 
87
87
  [[provider]]
88
88
  type = "openai_compat" # 驱动 `ask`
89
- base_url = "https://api.example.com/v1"
89
+ base_url = "https://api.openai.com/v1" # 默认;用兼容渠道时换成其地址(需含 /v1)
90
90
  api_key = "..."
91
91
  model = "grok-..."
92
92
 
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.example.com/v1"
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.0",
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.0"
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
- raise ArgsError(f"{provider_type}: 缺少 base_url", provider=provider_type)
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 or not line.startswith("data:"):
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