@llmindset/hf-mcp 0.1.16
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/LICENSE +21 -0
- package/dist/dataset-detail.d.ts +26 -0
- package/dist/dataset-detail.d.ts.map +1 -0
- package/dist/dataset-detail.js +157 -0
- package/dist/dataset-detail.js.map +1 -0
- package/dist/dataset-search.d.ts +62 -0
- package/dist/dataset-search.d.ts.map +1 -0
- package/dist/dataset-search.js +158 -0
- package/dist/dataset-search.js.map +1 -0
- package/dist/duplicate-space.d.ts +75 -0
- package/dist/duplicate-space.d.ts.map +1 -0
- package/dist/duplicate-space.js +189 -0
- package/dist/duplicate-space.js.map +1 -0
- package/dist/error-messages.d.ts +4 -0
- package/dist/error-messages.d.ts.map +1 -0
- package/dist/error-messages.js +30 -0
- package/dist/error-messages.js.map +1 -0
- package/dist/hf-api-call.d.ts +18 -0
- package/dist/hf-api-call.d.ts.map +1 -0
- package/dist/hf-api-call.js +105 -0
- package/dist/hf-api-call.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/model-detail.d.ts +26 -0
- package/dist/model-detail.d.ts.map +1 -0
- package/dist/model-detail.js +224 -0
- package/dist/model-detail.js.map +1 -0
- package/dist/model-search.d.ts +64 -0
- package/dist/model-search.d.ts.map +1 -0
- package/dist/model-search.js +161 -0
- package/dist/model-search.js.map +1 -0
- package/dist/paper-search.d.ts +58 -0
- package/dist/paper-search.d.ts.map +1 -0
- package/dist/paper-search.js +114 -0
- package/dist/paper-search.js.map +1 -0
- package/dist/paper-summary.d.ts +35 -0
- package/dist/paper-summary.d.ts.map +1 -0
- package/dist/paper-summary.js +187 -0
- package/dist/paper-summary.js.map +1 -0
- package/dist/space-files.d.ts +44 -0
- package/dist/space-files.d.ts.map +1 -0
- package/dist/space-files.js +242 -0
- package/dist/space-files.js.map +1 -0
- package/dist/space-info.d.ts +56 -0
- package/dist/space-info.d.ts.map +1 -0
- package/dist/space-info.js +135 -0
- package/dist/space-info.js.map +1 -0
- package/dist/space-search.d.ts +71 -0
- package/dist/space-search.d.ts.map +1 -0
- package/dist/space-search.js +95 -0
- package/dist/space-search.js.map +1 -0
- package/dist/tool-ids.d.ts +23 -0
- package/dist/tool-ids.d.ts.map +1 -0
- package/dist/tool-ids.js +55 -0
- package/dist/tool-ids.js.map +1 -0
- package/dist/user-summary.d.ts +56 -0
- package/dist/user-summary.d.ts.map +1 -0
- package/dist/user-summary.js +271 -0
- package/dist/user-summary.js.map +1 -0
- package/dist/utilities.d.ts +8 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +53 -0
- package/dist/utilities.js.map +1 -0
- package/eslint.config.js +43 -0
- package/package.json +47 -0
- package/src/dataset-detail.ts +257 -0
- package/src/dataset-search.ts +237 -0
- package/src/duplicate-space.ts +263 -0
- package/src/error-messages.ts +57 -0
- package/src/hf-api-call.ts +182 -0
- package/src/index.ts +18 -0
- package/src/model-detail.ts +359 -0
- package/src/model-search.ts +231 -0
- package/src/paper-search.ts +188 -0
- package/src/paper-summary.ts +303 -0
- package/src/space-files.ts +325 -0
- package/src/space-info.ts +190 -0
- package/src/space-search.ts +177 -0
- package/src/tool-ids.ts +84 -0
- package/src/user-summary.ts +421 -0
- package/src/utilities.ts +64 -0
- package/test/duplicate-space.spec.ts +41 -0
- package/test/fixtures/paper_result_kazakh.json +854 -0
- package/test/fixtures/space-result.json +263 -0
- package/test/paper-search.spec.ts +57 -0
- package/test/paper-summary.spec.ts +113 -0
- package/test/space-files.spec.ts +232 -0
- package/test/space-search.spec.ts +29 -0
- package/test/user-summary.spec.ts +131 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { HfApiCall } from './hf-api-call.js';
|
|
3
|
+
import { formatUnknownDate } from './utilities.js';
|
|
4
|
+
|
|
5
|
+
// https://github.com/huggingface/huggingface_hub/blob/a26b93e8ba0b51ce76ce5c2044896587c47c6b60/src/huggingface_hub/hf_api.py#L1481-L1542
|
|
6
|
+
// Raw JSON response for https://hf.co/api/papers/search?q=llama%203%20herd Llama Herd is ~50,000 tokens
|
|
7
|
+
// Raw JSON response for https://hf.co/api/papers/search?q=kazakh -> ~ 9 papers,
|
|
8
|
+
// Return papers as delimited markdown (or simplified JSON)
|
|
9
|
+
// ---
|
|
10
|
+
//
|
|
11
|
+
// can we link to Collections, Datasets, Models, Spaces?
|
|
12
|
+
// Create a schema validator for search parameters
|
|
13
|
+
|
|
14
|
+
// 80 papers in full mode is ~ 35,000 tokens
|
|
15
|
+
// 105 papers in summary mode is ~ 23094 tokens
|
|
16
|
+
// 105 papers in full mode is ~ 45797 tokens
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_AUTHORS_TO_SHOW = 8;
|
|
19
|
+
const RESULTS_TO_RETURN = 10;
|
|
20
|
+
|
|
21
|
+
export const PAPER_SEARCH_TOOL_CONFIG = {
|
|
22
|
+
name: 'paper_search',
|
|
23
|
+
description:
|
|
24
|
+
'Find Machine Learning research papers on the Hugging Face hub. ' +
|
|
25
|
+
"Include 'Link to paper' When presenting the results. " +
|
|
26
|
+
'Consider whether tabulating results matches user intent.',
|
|
27
|
+
schema: z.object({
|
|
28
|
+
query: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(3, 'Supply at least one search term')
|
|
31
|
+
.max(200, 'Query too long')
|
|
32
|
+
.describe('Semantic Search query'),
|
|
33
|
+
results_limit: z.number().optional().default(12).describe('Number of results to return'),
|
|
34
|
+
concise_only: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.default(false)
|
|
38
|
+
.describe(
|
|
39
|
+
'Return a 2 sentence summary of the abstract. Use for broad search terms which may return a lot of results. Check with User if unsure.'
|
|
40
|
+
),
|
|
41
|
+
}),
|
|
42
|
+
annotations: {
|
|
43
|
+
title: 'Paper Search',
|
|
44
|
+
destructiveHint: false,
|
|
45
|
+
readOnlyHint: true,
|
|
46
|
+
openWorldHint: true,
|
|
47
|
+
},
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export interface Author {
|
|
51
|
+
name?: string;
|
|
52
|
+
user?: {
|
|
53
|
+
user: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Paper {
|
|
58
|
+
id: string;
|
|
59
|
+
authors?: Author[];
|
|
60
|
+
publishedAt?: string;
|
|
61
|
+
title?: string;
|
|
62
|
+
summary?: string;
|
|
63
|
+
upvotes?: number;
|
|
64
|
+
ai_keywords?: string[];
|
|
65
|
+
ai_summary?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PaperSearchResult {
|
|
69
|
+
paper: Paper;
|
|
70
|
+
numComments?: number;
|
|
71
|
+
isAuthorParticipating?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Define input types for paper search
|
|
75
|
+
interface PaperSearchParams {
|
|
76
|
+
q: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Service for searching Hugging Face Papers
|
|
81
|
+
*/
|
|
82
|
+
export class PaperSearchTool extends HfApiCall<PaperSearchParams, PaperSearchResult[]> {
|
|
83
|
+
/**
|
|
84
|
+
* Creates a new papers search service
|
|
85
|
+
* @param apiUrl The URL of the Hugging Face papers search API
|
|
86
|
+
* @param hfToken Optional Hugging Face token for API access
|
|
87
|
+
*/
|
|
88
|
+
constructor(hfToken?: string, apiUrl = 'https://huggingface.co/api/papers/search') {
|
|
89
|
+
super(apiUrl, hfToken);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Searches for papers on the Hugging Face Hub
|
|
94
|
+
* @param query Search query string (e.g. "llama", "attention")
|
|
95
|
+
* @param limit Maximum number of results to return
|
|
96
|
+
* @returns Formatted string with paper information
|
|
97
|
+
*/
|
|
98
|
+
async search(query: string, limit: number = RESULTS_TO_RETURN, conciseOnly: boolean = false): Promise<string> {
|
|
99
|
+
try {
|
|
100
|
+
if (!query) return 'No query';
|
|
101
|
+
|
|
102
|
+
const papers = await this.callApi<PaperSearchResult[]>({ q: query });
|
|
103
|
+
|
|
104
|
+
if (papers.length === 0) return `No papers found for query '${query}'`;
|
|
105
|
+
return formatSearchResults(query, papers.slice(0, limit), papers.length, conciseOnly);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
throw new Error(`Failed to search for papers: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function published(publishedDate: string | undefined): string {
|
|
116
|
+
const formatted = formatUnknownDate(publishedDate ?? '');
|
|
117
|
+
return formatted ? `Published on ${formatted}` : 'Publication date not available';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatSearchResults(
|
|
121
|
+
query: string,
|
|
122
|
+
papers: PaperSearchResult[],
|
|
123
|
+
totalCount: number,
|
|
124
|
+
conciseOnly: boolean = false
|
|
125
|
+
): string {
|
|
126
|
+
const r: string[] = [];
|
|
127
|
+
const showingText =
|
|
128
|
+
papers.length < totalCount
|
|
129
|
+
? `${totalCount} papers matched the query '${query}'. Here are the first ${papers.length} results.`
|
|
130
|
+
: `All ${papers.length} papers that matched the query '${query}'`;
|
|
131
|
+
r.push(showingText);
|
|
132
|
+
|
|
133
|
+
for (const result of papers) {
|
|
134
|
+
r.push('');
|
|
135
|
+
r.push('---');
|
|
136
|
+
const title = result.paper.title ?? `Paper ID ${result.paper.id}`;
|
|
137
|
+
r.push('');
|
|
138
|
+
r.push(`## ${title}`);
|
|
139
|
+
r.push('');
|
|
140
|
+
const publishedDate = result.paper.publishedAt
|
|
141
|
+
? `Published on ${published(result.paper.publishedAt)}`
|
|
142
|
+
: 'Publication date not available';
|
|
143
|
+
r.push(publishedDate);
|
|
144
|
+
r.push(authors(result.paper.authors));
|
|
145
|
+
r.push('');
|
|
146
|
+
// Handle concise_only option: use ai_summary when enabled, or fallback to ai_summary if summary is blank
|
|
147
|
+
const useAiSummary = conciseOnly || !result.paper.summary;
|
|
148
|
+
const summaryText = useAiSummary ? result.paper.ai_summary : result.paper.summary;
|
|
149
|
+
const summaryHeader = useAiSummary ? '### AI Generated Summary' : '### Abstract';
|
|
150
|
+
|
|
151
|
+
r.push(summaryHeader);
|
|
152
|
+
r.push('');
|
|
153
|
+
r.push(summaryText ?? 'No summary available');
|
|
154
|
+
r.push('');
|
|
155
|
+
r.push(result.paper.ai_keywords ? `**AI Keywords**: ${result.paper.ai_keywords.join(', ')}` : '');
|
|
156
|
+
|
|
157
|
+
const upvotes: string =
|
|
158
|
+
result.paper.upvotes && result.paper.upvotes > 0 ? `Upvoted ${result.paper.upvotes} times` : '';
|
|
159
|
+
|
|
160
|
+
if (result.numComments && result.numComments > 0) {
|
|
161
|
+
if (result.isAuthorParticipating)
|
|
162
|
+
r.push(`${upvotes}. The authors are participating in a discussion with ${result.numComments} comments.`);
|
|
163
|
+
else r.push(`${upvotes}. There is a community discussion with ${result.numComments} comments.`);
|
|
164
|
+
} else {
|
|
165
|
+
if ('' != upvotes) r.push(upvotes);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
r.push(`**Link to paper:** [https://hf.co/papers/${result.paper.id}](https://hf.co/papers/${result.paper.id})`);
|
|
169
|
+
}
|
|
170
|
+
r.push('');
|
|
171
|
+
r.push('---');
|
|
172
|
+
return r.join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function authors(authors: Author[] | undefined, authorsToShow: number = DEFAULT_AUTHORS_TO_SHOW): string {
|
|
176
|
+
if (!authors || 0 === authors.length) return '**Authors:** Not available';
|
|
177
|
+
const f: string[] = [];
|
|
178
|
+
for (const author of authors.slice(0, authorsToShow)) {
|
|
179
|
+
const profileLink: string = author.user?.user ? ` ([${author.user.user}](https://hf.co/${author.user.user}))` : '';
|
|
180
|
+
const authorName: string = author.name ?? 'Unknown';
|
|
181
|
+
f.push(`${authorName}${profileLink}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (authors.length > authorsToShow) {
|
|
185
|
+
f.push(`and ${authors.length - authorsToShow} more.`);
|
|
186
|
+
}
|
|
187
|
+
return `**Authors:** ${f.join(', ')}`;
|
|
188
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { HfApiCall, HfApiError } from './hf-api-call.js';
|
|
3
|
+
import { formatDate, formatNumber, escapeMarkdown } from './utilities.js';
|
|
4
|
+
import { ModelSearchTool } from './model-search.js';
|
|
5
|
+
import { DatasetSearchTool } from './dataset-search.js';
|
|
6
|
+
import { SpaceSearchTool } from './space-search.js';
|
|
7
|
+
import { authors, type Author } from './paper-search.js';
|
|
8
|
+
|
|
9
|
+
// Paper Summary Prompt Configuration
|
|
10
|
+
export const PAPER_SUMMARY_PROMPT_CONFIG = {
|
|
11
|
+
name: 'Paper Summary',
|
|
12
|
+
description:
|
|
13
|
+
'Generate a comprehensive summary of an arXiv paper including its details and related models, datasets, and spaces on Hugging Face. ' +
|
|
14
|
+
'Accepts various formats: "2502.16161", "arxiv:2502.16161", "https://arxiv.org/abs/2502.16161", or Hugging Face paper URLs.',
|
|
15
|
+
schema: z.object({
|
|
16
|
+
paper_id: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1, 'Paper ID is required')
|
|
19
|
+
.describe('arXiv paper ID in various formats (e.g., "2502.16161", "arxiv:2502.16161", or full URL)')
|
|
20
|
+
.max(60)
|
|
21
|
+
.describe('Maximum length is 100 characters'),
|
|
22
|
+
}),
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
// Define parameter types
|
|
26
|
+
export type PaperSummaryParams = z.infer<typeof PAPER_SUMMARY_PROMPT_CONFIG.schema>;
|
|
27
|
+
|
|
28
|
+
// Paper API response interface
|
|
29
|
+
interface PaperDetails {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
authors?: Author[];
|
|
33
|
+
publishedAt: string;
|
|
34
|
+
summary?: string; // This is the abstract field in the API
|
|
35
|
+
upvotes?: number;
|
|
36
|
+
comments?: number;
|
|
37
|
+
pageUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validates and extracts arXiv ID from various input formats
|
|
42
|
+
* @param input - The user input (arXiv ID or URL)
|
|
43
|
+
* @returns The extracted arXiv ID in format "YYMM.NNNNN"
|
|
44
|
+
* @throws Error if input is invalid
|
|
45
|
+
*/
|
|
46
|
+
export function extractArxivIdFromInput(input: string): string {
|
|
47
|
+
// Remove whitespace
|
|
48
|
+
const trimmed = input.trim();
|
|
49
|
+
|
|
50
|
+
// Check for empty input
|
|
51
|
+
if (!trimmed) {
|
|
52
|
+
throw new Error('Paper ID is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Pattern for valid arXiv ID: YYMM.NNNNN (e.g., 2502.16161)
|
|
56
|
+
const arxivPattern = /^\d{4}\.\d{4,5}$/;
|
|
57
|
+
|
|
58
|
+
// Check if it's already a plain arXiv ID
|
|
59
|
+
if (arxivPattern.test(trimmed)) {
|
|
60
|
+
return trimmed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle URL formats first - check if it looks like a URL
|
|
64
|
+
// Check for: protocol, www prefix, domain pattern with TLD, or path separator
|
|
65
|
+
const urlPattern = /^(https?:\/\/|www\.)|^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(\/|$)/;
|
|
66
|
+
if (urlPattern.test(trimmed) || trimmed.includes('://')) {
|
|
67
|
+
let url: URL;
|
|
68
|
+
try {
|
|
69
|
+
// Try to parse as URL, adding protocol if missing
|
|
70
|
+
if (!trimmed.startsWith('http')) {
|
|
71
|
+
url = new URL(`https://${trimmed}`);
|
|
72
|
+
} else {
|
|
73
|
+
url = new URL(trimmed);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error('Invalid URL format');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for query parameters or fragments
|
|
80
|
+
if (url.search || url.hash) {
|
|
81
|
+
throw new Error('URL must contain only the paper ID path');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Only accept specific domains
|
|
85
|
+
const allowedHosts = ['arxiv.org', 'www.arxiv.org', 'huggingface.co', 'hf.co'];
|
|
86
|
+
if (!allowedHosts.includes(url.hostname)) {
|
|
87
|
+
throw new Error(`URL must be from arxiv.org, huggingface.co, or hf.co. Got: ${url.hostname}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle arxiv.org URLs
|
|
91
|
+
if (url.hostname === 'arxiv.org' || url.hostname === 'www.arxiv.org') {
|
|
92
|
+
// Pattern: /abs/YYMM.NNNNN
|
|
93
|
+
const match = url.pathname.match(/\/abs\/(\d{4}\.\d{4,5})/);
|
|
94
|
+
if (match && match[1]) {
|
|
95
|
+
return match[1];
|
|
96
|
+
}
|
|
97
|
+
throw new Error('arXiv URL must be in format: arxiv.org/abs/YYMM.NNNNN');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle Hugging Face paper URLs
|
|
101
|
+
if (url.hostname === 'huggingface.co' || url.hostname === 'hf.co') {
|
|
102
|
+
// Pattern: /papers/YYMM.NNNNN
|
|
103
|
+
const match = url.pathname.match(/\/papers\/(\d{4}\.\d{4,5})/);
|
|
104
|
+
if (match && match[1]) {
|
|
105
|
+
return match[1];
|
|
106
|
+
}
|
|
107
|
+
throw new Error('Hugging Face URL must be in format: hf.co/papers/YYMM.NNNNN');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// This should never be reached due to the allowedHosts check above
|
|
111
|
+
throw new Error('URL does not contain a valid arXiv ID');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle "arxiv:" prefix variations
|
|
115
|
+
if (trimmed.toLowerCase().startsWith('arxiv:')) {
|
|
116
|
+
const id = trimmed.substring(6);
|
|
117
|
+
if (arxivPattern.test(id)) {
|
|
118
|
+
return id;
|
|
119
|
+
}
|
|
120
|
+
throw new Error('Invalid arXiv ID format after "arxiv:" prefix');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle "arxiv." prefix (typo)
|
|
124
|
+
if (trimmed.toLowerCase().startsWith('arxiv.')) {
|
|
125
|
+
const id = trimmed.substring(6);
|
|
126
|
+
if (arxivPattern.test(id)) {
|
|
127
|
+
return id;
|
|
128
|
+
}
|
|
129
|
+
throw new Error('Invalid arXiv ID format after "arxiv." prefix');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If we get here, it's not a recognized format
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Invalid arXiv ID format: "${trimmed}". Expected formats: "2502.16161", "arxiv:2502.16161", or paper URL`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Service for generating comprehensive paper summaries
|
|
140
|
+
*/
|
|
141
|
+
export class PaperSummaryPrompt extends HfApiCall<Record<string, string>, PaperDetails> {
|
|
142
|
+
/**
|
|
143
|
+
* @param hfToken Optional Hugging Face token for API access
|
|
144
|
+
*/
|
|
145
|
+
constructor(hfToken?: string) {
|
|
146
|
+
super('https://huggingface.co/api/papers', hfToken);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generate a comprehensive paper summary
|
|
151
|
+
*/
|
|
152
|
+
async generateSummary(params: PaperSummaryParams): Promise<string> {
|
|
153
|
+
try {
|
|
154
|
+
// Extract and validate arXiv ID
|
|
155
|
+
const arxivId = extractArxivIdFromInput(params.paper_id);
|
|
156
|
+
|
|
157
|
+
// Get paper details
|
|
158
|
+
let paperDetails: PaperDetails;
|
|
159
|
+
try {
|
|
160
|
+
paperDetails = await this.getPaperDetails(arxivId);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error instanceof HfApiError && error.status === 404) {
|
|
163
|
+
return "I'm sorry, paper not found.";
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Build the summary
|
|
169
|
+
const sections: string[] = [];
|
|
170
|
+
|
|
171
|
+
// Paper details section
|
|
172
|
+
sections.push(this.formatPaperDetails(paperDetails));
|
|
173
|
+
|
|
174
|
+
// Search for related resources
|
|
175
|
+
const relatedResources = await this.getRelatedResources(arxivId);
|
|
176
|
+
|
|
177
|
+
// Add related models section if found
|
|
178
|
+
if (relatedResources.models) {
|
|
179
|
+
sections.push(relatedResources.models);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add related datasets section if found
|
|
183
|
+
if (relatedResources.datasets) {
|
|
184
|
+
sections.push(relatedResources.datasets);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add related spaces section if found
|
|
188
|
+
if (relatedResources.spaces) {
|
|
189
|
+
sections.push(relatedResources.spaces);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Add reminder about tags
|
|
193
|
+
sections.push(
|
|
194
|
+
'\n**Note:** Tags and paper references on Hugging Face are not always complete or up-to-date. ' +
|
|
195
|
+
'-- validate information if necessary'
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Add final instruction
|
|
199
|
+
sections.push('\nPlease provide a summary of this paper and any associated resources.');
|
|
200
|
+
|
|
201
|
+
return sections.join('\n\n');
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof Error) {
|
|
204
|
+
throw new Error(`Failed to generate paper summary: ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get paper details from HF API
|
|
212
|
+
*/
|
|
213
|
+
private async getPaperDetails(arxivId: string): Promise<PaperDetails> {
|
|
214
|
+
const url = new URL(`${this.apiUrl}/${arxivId}`);
|
|
215
|
+
return this.fetchFromApi<PaperDetails>(url);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Format paper details as markdown
|
|
220
|
+
*/
|
|
221
|
+
private formatPaperDetails(paper: PaperDetails): string {
|
|
222
|
+
const lines: string[] = [];
|
|
223
|
+
|
|
224
|
+
// Title as main heading
|
|
225
|
+
lines.push(`# ${escapeMarkdown(paper.title || 'Untitled')}`);
|
|
226
|
+
lines.push('');
|
|
227
|
+
|
|
228
|
+
// Authors - use the existing authors formatting function
|
|
229
|
+
lines.push(authors(paper.authors));
|
|
230
|
+
|
|
231
|
+
// Published date
|
|
232
|
+
lines.push(`**Published:** ${formatDate(paper.publishedAt)}`);
|
|
233
|
+
|
|
234
|
+
// Engagement metrics - only show if they exist and are > 0
|
|
235
|
+
if (paper.upvotes && paper.upvotes > 0) {
|
|
236
|
+
lines.push(`**Upvotes:** ${formatNumber(paper.upvotes)}`);
|
|
237
|
+
}
|
|
238
|
+
if (paper.comments && paper.comments > 0) {
|
|
239
|
+
lines.push(`**Comments:** ${formatNumber(paper.comments)}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Links
|
|
243
|
+
lines.push('');
|
|
244
|
+
lines.push('**Links:**');
|
|
245
|
+
lines.push(`- [Hugging Face Paper Page](https://hf.co/papers/${paper.id})`);
|
|
246
|
+
lines.push(`- [arXiv Page](https://arxiv.org/abs/${paper.id})`);
|
|
247
|
+
|
|
248
|
+
// Abstract
|
|
249
|
+
if (paper.summary) {
|
|
250
|
+
lines.push('');
|
|
251
|
+
lines.push('## Abstract');
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push(paper.summary);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return lines.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Search for related resources (models, datasets, spaces)
|
|
261
|
+
*/
|
|
262
|
+
private async getRelatedResources(arxivId: string): Promise<{ models?: string; datasets?: string; spaces?: string }> {
|
|
263
|
+
const results: { models?: string; datasets?: string; spaces?: string } = {};
|
|
264
|
+
|
|
265
|
+
// Search for related models
|
|
266
|
+
try {
|
|
267
|
+
const modelSearch = new ModelSearchTool(this.hfToken);
|
|
268
|
+
// Use the filter parameter to search for models referencing this paper
|
|
269
|
+
const modelResults = await modelSearch.searchWithFilter(`arxiv:${arxivId}`, 25);
|
|
270
|
+
if (modelResults && !modelResults.includes('No models found')) {
|
|
271
|
+
results.models = `## Related Models\n\n${modelResults}`;
|
|
272
|
+
}
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.warn(`Failed to fetch related models for paper ${arxivId}:`, error);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Search for related datasets
|
|
278
|
+
try {
|
|
279
|
+
const datasetSearch = new DatasetSearchTool(this.hfToken);
|
|
280
|
+
// Use the filter parameter to search for datasets referencing this paper
|
|
281
|
+
const datasetResults = await datasetSearch.searchWithFilter(`arxiv:${arxivId}`, 25);
|
|
282
|
+
if (datasetResults && !datasetResults.includes('No datasets found')) {
|
|
283
|
+
results.datasets = `## Related Datasets\n\n${datasetResults}`;
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.warn(`Failed to fetch related datasets for paper ${arxivId}:`, error);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Search for related spaces
|
|
290
|
+
try {
|
|
291
|
+
const spaceSearch = new SpaceSearchTool(this.hfToken);
|
|
292
|
+
// Use the filter parameter to search for spaces referencing this paper
|
|
293
|
+
const spaceResults = await spaceSearch.searchWithFilter(`arxiv:${arxivId}`, 25, 2);
|
|
294
|
+
if (spaceResults && !spaceResults.includes('No matching Hugging Face Spaces found')) {
|
|
295
|
+
results.spaces = `## Related Spaces\n\n${spaceResults}`;
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn(`Failed to fetch related spaces for paper ${arxivId}:`, error);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return results;
|
|
302
|
+
}
|
|
303
|
+
}
|