Collect first agent research and market sources

This commit is contained in:
wuyang
2026-07-08 12:08:24 +08:00
parent 36c2499d3c
commit 1cbc98e432
34 changed files with 3417 additions and 58 deletions
+53
View File
@@ -0,0 +1,53 @@
# Collection Tools
这些脚本用于把 JD、论文和行业资料从 Markdown 笔记变成可检索、可检查、可视化的数据。
## Scripts
### `new_item.py`
生成一个带 frontmatter 的新条目草稿。
```bash
python3 tools/collection/new_item.py jobs "baidu aidu agent algorithm engineer beijing" \
--title "Baidu AIDU Agent Algorithm Engineer" \
--url "https://talent.baidu.com/jobs/list?projectType=3&recruitType=GRADUATE"
```
支持的 collection:
- `jobs`
- `papers`
- `industry`
### `build_index.py`
扫描 `jobs/items/``papers/items/``industry/items/` 的 frontmatter,生成结构化索引。
```bash
python3 tools/collection/build_index.py
```
输出:
- `data/index.json`
- `data/summary.json`
### `check_urls.py`
检查条目中的 `url``source_url``code_url` 是否可访问。
```bash
python3 tools/collection/check_urls.py
```
可加 `--json` 输出机器可读结果。
## Method
1. 搜索或导入资料。
2.`new_item.py` 生成草稿。
3. 人工整理摘要、标签、判断和关联。
4. 运行 `build_index.py` 更新结构化数据。
5. 运行 `check_urls.py` 检查来源可达性。
6. 更新对应 insight 文件。
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Build a JSON index from Markdown frontmatter."""
from __future__ import annotations
import json
from collections import Counter, defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
COLLECTIONS = ("jobs", "papers", "industry")
def parse_scalar(value: str):
value = value.strip()
if value == "":
return None
return value
def parse_frontmatter(text: str) -> dict:
start = text.find("\n---\n")
if text.startswith("---\n"):
start = -1
body_start = 4
elif start != -1:
body_start = start + 5
else:
return {}
end = text.find("\n---", body_start)
if end == -1:
return {}
lines = text[body_start:end].splitlines()
data: dict[str, object] = {}
current_key: str | None = None
for raw in lines:
line = raw.rstrip()
if not line:
continue
if line.startswith(" -"):
if current_key:
item = line[3:].strip()
if item:
data.setdefault(current_key, [])
assert isinstance(data[current_key], list)
data[current_key].append(item)
continue
if ":" in line:
key, value = line.split(":", 1)
key = key.strip()
value = value.strip()
current_key = key
data[key] = [] if value == "" else parse_scalar(value)
return data
def iter_items():
for collection in COLLECTIONS:
item_dir = ROOT / collection / "items"
for path in sorted(item_dir.glob("*.md")):
if path.name == "README.md":
continue
text = path.read_text(encoding="utf-8")
meta = parse_frontmatter(text)
yield {
"collection": collection,
"path": str(path.relative_to(ROOT)),
"title": meta.get("title") or meta.get("role") or path.stem,
"type": meta.get("type") or collection.rstrip("s"),
"meta": meta,
}
def main() -> int:
items = list(iter_items())
data_dir = ROOT / "data"
data_dir.mkdir(exist_ok=True)
(data_dir / "index.json").write_text(
json.dumps(items, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
by_collection = Counter(item["collection"] for item in items)
by_status = defaultdict(Counter)
topics = Counter()
skills = Counter()
companies = Counter()
for item in items:
meta = item["meta"]
by_status[item["collection"]][meta.get("status") or "unknown"] += 1
for topic in meta.get("topics") or []:
topics[topic] += 1
for skill in meta.get("skills") or []:
skills[skill] += 1
company = meta.get("company")
if company:
companies[str(company)] += 1
summary = {
"total": len(items),
"by_collection": dict(by_collection),
"by_status": {key: dict(value) for key, value in by_status.items()},
"top_topics": topics.most_common(20),
"top_skills": skills.most_common(20),
"top_companies": companies.most_common(20),
}
(data_dir / "summary.json").write_text(
json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(json.dumps(summary, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Check source URLs stored in collection item frontmatter."""
from __future__ import annotations
import argparse
import json
import urllib.error
import urllib.request
from pathlib import Path
from build_index import parse_frontmatter
ROOT = Path(__file__).resolve().parents[2]
URL_KEYS = ("url", "source_url", "code_url")
def check_url(url: str, timeout: int) -> dict:
request = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "agent-kb-url-check/1.0"})
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return {"url": url, "ok": True, "status": response.status}
except urllib.error.HTTPError as exc:
if exc.code in (403, 405):
return check_url_get(url, timeout, exc.code)
return {"url": url, "ok": False, "status": exc.code, "error": str(exc)}
except Exception as exc: # noqa: BLE001
return {"url": url, "ok": False, "status": None, "error": str(exc)}
def check_url_get(url: str, timeout: int, head_status: int) -> dict:
request = urllib.request.Request(url, method="GET", headers={"User-Agent": "agent-kb-url-check/1.0"})
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return {"url": url, "ok": True, "status": response.status, "head_status": head_status}
except urllib.error.HTTPError as exc:
if exc.code == 403:
return {
"url": url,
"ok": True,
"status": exc.code,
"head_status": head_status,
"blocked": True,
"error": "blocked automated request; verify in browser",
}
return {"url": url, "ok": False, "status": exc.code, "head_status": head_status, "error": str(exc)}
except Exception as exc: # noqa: BLE001
return {"url": url, "ok": False, "status": head_status, "error": str(exc)}
def iter_urls():
for collection in ("jobs", "papers", "industry"):
for path in sorted((ROOT / collection / "items").glob("*.md")):
if path.name == "README.md":
continue
meta = parse_frontmatter(path.read_text(encoding="utf-8"))
for key in URL_KEYS:
value = meta.get(key)
if value:
yield path.relative_to(ROOT).as_posix(), key, str(value)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--json", action="store_true")
parser.add_argument("--timeout", type=int, default=12)
args = parser.parse_args()
results = []
for path, key, url in iter_urls():
result = check_url(url, args.timeout)
result.update({"path": path, "key": key})
results.append(result)
if args.json:
print(json.dumps(results, ensure_ascii=False, indent=2))
else:
for result in results:
marker = "BLOCKED" if result.get("blocked") else ("OK" if result["ok"] else "FAIL")
print(f"{marker} {result.get('status')} {result['path']} {result['key']} {result['url']}")
return 1 if any(not result["ok"] for result in results) else 0
if __name__ == "__main__":
raise SystemExit(main())
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Create a new collection item draft."""
from __future__ import annotations
import argparse
import re
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
TEMPLATES = {
"jobs": """# Job: {title}
---
type: job
company:
role: {title}
location:
source_url: {url}
source_name:
source_quality: unknown
collected_at: {today}
posted_at:
first_seen_at: {today}
last_checked_at: {today}
snapshot_type: new
job_code:
previous_snapshot:
status: unknown
level:
team:
business_area:
employment_type:
salary:
skills:
-
topics:
-
models:
-
related_papers:
-
related_experiments:
-
related_projects:
-
relevance: medium
---
## Source Snapshot
- source:
- collected_at: {today}
- availability:
## Change Snapshot
- first_seen_at: {today}
- last_checked_at: {today}
- snapshot_type: new
- previous_snapshot:
- changed_fields:
## Raw JD Summary
-
## Extracted Signals
- hard requirements:
- preferred skills:
- hidden signals:
- business direction:
## Knowledge Links
- concepts:
- papers:
- experiments:
- projects:
## Gaps for Us
-
""",
"papers": """# Paper: {title}
---
type: paper
title: {title}
authors:
year:
venue:
url: {url}
code_url:
source:
collected_at: {today}
status: queued
relevance: medium
topics:
-
methods:
-
benchmarks:
-
models:
-
datasets:
-
related_concepts:
-
related_jobs:
-
related_experiments:
-
related_projects:
-
---
## One-line Takeaway
-
## Problem
-
## Core Idea
-
## Evidence
-
## Useful For Us
-
## Follow-up Experiments
-
""",
"industry": """# Industry: {title}
---
type: industry
company:
team:
title: {title}
url: {url}
source_name:
source_type: other
source_quality: unknown
published_at:
collected_at: {today}
status: queued
topics:
-
implementation_signals:
-
product_area:
-
models:
-
tools:
-
benchmarks:
-
related_papers:
-
related_jobs:
-
related_experiments:
-
related_projects:
-
evidence_level: medium
relevance: medium
---
## One-line Takeaway
-
## What They Built or Claimed
-
## Technical Signals
- architecture:
- tool use:
- memory:
- evaluation:
- safety:
- deployment:
- data:
## Evidence Quality
-
""",
}
def slugify(text: str) -> str:
text = text.lower().strip()
text = re.sub(r"[^a-z0-9\u4e00-\u9fff]+", "-", text)
text = re.sub(r"-+", "-", text).strip("-")
return text or "untitled"
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("collection", choices=sorted(TEMPLATES))
parser.add_argument("slug")
parser.add_argument("--title", default=None)
parser.add_argument("--url", default="")
parser.add_argument("--date", default=date.today().isoformat())
args = parser.parse_args()
title = args.title or args.slug.replace("-", " ").title()
filename = f"{args.date}-{slugify(args.slug)}.md"
path = ROOT / args.collection / "items" / filename
if path.exists():
raise SystemExit(f"refusing to overwrite existing file: {path}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
TEMPLATES[args.collection].format(title=title, url=args.url, today=args.date),
encoding="utf-8",
)
print(path.relative_to(ROOT))
return 0
if __name__ == "__main__":
raise SystemExit(main())