@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,359 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { modelInfo } from '@huggingface/hub';
|
|
3
|
+
import { formatDate, formatNumber } from './utilities.js';
|
|
4
|
+
|
|
5
|
+
const SPACES_TO_INCLUDE = 12;
|
|
6
|
+
// Model Detail Tool Configuration
|
|
7
|
+
export const MODEL_DETAIL_TOOL_CONFIG = {
|
|
8
|
+
name: 'model_details',
|
|
9
|
+
description: 'Get detailed information about a specific model from the Hugging Face Hub.',
|
|
10
|
+
schema: z.object({
|
|
11
|
+
model_id: z.string().min(1, 'Model ID is required').describe('Model ID (e.g., microsoft/DialoGPT-large)'),
|
|
12
|
+
}),
|
|
13
|
+
annotations: {
|
|
14
|
+
title: 'Model Details',
|
|
15
|
+
destructiveHint: false,
|
|
16
|
+
readOnlyHint: true,
|
|
17
|
+
openWorldHint: false,
|
|
18
|
+
},
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type ModelDetailParams = z.infer<typeof MODEL_DETAIL_TOOL_CONFIG.schema>;
|
|
22
|
+
|
|
23
|
+
// Clean interface design with explicit data availability
|
|
24
|
+
|
|
25
|
+
// Required core information that should always be available
|
|
26
|
+
interface ModelBasicInfo {
|
|
27
|
+
id: string; // Model ID
|
|
28
|
+
name: string; // Model name
|
|
29
|
+
downloads: number;
|
|
30
|
+
likes: number;
|
|
31
|
+
private: boolean;
|
|
32
|
+
gated: false | 'auto' | 'manual';
|
|
33
|
+
updatedAt: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Optional but reliable information with simple types
|
|
37
|
+
interface ModelExtendedInfo {
|
|
38
|
+
author?: string;
|
|
39
|
+
library_name?: string;
|
|
40
|
+
pipeline_tag?: string; // Task type
|
|
41
|
+
downloadsAllTime?: number;
|
|
42
|
+
tags?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Technical details that need validation
|
|
46
|
+
interface ModelTechnicalDetails {
|
|
47
|
+
modelType?: string; // From config.model_type if exists
|
|
48
|
+
vocabSize?: number; // From config.vocab_size if exists
|
|
49
|
+
parameters?: number; // From safetensors.total if exists
|
|
50
|
+
modelClass?: string; // From transformersInfo.auto_model if exists
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Metadata from cardData with careful extraction
|
|
54
|
+
interface ModelMetadata {
|
|
55
|
+
language?: string | string[];
|
|
56
|
+
license?: string | string[];
|
|
57
|
+
datasets?: string | string[];
|
|
58
|
+
fineTunedFrom?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Complete model information structure
|
|
62
|
+
interface ModelInformation extends ModelBasicInfo {
|
|
63
|
+
extended?: ModelExtendedInfo;
|
|
64
|
+
technical?: ModelTechnicalDetails;
|
|
65
|
+
metadata?: ModelMetadata;
|
|
66
|
+
spaces?: Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
title?: string;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Service for getting detailed model information using the official huggingface.js library
|
|
75
|
+
*/
|
|
76
|
+
export class ModelDetailTool {
|
|
77
|
+
private readonly hubUrl?: string;
|
|
78
|
+
private readonly accessToken?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new model detail service
|
|
82
|
+
* @param hfToken Optional Hugging Face token for API access
|
|
83
|
+
* @param hubUrl Optional custom hub URL
|
|
84
|
+
*/
|
|
85
|
+
constructor(hfToken?: string, hubUrl?: string) {
|
|
86
|
+
this.accessToken = hfToken;
|
|
87
|
+
this.hubUrl = hubUrl;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get detailed information about a specific model
|
|
92
|
+
*
|
|
93
|
+
* @param modelId The model ID to get details for (e.g., microsoft/DialoGPT-large)
|
|
94
|
+
* @returns Formatted string with model details
|
|
95
|
+
*/
|
|
96
|
+
async getDetails(modelId: string): Promise<string> {
|
|
97
|
+
try {
|
|
98
|
+
// Define additional fields we want to retrieve (only those available in the hub library)
|
|
99
|
+
const additionalFields = [
|
|
100
|
+
'author',
|
|
101
|
+
'downloadsAllTime',
|
|
102
|
+
'library_name',
|
|
103
|
+
'tags',
|
|
104
|
+
'config',
|
|
105
|
+
'transformersInfo',
|
|
106
|
+
'safetensors',
|
|
107
|
+
'cardData',
|
|
108
|
+
'spaces',
|
|
109
|
+
] as const;
|
|
110
|
+
|
|
111
|
+
const modelData = await modelInfo<(typeof additionalFields)[number]>({
|
|
112
|
+
name: modelId,
|
|
113
|
+
additionalFields: Array.from(additionalFields),
|
|
114
|
+
...(this.accessToken && { credentials: { accessToken: this.accessToken } }),
|
|
115
|
+
...(this.hubUrl && { hubUrl: this.hubUrl }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Build the structured model information
|
|
119
|
+
const modelDetails: ModelInformation = {
|
|
120
|
+
// Basic info (required fields)
|
|
121
|
+
id: modelId,
|
|
122
|
+
name: modelData.name,
|
|
123
|
+
downloads: modelData.downloads,
|
|
124
|
+
likes: modelData.likes,
|
|
125
|
+
private: modelData.private,
|
|
126
|
+
gated: modelData.gated,
|
|
127
|
+
updatedAt: modelData.updatedAt,
|
|
128
|
+
|
|
129
|
+
// Extended info (optional but reliable fields)
|
|
130
|
+
extended: {
|
|
131
|
+
author: modelData.author,
|
|
132
|
+
library_name: modelData.library_name,
|
|
133
|
+
pipeline_tag: modelData.task,
|
|
134
|
+
downloadsAllTime: modelData.downloadsAllTime,
|
|
135
|
+
tags: modelData.tags,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Technical details (requires validation)
|
|
140
|
+
const technical: ModelTechnicalDetails = {};
|
|
141
|
+
|
|
142
|
+
// Extract config details safely if they exist
|
|
143
|
+
if (modelData.config && typeof modelData.config === 'object') {
|
|
144
|
+
const config = modelData.config as Record<string, unknown>;
|
|
145
|
+
if ('model_type' in config && typeof config.model_type === 'string') {
|
|
146
|
+
technical.modelType = config.model_type;
|
|
147
|
+
}
|
|
148
|
+
if ('vocab_size' in config && typeof config.vocab_size === 'number') {
|
|
149
|
+
technical.vocabSize = config.vocab_size;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Extract safe tensors info
|
|
154
|
+
if (modelData.safetensors && typeof modelData.safetensors.total === 'number') {
|
|
155
|
+
technical.parameters = modelData.safetensors.total;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Extract transformers info
|
|
159
|
+
if (modelData.transformersInfo && modelData.transformersInfo.auto_model) {
|
|
160
|
+
technical.modelClass = modelData.transformersInfo.auto_model;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Only add technical section if we have data
|
|
164
|
+
if (Object.keys(technical).length > 0) {
|
|
165
|
+
modelDetails.technical = technical;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Metadata from card data
|
|
169
|
+
if (modelData.cardData) {
|
|
170
|
+
const metadata: ModelMetadata = {};
|
|
171
|
+
const cardData = modelData.cardData as Record<string, unknown>;
|
|
172
|
+
|
|
173
|
+
if ('language' in cardData) {
|
|
174
|
+
metadata.language = cardData.language as string | string[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ('license' in cardData) {
|
|
178
|
+
metadata.license = cardData.license as string | string[];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ('datasets' in cardData) {
|
|
182
|
+
metadata.datasets = cardData.datasets as string | string[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ('finetuned_from' in cardData) {
|
|
186
|
+
metadata.fineTunedFrom = cardData.finetuned_from as string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Only add metadata section if we have data
|
|
190
|
+
if (Object.keys(metadata).length > 0) {
|
|
191
|
+
modelDetails.metadata = metadata;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Extract spaces information if available
|
|
196
|
+
const spaces = modelData.spaces;
|
|
197
|
+
if (Array.isArray(spaces) && spaces.length > 0) {
|
|
198
|
+
try {
|
|
199
|
+
modelDetails.spaces = spaces.map((spaceId) => {
|
|
200
|
+
// Format is typically username/spacename
|
|
201
|
+
const parts = spaceId.split('/');
|
|
202
|
+
const name = parts.length > 1 ? parts[1] : spaceId;
|
|
203
|
+
return {
|
|
204
|
+
id: spaceId,
|
|
205
|
+
name: name || spaceId, // Ensure name is always a string
|
|
206
|
+
title: name, // Default to name if title not available
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
210
|
+
} catch (ignoreUnformattedSpaces) {
|
|
211
|
+
console.error(`Error processing spaces for model ${modelId}:`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return formatModelDetails(modelDetails);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (error instanceof Error) {
|
|
218
|
+
throw new Error(`Failed to get model details: ${error.message}`);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Formatting Function
|
|
226
|
+
function formatModelDetails(model: ModelInformation): string {
|
|
227
|
+
const r: string[] = [];
|
|
228
|
+
const [authorFromName] = model.name.includes('/') ? model.name.split('/') : ['', model.name];
|
|
229
|
+
|
|
230
|
+
r.push(`# ${model.name}`);
|
|
231
|
+
r.push('');
|
|
232
|
+
|
|
233
|
+
// Overview section - using only reliable fields
|
|
234
|
+
r.push('## Overview');
|
|
235
|
+
|
|
236
|
+
// Author - from extended info or parsed from name
|
|
237
|
+
if (model.extended?.author || authorFromName) {
|
|
238
|
+
r.push(`- **Author:** ${model.extended?.author || authorFromName || ''}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Task type
|
|
242
|
+
if (model.extended?.pipeline_tag) {
|
|
243
|
+
r.push(`- **Task:** ${model.extended.pipeline_tag}`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Library
|
|
247
|
+
if (model.extended?.library_name) {
|
|
248
|
+
r.push(`- **Library:** ${model.extended.library_name}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Statistics
|
|
252
|
+
const stats = [];
|
|
253
|
+
if (model.extended?.downloadsAllTime) {
|
|
254
|
+
stats.push(`**Downloads:** ${formatNumber(model.extended.downloadsAllTime)}`);
|
|
255
|
+
}
|
|
256
|
+
if (model.likes) {
|
|
257
|
+
stats.push(`**Likes:** ${model.likes.toString()}`);
|
|
258
|
+
}
|
|
259
|
+
if (stats.length > 0) {
|
|
260
|
+
r.push(`- ${stats.join(' | ')}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Dates
|
|
264
|
+
r.push(`- **Updated:** ${formatDate(model.updatedAt)}`);
|
|
265
|
+
|
|
266
|
+
// Status indicators
|
|
267
|
+
const status = [];
|
|
268
|
+
if (model.gated) status.push('🔒 Gated');
|
|
269
|
+
if (model.private) status.push('🔐 Private');
|
|
270
|
+
if (status.length > 0) {
|
|
271
|
+
r.push(`- **Status:** ${status.join(' | ')}`);
|
|
272
|
+
}
|
|
273
|
+
r.push('');
|
|
274
|
+
|
|
275
|
+
// Technical Details - only if we have validated information
|
|
276
|
+
if (model.technical && Object.keys(model.technical).length > 0) {
|
|
277
|
+
r.push('## Technical Details');
|
|
278
|
+
|
|
279
|
+
if (model.technical.modelClass) {
|
|
280
|
+
r.push(`- **Model Class:** ${model.technical.modelClass}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (model.technical.parameters) {
|
|
284
|
+
r.push(`- **Parameters:** ${formatNumber(model.technical.parameters)}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (model.technical.modelType) {
|
|
288
|
+
r.push(`- **Architecture:** ${model.technical.modelType}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (model.technical.vocabSize) {
|
|
292
|
+
r.push(`- **Vocab Size:** ${formatNumber(model.technical.vocabSize)}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
r.push('');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Tags - reliable field from extended info
|
|
299
|
+
if (model.extended?.tags && model.extended.tags.length > 0) {
|
|
300
|
+
r.push('## Tags');
|
|
301
|
+
r.push(model.extended.tags.map((tag) => `\`${tag}\``).join(' '));
|
|
302
|
+
r.push('');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Metadata - carefully extracted and validated
|
|
306
|
+
if (model.metadata) {
|
|
307
|
+
const metadata = [];
|
|
308
|
+
|
|
309
|
+
if (model.metadata.language) {
|
|
310
|
+
const languages = Array.isArray(model.metadata.language)
|
|
311
|
+
? model.metadata.language.join(', ')
|
|
312
|
+
: model.metadata.language;
|
|
313
|
+
metadata.push(`- **Language:** ${languages}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (model.metadata.license) {
|
|
317
|
+
const license = Array.isArray(model.metadata.license)
|
|
318
|
+
? model.metadata.license.join(', ')
|
|
319
|
+
: model.metadata.license;
|
|
320
|
+
metadata.push(`- **License:** ${license}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (model.metadata.datasets) {
|
|
324
|
+
const datasets = Array.isArray(model.metadata.datasets)
|
|
325
|
+
? model.metadata.datasets.join(', ')
|
|
326
|
+
: model.metadata.datasets;
|
|
327
|
+
metadata.push(`- **Datasets:** ${datasets}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (model.metadata.fineTunedFrom) {
|
|
331
|
+
metadata.push(`- **Fine-tuned from:** ${model.metadata.fineTunedFrom}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (metadata.length > 0) {
|
|
335
|
+
r.push('## Metadata');
|
|
336
|
+
r.push(...metadata);
|
|
337
|
+
r.push('');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Spaces - processed with validation
|
|
342
|
+
if (model.spaces && model.spaces.length > 0) {
|
|
343
|
+
r.push('## Demo Spaces');
|
|
344
|
+
for (const space of model.spaces.slice(0, SPACES_TO_INCLUDE)) {
|
|
345
|
+
const title = space.title || space.name;
|
|
346
|
+
r.push(`- [${title}](https://hf.co/spaces/${space.id})`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (model.spaces.length > SPACES_TO_INCLUDE) {
|
|
350
|
+
r.push(`- *... and ${(model.spaces.length - SPACES_TO_INCLUDE).toString()} more spaces*`);
|
|
351
|
+
}
|
|
352
|
+
r.push('');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Link is reliable - based on model name which is required
|
|
356
|
+
r.push(`**Link:** [https://hf.co/${model.name}](https://hf.co/${model.name})`);
|
|
357
|
+
|
|
358
|
+
return r.join('\n');
|
|
359
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { HfApiCall } from './hf-api-call.js';
|
|
3
|
+
import { formatDate, formatNumber } from './utilities.js';
|
|
4
|
+
|
|
5
|
+
export const TAGS_TO_RETURN = 20;
|
|
6
|
+
// Model Search Tool Configuration
|
|
7
|
+
export const MODEL_SEARCH_TOOL_CONFIG = {
|
|
8
|
+
name: 'model_search',
|
|
9
|
+
description:
|
|
10
|
+
'Find Machine Learning models hosted on Hugging Face. ' +
|
|
11
|
+
'Returns comprehensive information about matching models including downloads, likes, tags, and direct links. ' +
|
|
12
|
+
'Include links to the models in your response',
|
|
13
|
+
schema: z.object({
|
|
14
|
+
query: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(
|
|
18
|
+
'Search term. Leave blank and specify "sort" and "limit" to get e.g. "Top 20 trending models", "Top 10 most recent models" etc" '
|
|
19
|
+
),
|
|
20
|
+
author: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Organization or user who created the model (e.g., 'google', 'meta-llama', 'microsoft')"),
|
|
24
|
+
task: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Model task type (e.g., 'text-generation', 'image-classification', 'translation')"),
|
|
28
|
+
library: z.string().optional().describe("Framework the model uses (e.g., 'transformers', 'diffusers', 'timm')"),
|
|
29
|
+
sort: z
|
|
30
|
+
.enum(['trendingScore', 'downloads', 'likes', 'createdAt', 'lastModified'])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Sort order: trendingScore, downloads , likes, createdAt, lastModified'),
|
|
33
|
+
limit: z.number().min(1).max(100).optional().default(20).describe('Maximum number of results to return'),
|
|
34
|
+
}),
|
|
35
|
+
annotations: {
|
|
36
|
+
title: 'Model Search',
|
|
37
|
+
destructiveHint: false,
|
|
38
|
+
readOnlyHint: true,
|
|
39
|
+
openWorldHint: true,
|
|
40
|
+
},
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
// Define search parameter types
|
|
44
|
+
export type ModelSearchParams = z.infer<typeof MODEL_SEARCH_TOOL_CONFIG.schema>;
|
|
45
|
+
|
|
46
|
+
// API parameter interface for direct HF API calls
|
|
47
|
+
interface ModelApiParams {
|
|
48
|
+
search?: string;
|
|
49
|
+
author?: string;
|
|
50
|
+
filter?: string;
|
|
51
|
+
sort?: string;
|
|
52
|
+
direction?: string;
|
|
53
|
+
limit?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Model result interface matching HF API response
|
|
57
|
+
interface ModelApiResult {
|
|
58
|
+
_id: string;
|
|
59
|
+
id: string;
|
|
60
|
+
modelId: string;
|
|
61
|
+
likes: number;
|
|
62
|
+
downloads: number;
|
|
63
|
+
trendingScore?: number;
|
|
64
|
+
private: boolean;
|
|
65
|
+
tags: string[];
|
|
66
|
+
pipeline_tag?: string;
|
|
67
|
+
library_name?: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Service for searching Hugging Face Models using direct API calls
|
|
73
|
+
*/
|
|
74
|
+
export class ModelSearchTool extends HfApiCall<ModelApiParams, ModelApiResult[]> {
|
|
75
|
+
/**
|
|
76
|
+
* @param hfToken Optional Hugging Face token for API access
|
|
77
|
+
*/
|
|
78
|
+
constructor(hfToken?: string) {
|
|
79
|
+
super('https://huggingface.co/api/models', hfToken);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Search for models with detailed parameters
|
|
84
|
+
*/
|
|
85
|
+
async searchWithParams(params: Partial<ModelSearchParams>): Promise<string> {
|
|
86
|
+
try {
|
|
87
|
+
// Convert our params to the HF API format
|
|
88
|
+
const apiParams: ModelApiParams = {};
|
|
89
|
+
|
|
90
|
+
// Handle search query
|
|
91
|
+
if (params.query) {
|
|
92
|
+
apiParams.search = params.query;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle author filter
|
|
96
|
+
if (params.author) {
|
|
97
|
+
apiParams.author = params.author;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle task and library filters
|
|
101
|
+
const filters = [];
|
|
102
|
+
if (params.task) filters.push(params.task);
|
|
103
|
+
if (params.library) filters.push(params.library);
|
|
104
|
+
if (filters.length > 0) {
|
|
105
|
+
apiParams.filter = filters.join(',');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle sorting (always descending)
|
|
109
|
+
if (params.sort) {
|
|
110
|
+
apiParams.sort = params.sort;
|
|
111
|
+
apiParams.direction = '-1';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle limit
|
|
115
|
+
if (params.limit) {
|
|
116
|
+
apiParams.limit = params.limit.toString();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Call the API
|
|
120
|
+
const models = await this.callApi<ModelApiResult[]>(apiParams);
|
|
121
|
+
|
|
122
|
+
if (models.length === 0) {
|
|
123
|
+
return `No models found for the given criteria.`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return formatSearchResults(models, params);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof Error) {
|
|
129
|
+
throw new Error(`Failed to search for models: ${error.message}`);
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Search for models with a specific filter (e.g., arxiv:XXXX.XXXXX)
|
|
137
|
+
*/
|
|
138
|
+
async searchWithFilter(filter: string, limit: number = 10): Promise<string> {
|
|
139
|
+
try {
|
|
140
|
+
const apiParams: ModelApiParams = {
|
|
141
|
+
filter: filter,
|
|
142
|
+
limit: limit.toString(),
|
|
143
|
+
sort: 'downloads',
|
|
144
|
+
direction: '-1',
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Call the API
|
|
148
|
+
const models = await this.callApi<ModelApiResult[]>(apiParams);
|
|
149
|
+
|
|
150
|
+
if (models.length === 0) {
|
|
151
|
+
return `No models found referencing ${filter}.`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return formatSearchResults(models, { limit });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof Error) {
|
|
157
|
+
throw new Error(`Failed to search for models: ${error.message}`);
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Formatting Function
|
|
165
|
+
function formatSearchResults(models: ModelApiResult[], params: Partial<ModelSearchParams>): string {
|
|
166
|
+
const r: string[] = [];
|
|
167
|
+
|
|
168
|
+
// Build search description
|
|
169
|
+
const searchTerms = [];
|
|
170
|
+
if (params.query) searchTerms.push(`query "${params.query}"`);
|
|
171
|
+
if (params.author) searchTerms.push(`author "${params.author}"`);
|
|
172
|
+
if (params.task) searchTerms.push(`task "${params.task}"`);
|
|
173
|
+
if (params.library) searchTerms.push(`library "${params.library}"`);
|
|
174
|
+
if (params.sort) searchTerms.push(`sorted by ${params.sort} (descending)`);
|
|
175
|
+
|
|
176
|
+
const searchDesc = searchTerms.length > 0 ? ` matching ${searchTerms.join(', ')}` : '';
|
|
177
|
+
|
|
178
|
+
const resultText =
|
|
179
|
+
models.length === params.limit
|
|
180
|
+
? `Showing first ${models.length.toString()} models${searchDesc}:`
|
|
181
|
+
: `Found ${models.length.toString()} models${searchDesc}:`;
|
|
182
|
+
r.push(resultText);
|
|
183
|
+
r.push('');
|
|
184
|
+
|
|
185
|
+
for (const model of models) {
|
|
186
|
+
r.push(`## ${model.id}`);
|
|
187
|
+
r.push('');
|
|
188
|
+
|
|
189
|
+
// Basic info line
|
|
190
|
+
const info = [];
|
|
191
|
+
if (model.pipeline_tag) info.push(`**Task:** ${model.pipeline_tag}`);
|
|
192
|
+
if (model.library_name) info.push(`**Library:** ${model.library_name}`);
|
|
193
|
+
if (model.downloads) info.push(`**Downloads:** ${formatNumber(model.downloads)}`);
|
|
194
|
+
if (model.likes) info.push(`**Likes:** ${model.likes.toString()}`);
|
|
195
|
+
if (model.trendingScore) info.push(`**Trending Score:** ${model.trendingScore.toString()}`);
|
|
196
|
+
|
|
197
|
+
if (info.length > 0) {
|
|
198
|
+
r.push(info.join(' | '));
|
|
199
|
+
r.push('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Tags
|
|
203
|
+
if (model.tags && model.tags.length > 0) {
|
|
204
|
+
r.push(`**Tags:** ${model.tags.slice(0, TAGS_TO_RETURN).join(', ')}`);
|
|
205
|
+
if (model.tags.length > TAGS_TO_RETURN) {
|
|
206
|
+
r.push(`*and ${(model.tags.length - TAGS_TO_RETURN).toString()} more...*`);
|
|
207
|
+
}
|
|
208
|
+
r.push('');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Status indicators
|
|
212
|
+
const status = [];
|
|
213
|
+
if (model.private) status.push('🔐 Private');
|
|
214
|
+
if (status.length > 0) {
|
|
215
|
+
r.push(status.join(' | '));
|
|
216
|
+
r.push('');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Dates
|
|
220
|
+
if (model.createdAt) {
|
|
221
|
+
r.push(`**Created:** ${formatDate(model.createdAt)}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
r.push(`**Link:** [https://hf.co/${model.id}](https://hf.co/${model.id})`);
|
|
225
|
+
r.push('');
|
|
226
|
+
r.push('---');
|
|
227
|
+
r.push('');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return r.join('\n');
|
|
231
|
+
}
|