Skip to content

Commit 0767eac

Browse files
committed
chore(llm): fixed some problems with propts
1 parent f91d774 commit 0767eac

5 files changed

Lines changed: 115 additions & 140 deletions

File tree

prompts.yaml

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,61 @@ scoring:
22
system: |
33
You are a neutral curator for github repositories.
44
fewshot: |
5-
Examples (format matches schema):
5+
Examples (format matches schema).
66
77
Repo:
8-
"CLI that cross-posts your blog posts to Dev.to, Hashnode and Medium; manages canonical URLs; adds UTM; syncs updates."
9-
10-
Expected JSON:
8+
"Beatstore marketplace: upload & license beats, set pricing, cart/checkout, artist pages."
9+
Response:
1110
{
1211
"scores": [
13-
{ "list": "self-marketing", "score": 0.8, "why": "Publishing & cross-posting are personal promotion workflows." },
14-
{ "list": "productivity", "score": 0.4, "why": "CLI automation helps, but promotion is the primary goal." },
15-
{ "list": "learning", "score": 0.0 }
12+
{ "list": "monetise", "score": 0.9, "why": "Directly enables selling/licensing music for income." },
13+
{ "list": "self-marketing", "score": 0.5, "why": "Artist pages promote an individual's work." }
1614
]
1715
}
1816
1917
Repo:
20-
"Task-runner that automates image optimisation and builds; speeds up local dev commands; no publishing features."
18+
"Self-hosted community platform: forums, chat, events, moderation, invites."
19+
Response:
20+
{
21+
"scores": [
22+
{ "list": "networking", "score": 0.9, "why": "Explicit community-building features." },
23+
{ "list": "team-management", "score": 0.4, "why": "Moderation and events coordinate group activity." }
24+
]
25+
}
2126
22-
Expected JSON:
27+
Repo:
28+
"Project-based 'Build a RAG app' course: 8 modules, exercises with solutions, evaluation harness."
29+
Response:
2330
{
2431
"scores": [
25-
{ "list": "productivity", "score": 0.9, "why": "Developer time-saver for day-to-day workflows." },
26-
{ "list": "self-marketing", "score": 0.1, "why": "Not focused on promoting an individual." },
27-
{ "list": "learning", "score": 0.0 }
32+
{ "list": "learning", "score": 0.95, "why": "Structured curriculum with modules, exercises, assessments." },
33+
{ "list": "ai", "score": 0.6, "why": "Focus on RAG/LLM techniques and evaluation." }
2834
]
2935
}
3036
criteria: |
31-
productivity = only score if the repo saves time or automates repetitive tasks in any domain (e.g. work, study, daily life).
32-
monetise = only score if the repo explicitly helps generate revenue, enable payments, or provide monetisation strategies (business, commerce, content, services).
33-
networking = only score if the repo explicitly builds or supports communities, connections, or collaboration (social, professional, or technical).
34-
ai = only score if the repo’s primary focus is AI/ML models, frameworks, applications, or tooling.
35-
blockchain-finance = only score if the repo is about blockchain, crypto, DeFi, financial systems, or digital assets.
36-
learning = only score if the repo explicitly teaches through courses, tutorials, exercises, or curricula (any subject, not just programming).
37-
self-marketing = only score if the repo explicitly promotes an individual (portfolio, profile, blogging, personal branding, analytics).
38-
team-management = only score if the repo explicitly helps manage, scale, or structure teams (onboarding, communication, rituals, project or workforce management).
37+
productivity = runnable automation or one-command multi-step actions. anything that makes life easier through automation or provides resources to the same.
38+
monetise = concrete system/course to make money (bots, arbitrage, dropshipping, affiliate). explicit mentions of "free" resources also qualify.
39+
networking = explicit community features or community based solutions (forums/events/social netowrking, communication) but "awesome" lists and aggregated content generally don't qualify.
40+
ai = models/training/inference/eval or LLM/RAG pipelines and frameworks.
41+
blockchain-finance = smart contracts, wallets, swaps, on-chain ops/analytics.
42+
learning = course/curriculum with exercises or assessments, has direct links to resources, directly purposed to teach.
43+
self-marketing = portfolio/blog for an individual; personal analytics. social networking, tools/projects/resources that help one to promote themselves
44+
team-management = onboarding, rituals, approvals, shifts, project/OKR tools, thought leadership, leadership etc.
3945
summarise:
4046
one_paragraph: |
41-
Write ONE paragraph (<=100 words) that summarises the project for an experienced engineer.
42-
Include purpose, core tech, standout capability, maturity signal (if any), ideal use case.
43-
No bullet points or headings or em dashes. Neutral tone. Do not invent facts.
44-
Your summary must stand the test of time; do not mention scores.
47+
Write ONE paragraph (100 words) that explains the project in plain language
48+
for a general audience with no technical background.
49+
Focus on what it does and why it matters.
50+
Keep it clear, simple, and factual. Do not use bullet points, headings, or em dashes.
4551
4652
map_header: |
47-
From the following text, extract 2-3 concise bullets (10-18 words each), no fluff.
48-
Focus on: purpose, core tech/architecture, standout capabilities, maturity signals (derive only if stated).
53+
From the following text, extract 2-3 short bullets (10-18 words each) in plain language.
54+
Summarise what the project does and why it might be useful.
4955
Return only bullets prefixed with "- ".
5056
5157
reduce: |
52-
Write ONE paragraph (≤100 words) for the general public.
53-
Include: purpose, core tech/approach, one standout capability, maturity signal (if present), ideal use case.
54-
No marketing language. Present tense. If something isn’t in the notes, omit, do not guess. No em dashes.
55-
Return only the paragraph. Do not mention numeric scores.
58+
Write ONE paragraph (≤100 words) in plain language for a general audience.
59+
Explain what the project does and why it might be useful.
60+
Keep it clear, simple, and factual.
61+
Return only the paragraph, no bullet points or em dashes.
5662

