scientify 1.6.0 → 1.6.2
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/src/tools/openalex-search.d.ts +26 -0
- package/dist/src/tools/openalex-search.d.ts.map +1 -0
- package/dist/src/tools/openalex-search.js +123 -0
- package/dist/src/tools/openalex-search.js.map +1 -0
- package/dist/src/tools/paper-browser.d.ts +24 -0
- package/dist/src/tools/paper-browser.d.ts.map +1 -0
- package/dist/src/tools/paper-browser.js +121 -0
- package/dist/src/tools/paper-browser.js.map +1 -0
- package/dist/src/tools/unpaywall-download.d.ts +22 -0
- package/dist/src/tools/unpaywall-download.d.ts.map +1 -0
- package/dist/src/tools/unpaywall-download.js +169 -0
- package/dist/src/tools/unpaywall-download.js.map +1 -0
- package/package.json +11 -12
- package/skills/literature-survey/SKILL.md +17 -4
- package/skills/research-review/SKILL.md +181 -7
- package/skills/research-survey/SKILL.md +9 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAqBlD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,iBAAiB,QAoFtD"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import { handleResearchStatus, handlePapers, handleIdeas, handleProjects, handle
|
|
|
3
3
|
import { createArxivSearchTool } from "./src/tools/arxiv-search.js";
|
|
4
4
|
import { createArxivDownloadTool } from "./src/tools/arxiv-download.js";
|
|
5
5
|
import { createGithubSearchTool } from "./src/tools/github-search-tool.js";
|
|
6
|
+
import { createPaperBrowserTool } from "./src/tools/paper-browser.js";
|
|
7
|
+
import { createOpenAlexSearchTool } from "./src/tools/openalex-search.js";
|
|
8
|
+
import { createUnpaywallDownloadTool } from "./src/tools/unpaywall-download.js";
|
|
6
9
|
import { createAutoUpdaterService } from "./src/services/auto-updater.js";
|
|
7
10
|
import { createSkillInjectionHook } from "./src/hooks/inject-skill.js";
|
|
8
11
|
// Default: check every hour
|
|
@@ -12,6 +15,9 @@ export default function register(api) {
|
|
|
12
15
|
api.registerTool(createArxivSearchTool());
|
|
13
16
|
api.registerTool(createArxivDownloadTool());
|
|
14
17
|
api.registerTool(createGithubSearchTool());
|
|
18
|
+
api.registerTool(createPaperBrowserTool());
|
|
19
|
+
api.registerTool(createOpenAlexSearchTool());
|
|
20
|
+
api.registerTool(createUnpaywallDownloadTool());
|
|
15
21
|
// Register auto-updater service (silent updates)
|
|
16
22
|
const pluginConfig = api.pluginConfig;
|
|
17
23
|
const autoUpdateEnabled = pluginConfig?.autoUpdate !== false; // enabled by default
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,4BAA4B;AAC5B,MAAM,wBAAwB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAsB;IACrD,iBAAiB;IACjB,GAAG,CAAC,YAAY,CAAC,qBAAqB,EAAE,CAAC,CAAC;IAC1C,GAAG,CAAC,YAAY,CAAC,uBAAuB,EAAE,CAAC,CAAC;IAC5C,GAAG,CAAC,YAAY,CAAC,sBAAsB,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const OpenAlexSearchToolSchema: import("@sinclair/typebox").TObject<{
|
|
2
|
+
query: import("@sinclair/typebox").TString;
|
|
3
|
+
max_results: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
4
|
+
filter: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
5
|
+
sort: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function createOpenAlexSearchTool(): {
|
|
8
|
+
label: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
12
|
+
query: import("@sinclair/typebox").TString;
|
|
13
|
+
max_results: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
14
|
+
filter: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
15
|
+
sort: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
16
|
+
}>;
|
|
17
|
+
execute: (_toolCallId: string, rawArgs: unknown) => Promise<{
|
|
18
|
+
type: "tool_result";
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
isError?: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=openalex-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openalex-search.d.ts","sourceRoot":"","sources":["../../../src/tools/openalex-search.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,wBAAwB;;;;;EAqBnC,CAAC;AA4DH,wBAAgB,wBAAwB;;;;;;;;;;2BAOP,MAAM,WAAW,OAAO;;;;;;;;EA0ExD"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { Result } from "./result.js";
|
|
3
|
+
const OPENALEX_API = "https://api.openalex.org/works";
|
|
4
|
+
const DEFAULT_MAX_RESULTS = 20;
|
|
5
|
+
const MAX_RESULTS_LIMIT = 100;
|
|
6
|
+
export const OpenAlexSearchToolSchema = Type.Object({
|
|
7
|
+
query: Type.String({
|
|
8
|
+
description: "Search query for academic works (papers, articles). Can search by title, abstract, author, or keywords.",
|
|
9
|
+
}),
|
|
10
|
+
max_results: Type.Optional(Type.Number({
|
|
11
|
+
description: "Maximum number of results (1-100). Default: 20.",
|
|
12
|
+
minimum: 1,
|
|
13
|
+
maximum: MAX_RESULTS_LIMIT,
|
|
14
|
+
})),
|
|
15
|
+
filter: Type.Optional(Type.String({
|
|
16
|
+
description: 'Optional filter string (e.g., "publication_year:2020-2024", "type:journal-article", "is_oa:true"). See OpenAlex docs for filter syntax.',
|
|
17
|
+
})),
|
|
18
|
+
sort: Type.Optional(Type.String({
|
|
19
|
+
description: 'Sort by: "cited_by_count" (most cited), "publication_date" (newest first), or "relevance_score" (default).',
|
|
20
|
+
})),
|
|
21
|
+
});
|
|
22
|
+
function readStringParam(params, key, opts) {
|
|
23
|
+
const value = params[key];
|
|
24
|
+
if (value === undefined || value === null) {
|
|
25
|
+
if (opts?.required) {
|
|
26
|
+
throw new Error(`Missing required parameter: ${key}`);
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return String(value);
|
|
31
|
+
}
|
|
32
|
+
function readNumberParam(params, key, opts) {
|
|
33
|
+
const value = params[key];
|
|
34
|
+
if (value === undefined || value === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
const num = Number(value);
|
|
37
|
+
if (isNaN(num))
|
|
38
|
+
return undefined;
|
|
39
|
+
return opts?.integer ? Math.floor(num) : num;
|
|
40
|
+
}
|
|
41
|
+
function reconstructAbstract(invertedIndex) {
|
|
42
|
+
if (!invertedIndex)
|
|
43
|
+
return "";
|
|
44
|
+
const words = [];
|
|
45
|
+
for (const [word, positions] of Object.entries(invertedIndex)) {
|
|
46
|
+
for (const pos of positions) {
|
|
47
|
+
words.push([word, pos]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
words.sort((a, b) => a[1] - b[1]);
|
|
51
|
+
return words.map(([word]) => word).join(" ").substring(0, 500); // Limit to 500 chars
|
|
52
|
+
}
|
|
53
|
+
export function createOpenAlexSearchTool() {
|
|
54
|
+
return {
|
|
55
|
+
label: "OpenAlex Search",
|
|
56
|
+
name: "openalex_search",
|
|
57
|
+
description: "Search for academic papers across all disciplines using OpenAlex API. Returns paper metadata including DOI, authors, citations, and open access status. More comprehensive than arXiv (covers all fields, not just STEM).",
|
|
58
|
+
parameters: OpenAlexSearchToolSchema,
|
|
59
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
60
|
+
const params = rawArgs;
|
|
61
|
+
const query = readStringParam(params, "query", { required: true });
|
|
62
|
+
const maxResults = Math.min(readNumberParam(params, "max_results", { integer: true }) ?? DEFAULT_MAX_RESULTS, MAX_RESULTS_LIMIT);
|
|
63
|
+
const filterStr = readStringParam(params, "filter");
|
|
64
|
+
const sortStr = readStringParam(params, "sort") ?? "relevance_score";
|
|
65
|
+
// Build URL parameters
|
|
66
|
+
const urlParams = new URLSearchParams({
|
|
67
|
+
search: query,
|
|
68
|
+
per_page: String(maxResults),
|
|
69
|
+
mailto: "research@openclaw.ai", // Polite pool for higher rate limits
|
|
70
|
+
});
|
|
71
|
+
if (filterStr) {
|
|
72
|
+
urlParams.set("filter", filterStr);
|
|
73
|
+
}
|
|
74
|
+
// Map sort parameter
|
|
75
|
+
const sortMapping = {
|
|
76
|
+
cited_by_count: "cited_by_count:desc",
|
|
77
|
+
publication_date: "publication_date:desc",
|
|
78
|
+
relevance_score: "relevance_score:desc",
|
|
79
|
+
};
|
|
80
|
+
urlParams.set("sort", sortMapping[sortStr] || sortMapping.relevance_score);
|
|
81
|
+
const url = `${OPENALEX_API}?${urlParams.toString()}`;
|
|
82
|
+
let response;
|
|
83
|
+
try {
|
|
84
|
+
response = await fetch(url, {
|
|
85
|
+
headers: {
|
|
86
|
+
"User-Agent": "scientify-research-agent/1.0 (mailto:research@openclaw.ai)",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return Result.err("network_error", `Failed to reach OpenAlex API: ${error instanceof Error ? error.message : String(error)}`);
|
|
92
|
+
}
|
|
93
|
+
if (response.status === 429) {
|
|
94
|
+
return Result.err("rate_limited", "OpenAlex API rate limit exceeded. Please wait a moment and retry.");
|
|
95
|
+
}
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
return Result.err("api_error", `OpenAlex API returned ${response.status}: ${response.statusText}`);
|
|
98
|
+
}
|
|
99
|
+
const data = (await response.json());
|
|
100
|
+
const works = (data.results ?? []).map((work) => ({
|
|
101
|
+
id: work.id.replace("https://openalex.org/", ""), // Clean ID
|
|
102
|
+
title: work.title || "Untitled",
|
|
103
|
+
doi: work.doi?.replace("https://doi.org/", "") || null,
|
|
104
|
+
year: work.publication_year,
|
|
105
|
+
date: work.publication_date,
|
|
106
|
+
type: work.type,
|
|
107
|
+
authors: work.authorships.slice(0, 5).map((a) => a.author.display_name), // First 5 authors
|
|
108
|
+
venue: work.primary_location?.source?.display_name || "Unknown",
|
|
109
|
+
cited_by: work.cited_by_count,
|
|
110
|
+
is_open_access: work.open_access.is_oa,
|
|
111
|
+
oa_url: work.open_access.oa_url,
|
|
112
|
+
abstract_preview: reconstructAbstract(work.abstract_inverted_index),
|
|
113
|
+
}));
|
|
114
|
+
return Result.ok({
|
|
115
|
+
query,
|
|
116
|
+
total_count: data.meta?.count ?? 0,
|
|
117
|
+
returned: works.length,
|
|
118
|
+
works,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=openalex-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openalex-search.js","sourceRoot":"","sources":["../../../src/tools/openalex-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const PaperBrowserToolSchema: import("@sinclair/typebox").TObject<{
|
|
2
|
+
file_path: import("@sinclair/typebox").TString;
|
|
3
|
+
start_line: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
4
|
+
num_lines: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function createPaperBrowserTool(): {
|
|
7
|
+
label: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
11
|
+
file_path: import("@sinclair/typebox").TString;
|
|
12
|
+
start_line: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
13
|
+
num_lines: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
14
|
+
}>;
|
|
15
|
+
execute: (_toolCallId: string, rawArgs: unknown) => Promise<{
|
|
16
|
+
type: "tool_result";
|
|
17
|
+
content: {
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}[];
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=paper-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paper-browser.d.ts","sourceRoot":"","sources":["../../../src/tools/paper-browser.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,sBAAsB;;;;EAiBjC,CAAC;AAqBH,wBAAgB,sBAAsB;;;;;;;;;2BAOL,MAAM,WAAW,OAAO;;;;;;;;EAuFxD"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
3
|
+
import { Result } from "./result.js";
|
|
4
|
+
const DEFAULT_VIEWPORT_SIZE = 100;
|
|
5
|
+
const MAX_VIEWPORT_SIZE = 500;
|
|
6
|
+
export const PaperBrowserToolSchema = Type.Object({
|
|
7
|
+
file_path: Type.String({
|
|
8
|
+
description: "Path to the paper file (.tex, .md, or any text file).",
|
|
9
|
+
}),
|
|
10
|
+
start_line: Type.Optional(Type.Number({
|
|
11
|
+
description: "Starting line number (1-indexed). Default: 1.",
|
|
12
|
+
minimum: 1,
|
|
13
|
+
})),
|
|
14
|
+
num_lines: Type.Optional(Type.Number({
|
|
15
|
+
description: `Number of lines to display (default: ${DEFAULT_VIEWPORT_SIZE}, max: ${MAX_VIEWPORT_SIZE}).`,
|
|
16
|
+
minimum: 1,
|
|
17
|
+
maximum: MAX_VIEWPORT_SIZE,
|
|
18
|
+
})),
|
|
19
|
+
});
|
|
20
|
+
function readStringParam(params, key, opts) {
|
|
21
|
+
const value = params[key];
|
|
22
|
+
if (value === undefined || value === null) {
|
|
23
|
+
if (opts?.required) {
|
|
24
|
+
throw new Error(`Missing required parameter: ${key}`);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return String(value);
|
|
29
|
+
}
|
|
30
|
+
function readNumberParam(params, key, opts) {
|
|
31
|
+
const value = params[key];
|
|
32
|
+
if (value === undefined || value === null)
|
|
33
|
+
return undefined;
|
|
34
|
+
const num = Number(value);
|
|
35
|
+
if (isNaN(num))
|
|
36
|
+
return undefined;
|
|
37
|
+
return opts?.integer ? Math.floor(num) : num;
|
|
38
|
+
}
|
|
39
|
+
export function createPaperBrowserTool() {
|
|
40
|
+
return {
|
|
41
|
+
label: "Paper Browser",
|
|
42
|
+
name: "paper_browser",
|
|
43
|
+
description: "Read large paper files (.tex, .md) in paginated chunks. Use this to avoid loading entire multi-thousand-line files into context at once. Returns a viewport of lines with navigation information.",
|
|
44
|
+
parameters: PaperBrowserToolSchema,
|
|
45
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
46
|
+
const params = rawArgs;
|
|
47
|
+
const filePath = readStringParam(params, "file_path", { required: true });
|
|
48
|
+
const startLine = Math.max(1, readNumberParam(params, "start_line", { integer: true }) ?? 1);
|
|
49
|
+
const numLines = Math.min(readNumberParam(params, "num_lines", { integer: true }) ?? DEFAULT_VIEWPORT_SIZE, MAX_VIEWPORT_SIZE);
|
|
50
|
+
// Validate file exists
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
return Result.err("file_not_found", `File does not exist: ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
// Check if it's a file (not directory)
|
|
55
|
+
let stats;
|
|
56
|
+
try {
|
|
57
|
+
stats = statSync(filePath);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return Result.err("file_error", `Cannot access file: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
}
|
|
62
|
+
if (!stats.isFile()) {
|
|
63
|
+
return Result.err("not_a_file", `Path is not a file: ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
// Read file content
|
|
66
|
+
let content;
|
|
67
|
+
try {
|
|
68
|
+
content = readFileSync(filePath, "utf-8");
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return Result.err("read_error", `Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
|
|
72
|
+
}
|
|
73
|
+
// Split into lines
|
|
74
|
+
const lines = content.split("\n");
|
|
75
|
+
const totalLines = lines.length;
|
|
76
|
+
// Validate start line
|
|
77
|
+
if (startLine > totalLines) {
|
|
78
|
+
return Result.err("invalid_range", `Start line ${startLine} exceeds total lines ${totalLines}`);
|
|
79
|
+
}
|
|
80
|
+
// Extract viewport
|
|
81
|
+
const endLine = Math.min(startLine + numLines - 1, totalLines);
|
|
82
|
+
const viewportLines = lines.slice(startLine - 1, endLine);
|
|
83
|
+
// Add line numbers (matching cat -n format for consistency with Read tool)
|
|
84
|
+
const numberedLines = viewportLines
|
|
85
|
+
.map((line, idx) => {
|
|
86
|
+
const lineNum = startLine + idx;
|
|
87
|
+
return `${lineNum.toString().padStart(6, " ")}\t${line}`;
|
|
88
|
+
})
|
|
89
|
+
.join("\n");
|
|
90
|
+
// Navigation hints
|
|
91
|
+
const hasMore = endLine < totalLines;
|
|
92
|
+
const hasPrev = startLine > 1;
|
|
93
|
+
let navigationHint = "";
|
|
94
|
+
if (hasMore && hasPrev) {
|
|
95
|
+
navigationHint = `\n\nNavigate: paper_browser({ file_path: "${filePath}", start_line: ${endLine + 1} }) for next page, or start_line: ${Math.max(1, startLine - numLines)} for previous page.`;
|
|
96
|
+
}
|
|
97
|
+
else if (hasMore) {
|
|
98
|
+
navigationHint = `\n\nMore content below. Use: paper_browser({ file_path: "${filePath}", start_line: ${endLine + 1} })`;
|
|
99
|
+
}
|
|
100
|
+
else if (hasPrev) {
|
|
101
|
+
navigationHint = `\n\nEnd of file. Use: paper_browser({ file_path: "${filePath}", start_line: ${Math.max(1, startLine - numLines)} }) for previous page.`;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
navigationHint = "\n\n[End of file]";
|
|
105
|
+
}
|
|
106
|
+
return Result.ok({
|
|
107
|
+
file_path: filePath,
|
|
108
|
+
total_lines: totalLines,
|
|
109
|
+
viewport: {
|
|
110
|
+
start_line: startLine,
|
|
111
|
+
end_line: endLine,
|
|
112
|
+
num_lines: viewportLines.length,
|
|
113
|
+
},
|
|
114
|
+
content: numberedLines + navigationHint,
|
|
115
|
+
has_more: hasMore,
|
|
116
|
+
has_prev: hasPrev,
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=paper-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paper-browser.js","sourceRoot":"","sources":["../../../src/tools/paper-browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const UnpaywallDownloadToolSchema: import("@sinclair/typebox").TObject<{
|
|
2
|
+
dois: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
|
|
3
|
+
output_dir: import("@sinclair/typebox").TString;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function createUnpaywallDownloadTool(): {
|
|
6
|
+
label: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: import("@sinclair/typebox").TObject<{
|
|
10
|
+
dois: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
|
|
11
|
+
output_dir: import("@sinclair/typebox").TString;
|
|
12
|
+
}>;
|
|
13
|
+
execute: (_toolCallId: string, rawArgs: unknown) => Promise<{
|
|
14
|
+
type: "tool_result";
|
|
15
|
+
content: {
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
isError?: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=unpaywall-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unpaywall-download.d.ts","sourceRoot":"","sources":["../../../src/tools/unpaywall-download.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,2BAA2B;;;EAStC,CAAC;AAqDH,wBAAgB,2BAA2B;;;;;;;;2BAOV,MAAM,WAAW,OAAO;;;;;;;;EAgIxD"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { Result } from "./result.js";
|
|
5
|
+
const UNPAYWALL_API = "https://api.unpaywall.org/v2";
|
|
6
|
+
const USER_EMAIL = "research@openclaw.ai"; // Required by Unpaywall
|
|
7
|
+
export const UnpaywallDownloadToolSchema = Type.Object({
|
|
8
|
+
dois: Type.Array(Type.String(), {
|
|
9
|
+
description: "List of DOIs to download (e.g., ['10.1038/s41586-021-03819-2']). Maximum 20 DOIs per request.",
|
|
10
|
+
minItems: 1,
|
|
11
|
+
maxItems: 20,
|
|
12
|
+
}),
|
|
13
|
+
output_dir: Type.String({
|
|
14
|
+
description: "Absolute path to output directory where PDFs will be saved.",
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
function readStringParam(params, key, opts) {
|
|
18
|
+
const value = params[key];
|
|
19
|
+
if (value === undefined || value === null) {
|
|
20
|
+
if (opts?.required) {
|
|
21
|
+
throw new Error(`Missing required parameter: ${key}`);
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
async function downloadPDF(url, outputPath) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(url, {
|
|
30
|
+
headers: {
|
|
31
|
+
"User-Agent": "scientify-research-agent/1.0 (mailto:research@openclaw.ai)",
|
|
32
|
+
},
|
|
33
|
+
redirect: "follow",
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const contentType = response.headers.get("content-type") || "";
|
|
39
|
+
if (!contentType.includes("pdf") && !contentType.includes("octet-stream")) {
|
|
40
|
+
// Not a PDF, might be HTML landing page
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const buffer = await response.arrayBuffer();
|
|
44
|
+
writeFileSync(outputPath, Buffer.from(buffer));
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function createUnpaywallDownloadTool() {
|
|
52
|
+
return {
|
|
53
|
+
label: "Unpaywall Download",
|
|
54
|
+
name: "unpaywall_download",
|
|
55
|
+
description: "Download open access PDFs using Unpaywall API. Provide DOIs and output directory. Non-OA papers will be skipped with error captured (no failure). Returns list of successfully downloaded papers.",
|
|
56
|
+
parameters: UnpaywallDownloadToolSchema,
|
|
57
|
+
execute: async (_toolCallId, rawArgs) => {
|
|
58
|
+
const params = rawArgs;
|
|
59
|
+
const doisRaw = params.dois;
|
|
60
|
+
if (!Array.isArray(doisRaw) || doisRaw.length === 0) {
|
|
61
|
+
return Result.err("invalid_input", "dois must be a non-empty array");
|
|
62
|
+
}
|
|
63
|
+
const dois = doisRaw.map((d) => String(d).trim()).filter((d) => d.length > 0);
|
|
64
|
+
const outputDir = readStringParam(params, "output_dir", { required: true });
|
|
65
|
+
const resolvedOutputDir = resolve(outputDir);
|
|
66
|
+
// Create output directory if it doesn't exist
|
|
67
|
+
try {
|
|
68
|
+
if (!existsSync(resolvedOutputDir)) {
|
|
69
|
+
mkdirSync(resolvedOutputDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return Result.err("filesystem_error", `Failed to create output directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
|
+
}
|
|
75
|
+
const results = [];
|
|
76
|
+
let successCount = 0;
|
|
77
|
+
let notOACount = 0;
|
|
78
|
+
let failedCount = 0;
|
|
79
|
+
// Process each DOI
|
|
80
|
+
for (const doi of dois) {
|
|
81
|
+
try {
|
|
82
|
+
// Query Unpaywall API
|
|
83
|
+
const apiUrl = `${UNPAYWALL_API}/${encodeURIComponent(doi)}?email=${USER_EMAIL}`;
|
|
84
|
+
const response = await fetch(apiUrl, {
|
|
85
|
+
headers: {
|
|
86
|
+
"User-Agent": "scientify-research-agent/1.0 (mailto:research@openclaw.ai)",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
results.push({
|
|
91
|
+
doi,
|
|
92
|
+
status: "api_error",
|
|
93
|
+
message: `API error: ${response.status} ${response.statusText}`,
|
|
94
|
+
});
|
|
95
|
+
failedCount++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const data = (await response.json());
|
|
99
|
+
// Check if OA available
|
|
100
|
+
if (!data.is_oa || !data.best_oa_location) {
|
|
101
|
+
results.push({
|
|
102
|
+
doi,
|
|
103
|
+
status: "not_oa",
|
|
104
|
+
message: "Paper is not open access",
|
|
105
|
+
title: data.title,
|
|
106
|
+
});
|
|
107
|
+
notOACount++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Get PDF URL
|
|
111
|
+
const pdfUrl = data.best_oa_location.url_for_pdf || data.best_oa_location.url;
|
|
112
|
+
if (!pdfUrl) {
|
|
113
|
+
results.push({
|
|
114
|
+
doi,
|
|
115
|
+
status: "no_pdf_url",
|
|
116
|
+
message: "No PDF URL available",
|
|
117
|
+
title: data.title,
|
|
118
|
+
});
|
|
119
|
+
failedCount++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Download PDF
|
|
123
|
+
const sanitizedDoi = doi.replace(/[\/\\:]/g, "_");
|
|
124
|
+
const filename = `${sanitizedDoi}.pdf`;
|
|
125
|
+
const outputPath = join(resolvedOutputDir, filename);
|
|
126
|
+
const downloaded = await downloadPDF(pdfUrl, outputPath);
|
|
127
|
+
if (downloaded) {
|
|
128
|
+
results.push({
|
|
129
|
+
doi,
|
|
130
|
+
status: "success",
|
|
131
|
+
message: "Downloaded successfully",
|
|
132
|
+
file_path: outputPath,
|
|
133
|
+
title: data.title,
|
|
134
|
+
});
|
|
135
|
+
successCount++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
results.push({
|
|
139
|
+
doi,
|
|
140
|
+
status: "download_failed",
|
|
141
|
+
message: "Failed to download PDF (might be HTML landing page or access denied)",
|
|
142
|
+
title: data.title,
|
|
143
|
+
});
|
|
144
|
+
failedCount++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
results.push({
|
|
149
|
+
doi,
|
|
150
|
+
status: "api_error",
|
|
151
|
+
message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
152
|
+
});
|
|
153
|
+
failedCount++;
|
|
154
|
+
}
|
|
155
|
+
// Rate limiting: sleep 100ms between requests
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
157
|
+
}
|
|
158
|
+
return Result.ok({
|
|
159
|
+
total: dois.length,
|
|
160
|
+
success: successCount,
|
|
161
|
+
not_oa: notOACount,
|
|
162
|
+
failed: failedCount,
|
|
163
|
+
output_dir: resolvedOutputDir,
|
|
164
|
+
results,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=unpaywall-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unpaywall-download.js","sourceRoot":"","sources":["../../../src/tools/unpaywall-download.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scientify",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Scientify - AI-powered research workflow automation for OpenClaw. Includes idea generation, literature review, research pipeline skills, and arxiv tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,16 +29,6 @@
|
|
|
29
29
|
"bugs": {
|
|
30
30
|
"url": "https://github.com/tsingyuai/scientify/issues"
|
|
31
31
|
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "tsc",
|
|
34
|
-
"clean": "rm -rf dist",
|
|
35
|
-
"dev": "tsc --watch",
|
|
36
|
-
"dev:link": "npm run build && rm -rf ~/.openclaw/extensions/scientify && ln -sfn \"$(pwd)\" ~/.openclaw/extensions/scientify",
|
|
37
|
-
"dev:unlink": "rm -f ~/.openclaw/extensions/scientify",
|
|
38
|
-
"dev:reload": "nodemon --watch src --watch skills --ext ts,md,json --exec \"npm run build && pkill -USR1 -f 'openclaw.mjs gateway' || true\" --delay 500ms",
|
|
39
|
-
"prepublishOnly": "npm run clean && npm run build",
|
|
40
|
-
"release": "npm version patch && git push && git push --tags"
|
|
41
|
-
},
|
|
42
32
|
"devDependencies": {
|
|
43
33
|
"@semantic-release/changelog": "^6.0.3",
|
|
44
34
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
@@ -61,5 +51,14 @@
|
|
|
61
51
|
"extensions": [
|
|
62
52
|
"./dist/index.js"
|
|
63
53
|
]
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc",
|
|
57
|
+
"clean": "rm -rf dist",
|
|
58
|
+
"dev": "tsc --watch",
|
|
59
|
+
"dev:link": "npm run build && rm -rf ~/.openclaw/extensions/scientify && ln -sfn \"$(pwd)\" ~/.openclaw/extensions/scientify",
|
|
60
|
+
"dev:unlink": "rm -f ~/.openclaw/extensions/scientify",
|
|
61
|
+
"dev:reload": "nodemon --watch src --watch skills --ext ts,md,json --exec \"npm run build && pkill -USR1 -f 'openclaw.mjs gateway' || true\" --delay 500ms",
|
|
62
|
+
"release": "npm version patch && git push && git push --tags"
|
|
64
63
|
}
|
|
65
|
-
}
|
|
64
|
+
}
|
|
@@ -114,12 +114,25 @@ arxiv_download({
|
|
|
114
114
|
- 核心方法名 + 作者名
|
|
115
115
|
- 论文中提到的数据集名 + 任务名
|
|
116
116
|
|
|
117
|
-
使用 `
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
使用 `github_search` 工具:
|
|
118
|
+
```javascript
|
|
119
|
+
// 示例:
|
|
120
|
+
github_search({
|
|
121
|
+
query: "{paper_title} implementation",
|
|
122
|
+
max_results: 10,
|
|
123
|
+
sort: "stars",
|
|
124
|
+
language: "python" // 可选:根据论文领域选择语言
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// 如果有具体方法名:
|
|
128
|
+
github_search({
|
|
129
|
+
query: "{method_name} {author_last_name}",
|
|
130
|
+
max_results: 5
|
|
131
|
+
})
|
|
121
132
|
```
|
|
122
133
|
|
|
134
|
+
**提示**:如果需要 GitHub API 高频率限制,设置环境变量 `GITHUB_TOKEN`。
|
|
135
|
+
|
|
123
136
|
#### 3.3 筛选与 clone
|
|
124
137
|
|
|
125
138
|
对搜索到的仓库,评估:
|
|
@@ -96,6 +96,31 @@ metadata:
|
|
|
96
96
|
| Loss 合理 | 非 NaN/Inf,有下降趋势(epoch 1 loss > epoch 2 loss) |
|
|
97
97
|
| 数据管道匹配 plan | 对比 plan Dataset Plan vs `data/` 实现,batch size、预处理步骤一致 |
|
|
98
98
|
|
|
99
|
+
#### D. 性能初步评估
|
|
100
|
+
|
|
101
|
+
**⚠️ 关键新增 — 防止"代码正确但效果很差"的算法被放行。**
|
|
102
|
+
|
|
103
|
+
从 `ml_res.md` 提取 2 epoch 验证结果,评估算法有效性:
|
|
104
|
+
|
|
105
|
+
| 检查项 | 判定标准 | 诊断 |
|
|
106
|
+
|--------|----------|------|
|
|
107
|
+
| **Loss 下降幅度** | 计算 `reduction = (epoch1_loss - epoch2_loss) / epoch1_loss * 100%` | <5% → 可能学习率过小、架构有问题或数据未正确处理 |
|
|
108
|
+
| **Loss 稳定性** | 检查 epoch 1 和 epoch 2 的 loss 波动 | 震荡 >20% → 可能学习率过大或 batch size 不当 |
|
|
109
|
+
| **Metrics 合理性** | 对比任务的随机 baseline(分类: 1/num_classes,回归: 数据方差) | 接近随机(±10%)→ 模型未真正学习,可能特征无效或架构过简单 |
|
|
110
|
+
| **与 plan 预期对比** | 如果 plan_res.md 中有性能预期,对比实际结果 | 低于预期 30% → 需要反思算法设计或超参数设置 |
|
|
111
|
+
|
|
112
|
+
**性能异常的常见原因**:
|
|
113
|
+
|
|
114
|
+
| 症状 | 可能原因 | 验证方法 |
|
|
115
|
+
|------|----------|----------|
|
|
116
|
+
| Loss 几乎不变(<2%) | 学习率过小 | 检查 plan_res.md 中 lr 值,对比 survey_res.md 中 baseline lr |
|
|
117
|
+
| Loss 震荡剧烈(>30%) | 学习率过大 | 同上 |
|
|
118
|
+
| Loss 下降但 metric 不变 | 模型过简单或特征无效 | 检查模型参数量;检查数据预处理是否正确(归一化、标准化) |
|
|
119
|
+
| Accuracy 接近随机 | 数据标签错误或未正确加载 | 重新验证数据加载代码,打印样本检查 |
|
|
120
|
+
| Loss=NaN/Inf | 梯度爆炸、数值不稳定 | 检查是否有 Batch/Layer Normalization;检查 lr 是否过大 |
|
|
121
|
+
|
|
122
|
+
**如果发现性能异常,标记 `verdict: NEEDS_ALGORITHM_REVIEW`(不同于 NEEDS_REVISION)。**
|
|
123
|
+
|
|
99
124
|
### Step 4: 写入审查报告
|
|
100
125
|
|
|
101
126
|
写入 `$W/iterations/judge_v1.md`:
|
|
@@ -103,7 +128,7 @@ metadata:
|
|
|
103
128
|
```markdown
|
|
104
129
|
# Review v1
|
|
105
130
|
|
|
106
|
-
## Verdict: PASS / NEEDS_REVISION
|
|
131
|
+
## Verdict: PASS / NEEDS_REVISION / NEEDS_ALGORITHM_REVIEW
|
|
107
132
|
|
|
108
133
|
## Checklist
|
|
109
134
|
|
|
@@ -131,12 +156,45 @@ metadata:
|
|
|
131
156
|
- [x/✗] Training loop proper (loss decreasing)
|
|
132
157
|
- [x/✗] Results are from real execution (not fabricated)
|
|
133
158
|
|
|
159
|
+
### 性能初步评估(新增)
|
|
160
|
+
|
|
161
|
+
**2-Epoch Validation Results** (from `ml_res.md`):
|
|
162
|
+
- Epoch 1 loss: {value}
|
|
163
|
+
- Epoch 2 loss: {value}
|
|
164
|
+
- Loss reduction: {percent}% (expected: >10% for initial epochs)
|
|
165
|
+
- Metric (e.g., accuracy): {value} (random baseline: {baseline_value})
|
|
166
|
+
|
|
167
|
+
**Performance Assessment**:
|
|
168
|
+
- [x/✗] Loss decreasing adequately (reduction >5%)
|
|
169
|
+
- [x/✗] Metrics above random baseline (+10% or more)
|
|
170
|
+
- [x/✗] No severe oscillation (<20% variance)
|
|
171
|
+
- [x/✗] Meets plan expectations (if performance target specified in plan_res.md)
|
|
172
|
+
|
|
173
|
+
**Diagnosis** (if performance issues):
|
|
174
|
+
- **Symptom**: {what's wrong - e.g., "Loss reduction only 0.9%, far below 10% expected"}
|
|
175
|
+
- **Likely cause**: {diagnosis - e.g., "Learning rate too small (lr=1e-5, survey baseline=1e-3)"}
|
|
176
|
+
- **Evidence**: {supporting evidence - e.g., "survey_res.md Table 2 shows all baselines use lr=1e-3"}
|
|
177
|
+
|
|
134
178
|
## Issues (if NEEDS_REVISION)
|
|
135
179
|
1. **{issue}**: {description} → **Fix**: {specific fix instruction}
|
|
136
180
|
2. ...
|
|
181
|
+
|
|
182
|
+
## Algorithm Review Suggestions (if NEEDS_ALGORITHM_REVIEW)
|
|
183
|
+
|
|
184
|
+
**按优先级排序的改进建议**(只调整超参数/训练配置,不改核心算法):
|
|
185
|
+
|
|
186
|
+
1. **{建议名称}**(最可能有效)
|
|
187
|
+
- **What to change**: {具体修改内容}
|
|
188
|
+
- **Where**: {文件路径和代码位置}
|
|
189
|
+
- **Expected improvement**: {预期效果}
|
|
190
|
+
|
|
191
|
+
2. **{次要建议}**
|
|
192
|
+
- ...
|
|
193
|
+
|
|
194
|
+
**Note**: 如果尝试所有建议后仍无改善,可能需要重新考虑算法选择或数据质量。
|
|
137
195
|
```
|
|
138
196
|
|
|
139
|
-
### Step
|
|
197
|
+
### Step 5a: 代码修复迭代(如果 NEEDS_REVISION)
|
|
140
198
|
|
|
141
199
|
**⚠️ 防偏移机制:每轮迭代都重新读取原始设计文档,确保修改方向正确。**
|
|
142
200
|
|
|
@@ -147,29 +205,145 @@ metadata:
|
|
|
147
205
|
- 对照原始学术设计目标
|
|
148
206
|
- 确保修改不是为了"绕过审查"而偏离学术严谨性
|
|
149
207
|
- 确认修改符合 survey 中的公式定义和 plan 中的设计意图
|
|
150
|
-
3. 修改 `$W/project/`
|
|
208
|
+
3. 修改 `$W/project/` 中的代码(修复 bug、补全缺失实现)
|
|
151
209
|
4. 重新执行:
|
|
152
210
|
```bash
|
|
153
211
|
cd $W/project && source .venv/bin/activate && python run.py --epochs 2
|
|
154
212
|
```
|
|
155
213
|
5. 读取执行输出,验证修复
|
|
156
214
|
6. **重新执行 Step 2-4**(提取概念清单 → 逐项检查 → 写报告),写入 `judge_v{N+1}.md`
|
|
157
|
-
7. 如果 PASS → 停止;否则继续
|
|
215
|
+
7. 如果 PASS 或 NEEDS_ALGORITHM_REVIEW → 停止;否则继续
|
|
216
|
+
|
|
217
|
+
### Step 5b: 算法反思与调优(如果 NEEDS_ALGORITHM_REVIEW)
|
|
218
|
+
|
|
219
|
+
**⚠️ 关键新增 — 代码正确但性能不佳时的改进循环。**
|
|
220
|
+
|
|
221
|
+
**前提**:代码实现正确(所有原子性概念 ✓),但 2 epoch 验证显示性能异常。
|
|
222
|
+
|
|
223
|
+
循环最多 **2 次**:
|
|
224
|
+
|
|
225
|
+
#### 5b.1 性能诊断
|
|
226
|
+
|
|
227
|
+
重新读取以下材料进行诊断:
|
|
228
|
+
- `$W/ml_res.md` — 2 epoch 验证的具体数值
|
|
229
|
+
- `$W/survey_res.md` — baseline 方法的超参数设置(特别是学习率、batch size)
|
|
230
|
+
- `$W/plan_res.md` — 当前实现的超参数配置
|
|
231
|
+
- `$W/project/run.py` 和 `$W/project/training/` — 训练配置代码
|
|
232
|
+
|
|
233
|
+
**诊断检查清单**:
|
|
234
|
+
|
|
235
|
+
| 症状 | 诊断步骤 | 常见原因 |
|
|
236
|
+
|------|----------|----------|
|
|
237
|
+
| Loss 下降 <5% | 对比 plan lr vs survey baseline lr | lr 过小(如 plan=1e-5 但 survey=1e-3) |
|
|
238
|
+
| Loss 震荡 >20% | 同上 + 检查 batch size | lr 过大或 batch size 过小 |
|
|
239
|
+
| Accuracy 接近随机 | 检查数据预处理代码、检查 loss 是否下降 | 数据归一化缺失、特征错误、模型过简单 |
|
|
240
|
+
| Loss=NaN/Inf | 检查是否有 normalization、检查 lr | 梯度爆炸、数值不稳定 |
|
|
241
|
+
|
|
242
|
+
#### 5b.2 生成改进建议
|
|
243
|
+
|
|
244
|
+
基于诊断结果,生成**按优先级排序**的改进建议。
|
|
245
|
+
|
|
246
|
+
**改进范围限制**:
|
|
247
|
+
- ✅ 允许:调整超参数(lr、batch size、epochs、optimizer、scheduler)
|
|
248
|
+
- ✅ 允许:修改训练配置(添加 warmup、gradient clipping、weight decay)
|
|
249
|
+
- ✅ 允许:修复数据预处理问题(添加归一化、标准化)
|
|
250
|
+
- ❌ 禁止:修改核心算法逻辑(模型架构、loss 函数数学公式)
|
|
251
|
+
|
|
252
|
+
**建议格式**(写入 `judge_v{N}.md` 的 "Algorithm Review Suggestions" 部分):
|
|
253
|
+
|
|
254
|
+
```markdown
|
|
255
|
+
1. **调整学习率**(优先级:高,预期改善:显著)
|
|
256
|
+
- **当前值**: lr=1e-5 (from plan_res.md)
|
|
257
|
+
- **建议值**: lr=1e-3 (from survey_res.md Table 2, all baselines use 1e-3)
|
|
258
|
+
- **修改位置**: `$W/project/run.py:L15` — `optimizer = Adam(lr=1e-3)`
|
|
259
|
+
- **理由**: Loss 下降仅 0.9%,远低于正常 10%+,高度怀疑 lr 过小
|
|
260
|
+
|
|
261
|
+
2. **添加数据归一化**(优先级:中,预期改善:中等)
|
|
262
|
+
- **检查**: `$W/project/data/dataset.py` 是否有归一化
|
|
263
|
+
- **建议**: 添加 `transforms.Normalize(mean=[0.5], std=[0.5])`
|
|
264
|
+
- **理由**: 如果输入数据范围 [0,255],模型收敛会很慢
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### 5b.3 执行改进并验证
|
|
268
|
+
|
|
269
|
+
1. 根据建议**逐项尝试**(从优先级高的开始)
|
|
270
|
+
2. 每次修改后:
|
|
271
|
+
```bash
|
|
272
|
+
cd $W/project && source .venv/bin/activate && python run.py --epochs 2
|
|
273
|
+
```
|
|
274
|
+
3. 读取新的执行输出,对比改进前后:
|
|
275
|
+
- Loss reduction 是否提升?(如 0.9% → 12%)
|
|
276
|
+
- Metrics 是否改善?(如 accuracy 12% → 34%)
|
|
277
|
+
4. 记录每次尝试的结果到 `judge_v{N+1}.md` 的 "Algorithm Review Iterations" 部分:
|
|
278
|
+
|
|
279
|
+
```markdown
|
|
280
|
+
## Algorithm Review Iterations
|
|
281
|
+
|
|
282
|
+
### Iteration 1
|
|
283
|
+
- **Change**: Increased lr from 1e-5 to 1e-3
|
|
284
|
+
- **Result**:
|
|
285
|
+
- Loss reduction: 0.9% → 12.3% ✓ (improvement: +11.4%)
|
|
286
|
+
- Accuracy: 12% → 34% ✓ (improvement: +22%)
|
|
287
|
+
- **Conclusion**: Learning rate was the bottleneck. Issue resolved.
|
|
288
|
+
- **New verdict**: PASS ✓
|
|
289
|
+
|
|
290
|
+
### Iteration 2 (if needed)
|
|
291
|
+
- ...
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### 5b.4 判定
|
|
295
|
+
|
|
296
|
+
- **改善显著**(loss reduction 提升 >5%)→ `verdict: PASS`,停止
|
|
297
|
+
- **改善微小**(<2%)→ 继续下一个建议或下一轮
|
|
298
|
+
- **2 轮后仍无改善** → `verdict: BLOCKED`,标注原因(如"所有超参数调整均无效,可能需要重新选择算法或检查数据质量")
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
**Step 5a vs 5b 的区别**:
|
|
303
|
+
|
|
304
|
+
| | Step 5a (NEEDS_REVISION) | Step 5b (NEEDS_ALGORITHM_REVIEW) |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| 触发条件 | 代码有 bug、实现错误 | 代码正确但性能不佳 |
|
|
307
|
+
| 修改范围 | 核心算法代码 | 超参数和训练配置 |
|
|
308
|
+
| 迭代次数 | 3 次 | 2 次 |
|
|
309
|
+
| 目标 | 正确性 | 有效性 |
|
|
158
310
|
|
|
159
311
|
### Step 6: 最终判定
|
|
160
312
|
|
|
161
|
-
|
|
313
|
+
**终止条件**:
|
|
314
|
+
|
|
315
|
+
| 场景 | 判定 | 说明 |
|
|
316
|
+
|------|------|------|
|
|
317
|
+
| 所有 checklist ✓ + 性能合理 | `PASS` | 交付给 research-experiment |
|
|
318
|
+
| Step 5a 3 轮后仍有 bug | `BLOCKED - Code Issues` | 列出剩余问题,等待用户介入 |
|
|
319
|
+
| Step 5b 2 轮后性能仍异常 | `BLOCKED - Performance Issues` | 标注尝试过的改进和结果,建议用户重新考虑算法选择或数据质量 |
|
|
162
320
|
|
|
163
321
|
---
|
|
164
322
|
|
|
165
323
|
## Rules
|
|
166
324
|
|
|
325
|
+
### 审查标准
|
|
326
|
+
|
|
167
327
|
1. 审查必须逐项对照 plan,不能只看"代码能跑"
|
|
168
328
|
2. 每个 issue 必须给出具体的修复指令(不是"请改进")
|
|
169
329
|
3. 验证修复后必须重新执行代码并检查输出
|
|
170
|
-
4. PASS
|
|
330
|
+
4. **PASS 的前提**:所有 checklist 项通过 + 性能初步评估合理(不仅仅是"有下降")
|
|
171
331
|
5. **数据集必须验证真实性** —— 实际执行数据加载代码,确认有真实数据(哪怕是小规模);纯随机 tensor 不算
|
|
172
332
|
6. **执行时间必须与算力匹配** —— 2 epoch 训练时间过短(数据量 >1000 却 <2s)说明数据未加载或训练是空循环
|
|
173
333
|
7. **算法实现必须完整** —— plan 中标注的核心创新点必须逐一检查,不能被简化为 `nn.Linear` 占位
|
|
174
334
|
8. **原子性概念逐一核对(Novix Judge 机制)** —— Step 2 提取的每个概念都必须在 judge 报告的表格中有对应行,标注 ✓ 或 ✗
|
|
175
|
-
9. **防偏移(每轮迭代必须重新对齐)** —— Step
|
|
335
|
+
9. **防偏移(每轮迭代必须重新对齐)** —— Step 5a/5b 每轮修改前必须重新读取 survey_res.md 和 plan_res.md,确保不偏离原始设计目标
|
|
336
|
+
|
|
337
|
+
### 性能评估(新增)
|
|
338
|
+
|
|
339
|
+
10. **性能初步评估是强制项** —— Step 3D 必须执行,不能跳过
|
|
340
|
+
11. **Loss 下降幅度有最低要求** —— 2 epoch 验证的 loss reduction <5% 必须标记为性能异常
|
|
341
|
+
12. **Metrics 必须超过随机 baseline** —— 分类任务 accuracy 接近 1/num_classes(±10%)视为"模型未学习"
|
|
342
|
+
13. **性能异常触发算法反思** —— 代码正确但性能不佳时,必须进入 Step 5b 尝试调优,不能直接 PASS
|
|
343
|
+
|
|
344
|
+
### 算法反思(新增)
|
|
345
|
+
|
|
346
|
+
14. **Step 5b 只调超参数,不改算法** —— 禁止修改核心算法逻辑、模型架构、loss 函数公式
|
|
347
|
+
15. **改进建议必须有依据** —— 每个建议必须引用 survey_res.md 或 plan_res.md 中的具体内容
|
|
348
|
+
16. **改进效果必须量化** —— 每次尝试后必须记录改善幅度(如 "loss reduction +11.4%"),不能只说"有改善"
|
|
349
|
+
17. **2 轮算法反思后仍无改善视为 BLOCKED** —— 标注原因并建议用户介入(如"可能需要更换算法或检查数据质量")
|
|
@@ -61,6 +61,15 @@ ls $W/papers/_meta/
|
|
|
61
61
|
- **Model Architecture** section
|
|
62
62
|
- 数学公式定义
|
|
63
63
|
|
|
64
|
+
**对于大型论文**(>2000 行),使用 `paper_browser` 分页阅读:
|
|
65
|
+
```javascript
|
|
66
|
+
// 先读前 100 行找到 section 位置
|
|
67
|
+
paper_browser({ file_path: "$W/papers/{arxiv_id}/{file}.tex", start_line: 1, num_lines: 100 })
|
|
68
|
+
|
|
69
|
+
// 找到 Method section 后,跳转到该位置
|
|
70
|
+
paper_browser({ file_path: "$W/papers/{arxiv_id}/{file}.tex", start_line: 450, num_lines: 150 })
|
|
71
|
+
```
|
|
72
|
+
|
|
64
73
|
如果没有 .tex(只有 PDF),基于 abstract 分析。
|
|
65
74
|
|
|
66
75
|
#### 2.2 提取核心内容
|