HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1"""DeepSeek — API docs (Docusaurus): models & pricing table, change log, news posts.23Sources (tier 1):4 * pricing → api-docs.deepseek.com/quick_start/pricing : one transposed table (models as columns): API ids, model versions, context length,5 max output, feature matrix (JSON output, tool calls, vision…), peak / off-peak prices (cache hit, cache miss, output)6 * change log → api-docs.deepseek.com/updates : "Date: YYYY-MM-DD" sections → ANNOUNCEMENT events (release posts queued for LLM)7 * news → the docs top-nav "News" link (points to the latest post; /news/ itself redirects to the quick start); its sidebar lists every8 news post with its date → events + follow-ups910www.deepseek.com is a client-rendered Next.js site (Chinese landing page, no model list in the HTML) — not crawled.11"""12from __future__ import annotations1314import re15from typing import Any1617from aiatlas.registry import org_ref, provider_ref18from aiatlas.sdk.connector import BaseConnector, Parsed, RunContext19from aiatlas.sdk.extract.dates import parse_datetime20from aiatlas.sdk.extract.feeds import FeedItem21from aiatlas.sdk.extract.html import node_text22from aiatlas.sdk.facts import EntityRef, Facts, Target23from aiatlas.sdk.fetch import FetchResult2425from ._common import MODEL_WORDS, RELEASE_WORDS, announcement_events, clean_cell, model_ref, money, tokens2627DOCS = "https://api-docs.deepseek.com"28PROVIDER_KEY = "deepseek"29ID_SCHEME = "deepseek_model_id"30FOOTNOTE = re.compile(r"\s*\(\d+\)\s*$")31NEWS_LINK = re.compile(r"/news/news\d+/?$")32NEWS_DATE = re.compile(r"(\d{4})/(\d{2})/(\d{2})\s*$")333435class DeepSeekConnector(BaseConnector):36 name = "deepseek"37 label = "DeepSeek — API models & pricing, change log, news"38 description = "DeepSeek API docs: models & pricing table (ids, context, max output, features, peak/off-peak prices), change log and news posts."39 source_key = "deepseek.com"40 version = "1"41 parser_version = "1"42 interval_seconds = 360043 min_interval_seconds = 180044 rate_per_min = 1245 tier = 146 priority = 047 expected_min_records = 348 concurrency = 24950 async def discover(self, ctx: RunContext) -> list[Target]:51 return [52 Target(url=f"{DOCS}/quick_start/pricing", doc_type="pricing", key="pricing", min_bytes=5000),53 Target(url=f"{DOCS}/updates", doc_type="listing", key="changelog", min_bytes=5000),54 ]5556 async def extract(self, ctx: RunContext, target: Target, res: FetchResult, parsed: Parsed) -> Facts:57 facts = Facts()58 org = org_ref("deepseek")59 facts.entities.append(org)60 key = target.key or ""61 if key == "pricing" and parsed.html:62 self._pricing(facts, org, parsed, res.final_url or res.url)63 elif key == "changelog" and parsed.html:64 self._changelog(facts, org, parsed, res.final_url or res.url)65 elif target.doc_type == "news_index" and parsed.html:66 self._news_index(facts, org, parsed)67 return facts6869 # ------------------------------------------------------------------------------------------ models & pricing70 def _pricing(self, facts: Facts, org: EntityRef, parsed: Parsed, url: str) -> None:71 html = parsed.html72 assert html73 provider = provider_ref(PROVIDER_KEY)74 facts.entities.append(provider)75 facts.document_title, facts.document_entity = "DeepSeek models & pricing", provider76 table = next((t for t in html.tables if any(clean_cell(r[0]).upper().startswith("MODEL") for r in t["rows"] if r)), None)77 if not table:78 return79 rows = [[clean_cell(c) for c in r] for r in table["rows"]]80 ids = next(([FOOTNOTE.sub("", c) for c in r[1:]] for r in rows if r and r[0].upper().startswith("MODEL") and not r[0].upper().startswith("MODEL VERSION")), [])81 ids = [i for i in ids if re.fullmatch(r"[a-z0-9][a-z0-9.\-]*", i)]82 if not ids:83 return84 n = len(ids)85 versions = next(([c for c in r[1:]] for r in rows if r and r[0].upper().startswith("MODEL VERSION")), [])86 refs: list[EntityRef] = []87 for i, api_id in enumerate(ids):88 display = versions[i] if i < len(versions) and versions[i] else api_id89 ref = model_ref(facts, display, org, api_id=api_id, provider_key=PROVIDER_KEY, family="DeepSeek", aliases=[api_id])90 facts.claim(ref, "api_model_id", api_id)91 facts.claim(ref, "official_url", url)92 refs.append(ref)93 prices = [facts.price(model=ref, provider=provider, provider_model_id=api_id, meta={"from": "pricing page", "note": "peak-hour rates; off-peak in features"})94 for ref, api_id in zip(refs, ids, strict=True)]9596 def spread(values: list[str]) -> list[str]:97 return values * n if len(values) == 1 else values9899 kind: str | None = None100 for r in rows:101 if not r:102 continue103 label = r[0].upper()104 if label.startswith("CONTEXT LENGTH"):105 for ref, v in zip(refs, spread(r[1:]), strict=False):106 facts.claim(ref, "context_length", tokens(v), unit="tokens")107 elif label.startswith("MAX OUTPUT"):108 for ref, v in zip(refs, spread(r[1:]), strict=False):109 facts.claim(ref, "max_output_tokens", tokens(re.sub(r"(?i)maximum:?\s*|default:?\s*\d+\w*", "", v)), unit="tokens")110 elif label.startswith("THINKING"):111 for ref in refs:112 facts.claim(ref, "reasoning", True)113 cells = r[1:] if not label.startswith("FEATURES") else r[2:]114 feat_label = r[1] if label.startswith("FEATURES") and len(r) > 1 else r[0]115 if feat_label in ("Json Output", "JSON Output", "Tool Calls", "Vision", "FIM Completion(Beta)", "Chat Prefix Completion(Beta)", "Responses API", "Anthropic API"):116 vals = spread(cells)117 for ref, v in zip(refs, vals, strict=False):118 supported = "✓" in v or v.lower().startswith(("yes", "support"))119 if feat_label == "Tool Calls":120 facts.claim(ref, "tool_calling", supported)121 elif feat_label.lower() == "json output":122 facts.claim(ref, "structured_output", supported)123 elif feat_label == "Vision":124 facts.claim(ref, "vision", supported)125 if supported:126 facts.claim(ref, "modalities_input", ["text", "image"])127 # pricing rows: a "kind" cell then PEAK / OFF-PEAK rows with one money cell per model128 joined = " ".join(r).upper()129 if "INPUT TOKENS" in joined and "CACHE HIT" in joined:130 kind = "cached_input_per_mtok"131 elif "INPUT TOKENS" in joined and "CACHE MISS" in joined:132 kind = "input_per_mtok"133 elif "OUTPUT TOKENS" in joined:134 kind = "output_per_mtok"135 monies = [money(c) for c in r if c.startswith("$")]136 if kind and len(monies) == n:137 period = next((c.upper() for c in r if c.upper() in ("PEAK", "OFF-PEAK", "STANDARD")), "PEAK")138 for p, v in zip(prices, monies, strict=True):139 if period == "OFF-PEAK":140 p.features[f"off_peak_{kind}"] = v141 else:142 setattr(p, kind, v)143 for ref, api_id in zip(refs, ids, strict=True):144 for p in html.css("p"):145 t = node_text(p)146 if t.startswith("(") and api_id in t and "legacy" in t.lower():147 for legacy in re.findall(r"\b(deepseek-[a-z0-9.\-]+)\b", t):148 if legacy != api_id and legacy not in ref.aliases:149 ref.aliases.append(legacy)150 facts.prices = [p for p in facts.prices if any(v is not None for v in p.price_tuple()[:-1])]151152 # ------------------------------------------------------------------------------------------ change log153 def _changelog(self, facts: Facts, org: EntityRef, parsed: Parsed, url: str) -> None:154 html = parsed.html155 assert html156 facts.document_title, facts.document_entity = "DeepSeek API change log", org157 for h2 in html.css("h2"):158 m = re.search(r"Date:\s*(\d{4}-\d{2}-\d{2})", node_text(h2))159 if not m:160 continue161 date = parse_datetime(m.group(1))162 anchor = h2.attributes.get("id") or m.group(1)163 sib = h2.next164 title: str | None = None165 body: list[str] = []166 n = 0167 while sib is not None and sib.tag != "h2":168 if sib.tag == "h3":169 if title:170 n += 1171 self._changelog_event(facts, org, url, anchor, n, date, title, " ".join(body))172 body = []173 title = node_text(sib).replace("\u200b", "").strip()174 elif sib.tag in ("p", "ul", "ol", "div"):175 body.append(node_text(sib))176 sib = sib.next177 if title:178 n += 1179 self._changelog_event(facts, org, url, anchor, n, date, title, " ".join(body))180 news = next((href for href, text in html.links if text.strip() == "News" and NEWS_LINK.search(href)), None)181 if news:182 facts.follow(news, doc_type="news_index", key="news_index", priority=1, min_bytes=5000)183184 def _changelog_event(self, facts: Facts, org: EntityRef, url: str, anchor: str, n: int, date: Any, title: str, body: str) -> None:185 is_release = bool(MODEL_WORDS.search(title) or RELEASE_WORDS.search(title))186 facts.event("ANNOUNCEMENT", "release" if is_release else "company", f"DeepSeek: {title}", entity=org, importance=2 if is_release else 1,187 effective_at=date, dedupe_key=f"ANNOUNCEMENT:{url}#{anchor}:{n}", source_url=f"{url}#{anchor}",188 meta={"source": "api-docs.deepseek.com/updates", "summary": body[:300], "is_release": is_release})189190 # ------------------------------------------------------------------------------------------ news index (sidebar)191 def _news_index(self, facts: Facts, org: EntityRef, parsed: Parsed) -> None:192 html = parsed.html193 assert html194 items: list[FeedItem] = []195 seen: set[str] = set()196 for href, text in html.links:197 if not NEWS_LINK.search(href) or "/zh-cn/" in href or href in seen:198 continue199 m = NEWS_DATE.search(text)200 title = NEWS_DATE.sub("", text).strip()201 if not title or title in ("News", "English"):202 continue203 seen.add(href)204 date = parse_datetime(f"{m.group(1)}-{m.group(2)}-{m.group(3)}") if m else None205 items.append(FeedItem(id=href, url=href, title=title, summary=None, published_at=date, updated_at=None))206 announcement_events(facts, org, items, source_name="api-docs.deepseek.com/news", max_follow=10)207 facts.document_title, facts.document_entity = "DeepSeek news", org208209210CONNECTORS = [DeepSeekConnector]211