-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathllms.txt
More file actions
232 lines (173 loc) Β· 6.94 KB
/
Copy pathllms.txt
File metadata and controls
232 lines (173 loc) Β· 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# extensions/ β LLM context file
This directory contains all data source extensions for Linnet.
Each extension is a self-contained unit: fetch β process β render β FeedSection.
See also: extensions/README.md (human guide), extensions/_template/ (starter)
---
## File map
Each extension is a package directory containing `__init__.py` (the class) and `README.md` (extension-specific docs).
```
base.py BaseExtension ABC + FeedSection dataclass β read this first
__init__.py REGISTRY list β all active extensions registered here
README.md Human-readable guide (quickstart, checklist, testing)
llms.txt This file
arxiv/
__init__.py ArxivExtension β fetch + LLM score + summarise + figures
README.md Config options, output schema, test commands
hacker_news/
__init__.py HackerNewsExtension β fetch + LLM summarise
README.md Config options, output schema, test commands
github_trending/
__init__.py GitHubTrendingExtension β fetch + LLM summarise
README.md Config options, output schema, test commands
us_stocks/
__init__.py USStocksExtension β public market data + deterministic scoring + optional LLM synthesis
collector.py Quote/news/filing provider fallback chain
scorer.py Signal scoring and sector overview aggregation
summarizer.py Structured LLM text synthesis
README.md Config options, provider notes, output schema, test commands
postdoc_jobs/
__init__.py PostdocJobsExtension β RSS + scraping, LLM score + summarise
README.md Config options, output schema, enabling steps
supervisor_updates/
__init__.py SupervisorExtension β page change detection + LLM diff summary
README.md Config options, output schema, enabling steps
_template/
__init__.py Fully commented starter for new extensions
README.md How to use the template
```
Note: `postdoc_jobs` and `supervisor_updates` are registered extensions, but disabled in the default config. Enable them by setting `enabled: true` in `config/sources.yaml` and adding the needed detail config under `config/extensions/`.
---
## BaseExtension contract
```python
class BaseExtension(ABC):
key: str = "" # unique snake_case β must match config/sources.yaml key
title: str = "" # display name used in rendered output
def __init__(self, config: dict, llm_client: Any = None): ...
@property
def enabled(self) -> bool:
return self.config.get("enabled", True)
@abstractmethod
def fetch(self) -> list[dict]: ... # no LLM; return raw items
def process(self, items: list[dict]) -> list[dict]:
return items # override for scoring/summarising
@abstractmethod
def render(self, items: list[dict]) -> FeedSection: ... # no network
def run(self) -> FeedSection:
# called by orchestrator
# returns empty FeedSection if not self.enabled
if not self.enabled:
return FeedSection(key=self.key, title=self.title)
items = self.fetch()
items = self.process(items)
return self.render(items)
```
---
## FeedSection
```python
@dataclass
class FeedSection:
key: str # snake_case identifier
title: str # section heading
items: list[dict] # processed items; schema is extension-specific
meta: dict # stats (counts, durations, etc.)
```
---
## self.config β injected keys
The orchestrator builds the config dict by merging sources.yaml + keywords.yaml
and injecting these keys into every extension's config:
| Key | Type | Source |
|---|---|---|
| `enabled` | bool | sources.yaml |
| `language` | str | sources.yaml β BCP-47 code, e.g. "en", "zh" |
| `llm_scoring_model` | str | sources.yaml llm.scoring_model |
| `llm_summarization_model` | str | sources.yaml llm.summarization_model |
| `dry_run` | bool | set when `--dry-run` CLI flag is used |
Extension-specific keys (example for arxiv):
| Key | Source |
|---|---|
| `categories` | keywords.yaml arxiv.categories |
| `must_include` | keywords.yaml arxiv.must_include |
| `boost_keywords` | keywords.yaml arxiv.boost_keywords |
| `llm_score_threshold` | keywords.yaml arxiv.llm_score_threshold |
| `max_papers_per_run` | sources.yaml arxiv.max_papers_per_run |
---
## self.llm β LLM client
OpenAI-compatible client (OpenRouter). Standard usage:
```python
resp = self.llm.chat.completions.create(
model=self.config["llm_summarization_model"],
messages=[{"role": "user", "content": prompt}],
max_tokens=120,
)
result = resp.choices[0].message.content.strip()
```
Always guard with dry_run:
```python
def process(self, items):
if self.config.get("dry_run"):
return items # skip all LLM calls
...
```
---
## Item field conventions
Use these field names so templates render correctly without changes:
### Papers (produced by arxiv.py)
```
id, title, authors, affiliations, categories, primary_category,
primary_category_anchor, url, score, abstract, keywords_matched,
figure_url, figure_caption
```
### HN stories (produced by hacker_news.py)
```
id, title, url, score, comments_url, summary
```
### GitHub repos (produced by github_trending.py)
```
full_name, url, description, language, stars_today, total_stars, summary
```
### Jobs (produced by postdoc_jobs extension)
```
title, url, institution, location, deadline, salary, source,
relevance_score, requirements
```
### Supervisor updates (produced by supervisor_updates extension)
```
name, institution, url, change_summary
```
---
## How to add an extension (quick reference)
1. `cp -R extensions/_template extensions/my_source`
2. Set `key` and `title`; implement `fetch()`, optionally `process()`, `render()`
3. Add to `REGISTRY` in `extensions/__init__.py`
4. Add config block in `config/sources.yaml`
5. Write tests in `tests/test_my_source.py`
6. `PYTHONPATH=. pytest tests/ -q` β all must pass
Full guide: extensions/README.md
---
## Registering in __init__.py
```python
from extensions.base import BaseExtension, FeedSection
from extensions.arxiv import ArxivExtension
from extensions.hacker_news import HackerNewsExtension
from extensions.github_trending import GitHubTrendingExtension
# from extensions.my_source import MySourceExtension β add here
REGISTRY: list[type[BaseExtension]] = [
ArxivExtension,
HackerNewsExtension,
GitHubTrendingExtension,
# MySourceExtension, β add here
]
```
---
## Testing extensions
```bash
# Run all tests (no network, no LLM)
PYTHONPATH=. pytest tests/ -q
# Run tests for one extension's collector
PYTHONPATH=. pytest tests/test_hn_collector.py -v
# Smoke test with live data, zero LLM cost
python main.py --dry-run
```
Write tests in `tests/` targeting the collector functions (`collectors/*.py`),
not the extension class directly. Use inline fixture data β no live API calls.
See `tests/conftest.py` for shared fixtures (sample_paper, sample_job, etc.).