Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# .env.example
## Deployed Eliza URL
PROD_URL=https://your-production-url.com
DEV_URL=https://your-dev-url.com
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ node_modules
.env
.migration-complete
drizzle
oxigraph
oxigraph
26 changes: 26 additions & 0 deletions README.scaich.md
Original file line number Diff line number Diff line change
@@ -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).


3 changes: 2 additions & 1 deletion src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./dkgInsert.ts";
// src/actions/index.ts
export * from "./scaichSearch";
33 changes: 33 additions & 0 deletions src/actions/scaichSearch.ts
Original file line number Diff line number Diff line change
@@ -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}` };
}
},
};
150 changes: 75 additions & 75 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -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: "<describe memory in a short way, as a title here>",
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: "<describe memory in a short way, as a title here>",
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 = `
Expand Down Expand Up @@ -99,7 +99,7 @@ SELECT DISTINCT ?headline ?articleBody
LIMIT 10`;

export const sparqlExamples = [
`
`
SELECT DISTINCT ?headline ?articleBody
WHERE {
?s a <http://schema.org/SocialMediaPosting> .
Expand All @@ -124,7 +124,7 @@ export const sparqlExamples = [
}
LIMIT 10
`,
`
`
SELECT DISTINCT ?headline ?articleBody
WHERE {
?s a <http://schema.org/SocialMediaPosting> .
Expand All @@ -136,7 +136,7 @@ export const sparqlExamples = [
)
}
`,
`
`
SELECT DISTINCT ?headline ?articleBody ?keywordName
WHERE {
?s a <http://schema.org/SocialMediaPosting> .
Expand All @@ -150,7 +150,7 @@ export const sparqlExamples = [
)
}
`,
`
`
SELECT DISTINCT ?headline ?articleBody ?aboutName
WHERE {
?s a <http://schema.org/SocialMediaPosting> .
Expand All @@ -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=",
};
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>, runtime: IAgentRuntime) => {
Expand All @@ -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],
Expand Down
30 changes: 30 additions & 0 deletions src/services/scaich/client.ts
Original file line number Diff line number Diff line change
@@ -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<any> {
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;
}
}
4 changes: 4 additions & 0 deletions src/services/scaich/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// src/services/scaich/index.ts
export * from "./client";
export * from "./processResults";
export * from "./types";
48 changes: 48 additions & 0 deletions src/services/scaich/processResults.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, any>> {
const jsonLdGraph: Record<string, any> = {
"@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;
}
Loading