src/features/scoring/llm.ts

Lines changed: 59 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
// src/llm/scoring.ts
12
import { OllamaService } from "@jasonnathan/llm-core";
2-
import { toNum } from "@lib/utils";
33
import { promptsConfig as prompts } from "@lib/prompts";
4+
import { toNum } from "@lib/utils";
45
import type { MaybeOllama } from "./types";
56

67
/* ---------- Public types ---------- */
@@ -23,7 +24,7 @@ export type RepoFacts = {
2324
export type ScoreItem = { list: string; score: number; why?: string };
2425
export type ScoreResponse = { scores: ScoreItem[] };
2526

26-
/* ---------- LLM interface (simple) ---------- */
27+
/* ---------- LLM interface ---------- */
2728

2829
export type ScoringLLM = {
2930
generatePromptAndSend(
@@ -53,7 +54,12 @@ function defaultLLM(): ScoringLLM {
5354
};
5455
}
5556

56-
/* ---------- Tiny helpers ---------- */
57+
/* ---------- Helpers ---------- */
58+
59+
function requireStr(v: unknown, name: string): string {
60+
if (typeof v === "string" && v.trim()) return v;
61+
throw new Error(`${name} missing`);
62+
}
5763

5864
function buildSchema(slugs: string[]): unknown {
5965
return {
@@ -78,55 +84,6 @@ function buildSchema(slugs: string[]): unknown {
7884
} as const;
7985
}
8086

81-
function listsBlock(lists: ListDef[]): string {
82-
return lists
83-
.map((l) => `- ${l.name} (${l.slug}) - ${l.description ?? ""}`.trim())
84-
.join("\n");
85-
}
86-
87-
function listsBlockFromCriteria(lists: ListDef[], criteria?: unknown): string {
88-
const arr = Array.isArray(criteria)
89-
? (criteria as Array<{ slug: string; description?: string | null }>)
90-
: null;
91-
if (!arr?.length) return listsBlock(lists);
92-
const bySlug = new Map(arr.map((c) => [c.slug, c] as const));
93-
return lists
94-
.map((l) => {
95-
const c = bySlug.get(l.slug);
96-
const desc = (c?.description ?? l.description ?? "").trim();
97-
return `- ${l.name} (${l.slug}) - ${desc}`.trim();
98-
})
99-
.join("\n");
100-
}
101-
102-
const FEWSHOT = `
103-
Examples (format matches schema):
104-
105-
Repo:
106-
"CLI that cross-posts your blog posts to Dev.to, Hashnode and Medium; manages canonical URLs; adds UTM; syncs updates."
107-
108-
Expected JSON:
109-
{
110-
"scores": [
111-
{ "list": "self-marketing", "score": 0.8, "why": "Publishing & cross-posting are personal promotion workflows." },
112-
{ "list": "productivity", "score": 0.4, "why": "CLI automation helps, but promotion is the primary goal." },
113-
{ "list": "learning", "score": 0.0 }
114-
]
115-
}
116-
117-
Repo:
118-
"Task-runner that automates image optimisation and builds; speeds up local dev commands; no publishing features."
119-
120-
Expected JSON:
121-
{
122-
"scores": [
123-
{ "list": "productivity", "score": 0.9, "why": "Developer time-saver for day-to-day workflows." },
124-
{ "list": "self-marketing", "score": 0.1, "why": "Not focused on promoting an individual." },
125-
{ "list": "learning", "score": 0.0 }
126-
]
127-
}
128-
`.trim();
129-
13087
function repoBlock(r: RepoFacts): string {
13188
const bits = [
13289
`Name: ${r.nameWithOwner}`,
@@ -139,16 +96,46 @@ function repoBlock(r: RepoFacts): string {
13996
return bits.join("\n");
14097
}
14198

142-
/* ---------- Minimal validation/repair (no any) ---------- */
99+
/** Parse a block with lines like:
100+
* "• productivity = …" or "productivity: …"
101+
* returns Map<slug, description>
102+
*/
103+
function parseCriteriaBlock(block: string): Map<string, string> {
104+
const map = new Map<string, string>();
105+
for (const raw of block.split(/\r?\n/)) {
106+
const line = raw.trim();
107+
if (!line) continue;
108+
const cleaned = line.replace(/^[*\-\u2022]\s*/, ""); // strip bullet
109+
const m = cleaned.match(/^([a-z0-9-]+)\s*[:=]\s*(.+)$/i);
110+
if (!m) continue;
111+
map.set(m[1].toLowerCase(), m[2].trim());
112+
}
113+
return map;
114+
}
115+
116+
function listsBlockFromCriteria(
117+
lists: ListDef[],
118+
criteriaBlock: string,
119+
): string {
120+
const bySlug = parseCriteriaBlock(criteriaBlock);
121+
return lists
122+
.map((l) => {
123+
const desc = bySlug.get(l.slug);
124+
return desc
125+
? `- ${l.name} (${l.slug}) - ${desc}`
126+
: `- ${l.name} (${l.slug})`;
127+
})
128+
.join("\n");
129+
}
130+
131+
/* ---------- Response validation ---------- */
143132

144133
function isRecord(x: unknown): x is Record<string, unknown> {
145134
return typeof x === "object" && x !== null;
146135
}
147-
148136
function getProp(obj: Record<string, unknown>, key: string): unknown {
149137
return Object.hasOwn(obj, key) ? obj[key] : undefined;
150138
}
151-
152139
function getStr(obj: Record<string, unknown>, key: string): string | undefined {
153140
const v = getProp(obj, key);
154141
return typeof v === "string" ? v : undefined;
@@ -164,7 +151,6 @@ function validateAndRepair(
164151
if (!Array.isArray(scoresUnknown)) return false;
165152

166153
const out: ScoreItem[] = [];
167-
168154
for (const item of scoresUnknown) {
169155
if (!isRecord(item)) continue;
170156

@@ -194,9 +180,7 @@ function validateAndRepair(
194180
return out.length ? { scores: out } : false;
195181
}
196182

197-
/* ---------- Prompt + main ---------- */
198-
199-
const SYSTEM_PROMPT = `You are a neutral curator for github repositories.`;
183+
/* ---------- Main ---------- */
200184

201185
export async function scoreRepoAgainstLists(
202186
lists: ListDef[],
@@ -206,42 +190,33 @@ export async function scoreRepoAgainstLists(
206190
const slugs = lists.map((l) => l.slug);
207191
const schema = buildSchema(slugs);
208192

209-
const defaultGuide = `
210-
productivity = only score if the repo saves time or automates repetitive tasks in any domain (e.g. work, study, daily life).
211-
monetise = only score if the repo explicitly helps generate revenue, enable payments, or provide monetisation strategies (business, commerce, content, services).
212-
networking = only score if the repo explicitly builds or supports communities, connections, or collaboration (social, professional, or technical).
213-
ai = only score if the repo’s primary focus is AI/ML models, frameworks, applications, or tooling.
214-
blockchain-finance = only score if the repo is about blockchain, crypto, DeFi, financial systems, or digital assets.
215-
learning = only score if the repo explicitly teaches through courses, tutorials, exercises, or curricula (any subject, not just programming).
216-
self-marketing = only score if the repo explicitly promotes an individual (portfolio, profile, blogging, personal branding, analytics).
217-
team-management = only score if the repo explicitly helps manage, scale, or structure teams (onboarding, communication, rituals, project or workforce management).
218-
`.trim();
219-
220-
const guide =
221-
prompts?.scoring?.criteria && Array.isArray(prompts.scoring.criteria)
222-
? listsBlockFromCriteria(lists, prompts.scoring.criteria)
223-
: defaultGuide;
224-
225-
const system = prompts?.scoring?.system ?? SYSTEM_PROMPT;
226-
const fewshot = prompts?.scoring?.fewshot ?? FEWSHOT;
193+
// STRICT: read strings only; throw if missing
194+
const system = requireStr(prompts?.scoring?.system, "prompts.scoring.system");
195+
const fewshot = requireStr(
196+
prompts?.scoring?.fewshot,
197+
"prompts.scoring.fewshot",
198+
);
199+
const criteria = requireStr(
200+
prompts?.scoring?.criteria,
201+
"prompts.scoring.criteria",
202+
);
203+
204+
const guideText = criteria; // injected verbatim
205+
const listsText = listsBlockFromCriteria(lists, criteria);
227206

228207
const userPrompt = `
229-
Your task is to score the repository against EACH list from 0 to 1, where 1 = perfect fit. Multiple lists may apply. Provide a reason why for repos that meet the criteria.
230-
Scoring Guide (if the repo does not or barely meets the criteria, score MUST be < 0.5):
231-
${guide}
208+
${guideText}
232209
233210
Lists:
234-
${listsBlockFromCriteria(lists, prompts?.scoring?.criteria)}
211+
${listsText}
235212
236213
${fewshot}
237214
238215
Repository to score:
239216
${repoBlock(repo)}
240217
`.trim();
241218

242-
const raw = await llm.generatePromptAndSend(system, userPrompt, {
243-
schema,
244-
});
219+
const raw = await llm.generatePromptAndSend(system, userPrompt, { schema });
245220
const repaired = validateAndRepair(raw, new Set(slugs));
246221
if (!repaired) throw new Error("Invalid LLM response");
247222
return repaired;

src/features/scoring/service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ import type {
1313
Thresholds,
1414
} from "./types";
1515

16+
const SUMMARY_PRED = "r.summary IS NOT NULL AND length(trim(r.summary)) > 0";
17+
1618
export function createScoringService(db = getDefaultDb()): ScoringService {
1719
const qBatchDefault = db.query<RepoRow, BindRunLimit>(`
1820
SELECT r.id, r.name_with_owner, r.url, r.description, r.primary_language, r.topics,
1921
r.stars, r.forks, r.popularity, r.freshness, r.activeness, r.pushed_at,
2022
r.last_commit_iso, r.last_release_iso, r.updated_at, r.summary
2123
FROM repo r
22-
WHERE (? IS NULL) OR NOT EXISTS (
24+
WHERE ${SUMMARY_PRED}
25+
AND (? IS NULL) OR NOT EXISTS (
2326
SELECT 1 FROM repo_list_score s
2427
WHERE s.repo_id = r.id AND s.run_id = ?
2528
)
@@ -34,7 +37,8 @@ export function createScoringService(db = getDefaultDb()): ScoringService {
3437
FROM repo r
3538
JOIN list_repo lr ON lr.repo_id = r.id
3639
JOIN list l ON l.id = lr.list_id
37-
WHERE l.slug = ?
40+
WHERE ${SUMMARY_PRED}
41+
AND l.slug = ?
3842
AND ((? IS NULL) OR NOT EXISTS (
3943
SELECT 1 FROM repo_list_score s WHERE s.repo_id = r.id AND s.run_id = ?
4044
))

src/features/setup/.prompts.tmpl.yaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@ scoring:
3737

3838
summarise:
3939
one_paragraph: |
40-
Write ONE paragraph (<=100 words) that summarises the project for an experienced engineer.
41-
Include purpose, core tech, standout capability, maturity signal (if any), ideal use case.
42-
No bullet points or headings or em dashes. Neutral tone. Do not invent facts.
43-
Your summary must stand the test of time; do not mention scores.
40+
Write ONE paragraph (100 words) that explains the project in plain language
41+
for a general audience with no technical background.
42+
Focus on what it does and why it matters.
43+
Keep it clear, simple, and factual. Do not use bullet points, headings, or em dashes.
4444
4545
map_header: |
46-
From the following text, extract 2-3 concise bullets (10-18 words each), no fluff.
47-
Focus on: purpose, core tech/architecture, standout capabilities, maturity signals (derive only if stated).
46+
From the following text, extract 2-3 short bullets (10-18 words each) in plain language.
47+
Summarise what the project does and why it might be useful.
4848
Return only bullets prefixed with "- ".
4949
5050
reduce: |
51-
Write ONE paragraph (≤100 words) for the general public.
52-
Include: purpose, core tech/approach, one standout capability, maturity signal (if present), ideal use case.
53-
No marketing language. Present tense. If something isn’t in the notes, omit, do not guess. No em dashes.
54-
Return only the paragraph. Do not mention numeric scores.
51+
Write ONE paragraph (≤100 words) in plain language for a general audience.
52+
Explain what the project does and why it might be useful.
53+
Keep it clear, simple, and factual.
54+
Return only the paragraph, no bullet points or em dashes.
5555

0 commit comments

Comments
 (0)