diff --git a/.env.example b/.env.example index 797c6e5..a42c855 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +# .env.example ## Deployed Eliza URL PROD_URL=https://your-production-url.com DEV_URL=https://your-dev-url.com @@ -33,14 +34,6 @@ OPENAI_API_KEY=your_openai_api_key # Starting with sk- # Anthropic Configuration ANTHROPIC_API_KEY=your_anthropic_api_key # For Claude -# OriginTrail DKG -DKG_ENVIRONMENT="testnet" # Values: "development", "testnet", "mainnet" -DKG_HOSTNAME="your_dkg_hostname" -DKG_PORT="8900" -DKG_PUBLIC_KEY="your_dkg_public_key" -DKG_PRIVATE_KEY="your_dkg_private_key" -DKG_BLOCKCHAIN_NAME="base:84532" # Values: (mainnet) "base:8453", "gnosis:100", "otp:2043" (testnet) "base:84532", "gnosis:10200", "otp:20430" - # Unstructured API Key UNSTRUCTURED_API_KEY=your_unstructured_api_key diff --git a/.gitignore b/.gitignore index 4363e8a..0407c90 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ node_modules .env .migration-complete drizzle -oxigraph \ No newline at end of file +oxigraph diff --git a/README.scaich.md b/README.scaich.md new file mode 100644 index 0000000..b0ab9c1 --- /dev/null +++ b/README.scaich.md @@ -0,0 +1,26 @@ +# ScaiCh Plugin 🤖📚 + +A plugin for researchers to search literature via SCAI API, process Google Drive documents, and generate biological hypotheses. Integrates with RDF stores like [OriginTrail DKG](https://origintrail.io) or [Oxigraph](https://github.com/oxigraph/oxigraph). + +## 🚀 Features + +- **Literature Search**: Query SCAI API for scientific articles and store as Knowledge Assets. +- **Google Drive Monitoring**: Auto-process new PDFs into Knowledge Assets. +- **Hypothesis Generation**: Generate and evaluate biological hypotheses, scored by JudgeLLM. + +> [!NOTE] Under development. Google Drive webhooks require public URLs (e.g., ngrok). + +``` +{ + "action": "SCAI_SEARCH", + "content": "The History of Sci-hub" +} +``` + +## 📋 Notes + +- SCAI API requires no authentication for now. +- Logs: Set `DEFAULT_LOG_LEVEL=debug` in `.env`. +- Issues? Check logs or submit to [GitHub](https://github.com/bio-xyz/plugin-scaich). + + diff --git a/src/actions/index.ts b/src/actions/index.ts index 66474ff..a01bffd 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1 +1,2 @@ -export * from "./dkgInsert.ts"; +// src/actions/index.ts +export * from "./scaichSearch"; \ No newline at end of file diff --git a/src/actions/scaichSearch.ts b/src/actions/scaichSearch.ts new file mode 100644 index 0000000..5980654 --- /dev/null +++ b/src/actions/scaichSearch.ts @@ -0,0 +1,33 @@ +// src/actions/scaichSearch.ts +import { searchLiterature } from "../services/scaich/client"; +import { processScaiResults } from "../services/scaich/processResults"; +import { logger } from "@elizaos/core"; + +export const SCAI_SEARCH_ACTION = "SCAI_SEARCH"; + +export const scaichSearch = { + name: SCAI_SEARCH_ACTION, + description: "Search scientific literature using the SCAI API", + validate: async () => { + // 移除 SCAI_API_KEY 检查,因为 API 不需要认证 + return { valid: true }; + }, + handler: async ({ message, runtime }) => { + const query = message.content.trim(); + if (!query) { + return { response: "Please provide a search query" }; + } + + try { + const response = await searchLiterature(query, 10, false); + const jsonLdGraph = await processScaiResults(response); + return { + response: `Found ${response.results.length} articles for query "${query}". Results stored in knowledge graph.`, + data: jsonLdGraph, + }; + } catch (error) { + logger.error(`SCAI search failed: ${error.message}`); + return { response: `Error searching literature: ${error.message}` }; + } + }, +}; \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index 01c0ede..aa9b5fb 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,76 +1,76 @@ import { z } from "zod"; // TODO: add isConnectedTo field or similar which you will use to connect w other KAs export const dkgMemoryTemplate = { - "@context": "http://schema.org", - "@type": "SocialMediaPosting", - headline: "", - articleBody: - "Check out this amazing project on decentralized cloud networks! @DecentralCloud #Blockchain #Web3", - author: { - "@type": "Person", - "@id": "uuid:john:doe", - name: "John Doe", - identifier: "@JohnDoe", - url: "https://twitter.com/JohnDoe", + "@context": "http://schema.org", + "@type": "SocialMediaPosting", + headline: "", + articleBody: + "Check out this amazing project on decentralized cloud networks! @DecentralCloud #Blockchain #Web3", + author: { + "@type": "Person", + "@id": "uuid:john:doe", + name: "John Doe", + identifier: "@JohnDoe", + url: "https://twitter.com/JohnDoe", + }, + dateCreated: "yyyy-mm-ddTHH:mm:ssZ", + interactionStatistic: [ + { + "@type": "InteractionCounter", + interactionType: { + "@type": "LikeAction", + }, + userInteractionCount: 150, }, - dateCreated: "yyyy-mm-ddTHH:mm:ssZ", - interactionStatistic: [ - { - "@type": "InteractionCounter", - interactionType: { - "@type": "LikeAction", - }, - userInteractionCount: 150, - }, - { - "@type": "InteractionCounter", - interactionType: { - "@type": "ShareAction", - }, - userInteractionCount: 45, - }, - ], - mentions: [ - { - "@type": "Person", - name: "Twitter account mentioned name goes here", - identifier: "@TwitterAccount", - url: "https://twitter.com/TwitterAccount", - }, - ], - keywords: [ - { - "@type": "Text", - "@id": "uuid:keyword1", - name: "keyword1", - }, - { - "@type": "Text", - "@id": "uuid:keyword2", - name: "keyword2", - }, - ], - about: [ - { - "@type": "Thing", - "@id": "uuid:thing1", - name: "Blockchain", - url: "https://en.wikipedia.org/wiki/Blockchain", - }, - { - "@type": "Thing", - "@id": "uuid:thing2", - name: "Web3", - url: "https://en.wikipedia.org/wiki/Web3", - }, - { - "@type": "Thing", - "@id": "uuid:thing3", - name: "Decentralized Cloud", - url: "https://example.com/DecentralizedCloud", - }, - ], - url: "https://twitter.com/JohnDoe/status/1234567890", + { + "@type": "InteractionCounter", + interactionType: { + "@type": "ShareAction", + }, + userInteractionCount: 45, + }, + ], + mentions: [ + { + "@type": "Person", + name: "Twitter account mentioned name goes here", + identifier: "@TwitterAccount", + url: "https://twitter.com/TwitterAccount", + }, + ], + keywords: [ + { + "@type": "Text", + "@id": "uuid:keyword1", + name: "keyword1", + }, + { + "@type": "Text", + "@id": "uuid:keyword2", + name: "keyword2", + }, + ], + about: [ + { + "@type": "Thing", + "@id": "uuid:thing1", + name: "Blockchain", + url: "https://en.wikipedia.org/wiki/Blockchain", + }, + { + "@type": "Thing", + "@id": "uuid:thing2", + name: "Web3", + url: "https://en.wikipedia.org/wiki/Web3", + }, + { + "@type": "Thing", + "@id": "uuid:thing3", + name: "Decentralized Cloud", + url: "https://example.com/DecentralizedCloud", + }, + ], + url: "https://twitter.com/JohnDoe/status/1234567890", }; export const combinedSparqlExample = ` @@ -99,7 +99,7 @@ SELECT DISTINCT ?headline ?articleBody LIMIT 10`; export const sparqlExamples = [ - ` + ` SELECT DISTINCT ?headline ?articleBody WHERE { ?s a . @@ -124,7 +124,7 @@ export const sparqlExamples = [ } LIMIT 10 `, - ` + ` SELECT DISTINCT ?headline ?articleBody WHERE { ?s a . @@ -136,7 +136,7 @@ export const sparqlExamples = [ ) } `, - ` + ` SELECT DISTINCT ?headline ?articleBody ?keywordName WHERE { ?s a . @@ -150,7 +150,7 @@ export const sparqlExamples = [ ) } `, - ` + ` SELECT DISTINCT ?headline ?articleBody ?aboutName WHERE { ?s a . @@ -177,6 +177,6 @@ export const generalSparqlQuery = ` `; export const DKG_EXPLORER_LINKS = { - testnet: "https://dkg-testnet.origintrail.io/explore?ual=", - mainnet: "https://dkg.origintrail.io/explore?ual=", + testnet: "https://dkg-testnet.origintrail.io/explore?ual=", + mainnet: "https://dkg.origintrail.io/explore?ual=", }; diff --git a/src/index.ts b/src/index.ts index 2abf07c..8e5f8c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { dkgInsert } from "./actions/dkgInsert"; import { HypothesisService } from "./services"; import { initWithMigrations } from "./helper"; import { gdriveManualSync, gdriveWebhook, health } from "./routes"; +import { scaichSearch } from "./actions/scaichSearch"; export const dkgPlugin: Plugin = { init: async (config: Record, runtime: IAgentRuntime) => { @@ -13,10 +14,9 @@ export const dkgPlugin: Plugin = { await initWithMigrations(runtime); }, 20000); // prevent `undefined` error, the db property is not available immediately }, - name: "dkg", - description: - "Agent DKG which allows you to store memories on the OriginTrail Decentralized Knowledge Graph", - actions: [dkgInsert], + name: "scaichplugin", + description: "A plugin for literature search using SCAI API and knowledge graph integration with BioAgent Plugin", + actions: [dkgInsert, scaichSearch], providers: [], evaluators: [], services: [HypothesisService], diff --git a/src/services/scaich/client.ts b/src/services/scaich/client.ts new file mode 100644 index 0000000..ec1e5d7 --- /dev/null +++ b/src/services/scaich/client.ts @@ -0,0 +1,30 @@ +// src/services/scaich/client.ts +import axios from "axios"; +import { logger } from "@elizaos/core"; + +const SCAI_API_URL = "https://api.scai.sh/search"; + +/** + * Sends a search query to the SCAI API and returns the response. + * @param query - The search query string + * @param limit - Maximum number of results to return + * @param oa - Whether to restrict to open access articles + * @returns The API response data + */ +export async function searchLiterature( + query: string, + limit: number = 10, + oa: boolean = false +): Promise { + try { + const response = await axios.get(SCAI_API_URL, { + params: { query, limit, oa }, + timeout: 10000, // 10 seconds timeout + }); + logger.info(`SCAI API request successful for query: ${query}`); + return response.data; + } catch (error) { + logger.error(`SCAI API request failed: ${error.message}`); + throw error; + } +} \ No newline at end of file diff --git a/src/services/scaich/index.ts b/src/services/scaich/index.ts new file mode 100644 index 0000000..48fb9ed --- /dev/null +++ b/src/services/scaich/index.ts @@ -0,0 +1,4 @@ +// src/services/scaich/index.ts +export * from "./client"; +export * from "./processResults"; +export * from "./types"; \ No newline at end of file diff --git a/src/services/scaich/processResults.ts b/src/services/scaich/processResults.ts new file mode 100644 index 0000000..3b04a2d --- /dev/null +++ b/src/services/scaich/processResults.ts @@ -0,0 +1,48 @@ +// src/services/scaich/processResults.ts +import { ScaiResponse, ScaiSearchResult } from "./types"; +import { logger } from "@elizaos/core"; +import { storeJsonLd } from "../gdrive/storeJsonLdToKg"; // 重用现有存储逻辑 + +/** + * Converts SCAI search results to JSON-LD and stores them in the knowledge graph. + * @param response - The SCAI API response + * @returns The generated JSON-LD graph + */ +export async function processScaiResults(response: ScaiResponse): Promise> { + const jsonLdGraph: Record = { + "@context": "http://schema.org", + "@type": "ScholarlyArticle", + "query": response.query, + "articles": [], + }; + + for (const result of response.results) { + const article = { + "@id": result.doi, + "@type": "ScholarlyArticle", + "name": result.title, + "abstract": result.abstract, + "author": result.author.split(", ").map((name) => ({ "@type": "Person", "name": name })), + "url": result.url, + "sameAs": result.scihub_url, + "datePublished": result.year.toString(), + "identifier": result.doi, + "keywords": response.summary.sum.split(", "), + }; + jsonLdGraph.articles.push(article); + } + + try { + // 存储到知识图谱 + const success = await storeJsonLd(jsonLdGraph); + if (success) { + logger.info(`Successfully stored SCAI search results for query: ${response.query}`); + } else { + logger.error("Failed to store JSON-LD to knowledge graph"); + } + } catch (error) { + logger.error(`Error storing JSON-LD: ${error.message}`); + } + + return jsonLdGraph; +} \ No newline at end of file diff --git a/src/services/scaich/types.ts b/src/services/scaich/types.ts new file mode 100644 index 0000000..a7f6a37 --- /dev/null +++ b/src/services/scaich/types.ts @@ -0,0 +1,25 @@ +// src/services/scaich/types.ts +export interface ScaiSearchResult { + abstract: string; + author: string; + doi: string; + location: string; + referencecount: number; + scihub_url: string; + similarity: string; + source: string; + title: string; + url: string; + year: number; +} + +export interface ScaiSummary { + cot: string; + sum: string; +} + +export interface ScaiResponse { + query: string; + results: ScaiSearchResult[]; + summary: ScaiSummary; +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 7a10da6..70ec670 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,6 @@ import { z } from "zod"; +// src/types.ts +export * from "./services/scaich/types"; // 添加 SCAI 类型 export const DKGMemorySchema = z.object({ "@context": z.literal("http://schema.org"),