Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
7 changes: 2 additions & 5 deletions dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
/// <reference types="vite/client" />
import { Calculator } from "@langchain/community/tools/calculator";
import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";
import Viewer from "@samvera/clover-iiif/viewer";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { PluginControl, PluginPanel } from "../src/plugin";
import { UserTokenProvider } from "../src/providers";
import { WikipediaQueryRun } from "./tools/wikipedia-query-run";

const wiki_tool = new WikipediaQueryRun({
topKResults: 3,
maxDocContentLength: 4000,
});

const calc_tool = new Calculator();

createRoot(document.getElementById("root")!).render(
<StrictMode>
<Viewer
Expand All @@ -35,7 +32,7 @@ createRoot(document.getElementById("root")!).render(
en: ["AI Chat"],
},
componentProps: {
provider: new UserTokenProvider({ tools: [wiki_tool, calc_tool] }),
provider: new UserTokenProvider({ tools: [wiki_tool] }),
},
},
},
Expand Down
237 changes: 237 additions & 0 deletions dev/tools/wikipedia-query-run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* Adapted from @langchain/community (MIT License)
* Source: https://github.com/langchain-ai/langchainjs/blob/5d8b75f2ad32ac8b322e5d03b99ff2ee99515e81/libs/langchain-community/src/tools/wikipedia_query_run.ts
* Modifications: Removed build process causing rolldown errors in client; adjusted for browser-only usage; added origin=* to fetch requests
* The original project license text is available in the root LICENSE file or at:
* https://github.com/langchain-ai/langchainjs/blob/main/LICENSE
*/

import { Tool } from "@langchain/core/tools";

/**
* Interface for the parameters that can be passed to the
* WikipediaQueryRun constructor.
*/
export interface WikipediaQueryRunParams {
topKResults?: number;
maxDocContentLength?: number;
baseUrl?: string;
}

/**
* Type alias for URL parameters. Represents a record where keys are
* strings and values can be string, number, boolean, undefined, or null.
*/
type UrlParameters = Record<string, string | number | boolean | undefined | null>;

/**
* Interface for the structure of search results returned by the Wikipedia
* API.
*/
interface SearchResults {
query: {
search: Array<{
title: string;
}>;
};
}

/**
* Interface for the structure of a page returned by the Wikipedia API.
*/
interface Page {
pageid: number;
ns: number;
title: string;
extract: string;
}

/**
* Interface for the structure of a page result returned by the Wikipedia
* API.
*/
interface PageResult {
batchcomplete: string;
query: {
pages: Record<string, Page>;
};
}

/**
* Wikipedia query tool integration.
*
* Setup:
* Install `@langchain/community`. You'll also need an API key.
*
* ```bash
* npm install @langchain/community
* ```
*
* ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.tools_wikipedia_query_run.WikipediaQueryRun.html#constructor)
*
* <details open>
* <summary><strong>Instantiate</strong></summary>
*
* ```typescript
* import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run";
*
* const tool = new WikipediaQueryRun({
* topKResults: 3,
* maxDocContentLength: 4000,
* });
* ```
* </details>
*
* <br />
*
* <details>
*
* <summary><strong>Invocation</strong></summary>
*
* ```typescript
* await tool.invoke("what is the current weather in sf?");
* ```
* </details>
*
* <br />
*
* <details>
*
* <summary><strong>Invocation with tool call</strong></summary>
*
* ```typescript
* // This is usually generated by a model, but we'll create a tool call directly for demo purposes.
* const modelGeneratedToolCall = {
* args: {
* input: "what is the current weather in sf?",
* },
* id: "tool_call_id",
* name: tool.name,
* type: "tool_call",
* };
* await tool.invoke(modelGeneratedToolCall);
* ```
*
* ```text
* ToolMessage {
* "content": "...",
* "name": "wikipedia-api",
* "additional_kwargs": {},
* "response_metadata": {},
* "tool_call_id": "tool_call_id"
* }
* ```
* </details>
*/
export class WikipediaQueryRun extends Tool {
static lc_name() {
return "WikipediaQueryRun";
}

name = "wikipedia-api";

description = "A tool for interacting with and fetching data from the Wikipedia API.";

protected topKResults = 3;

protected maxDocContentLength = 4000;

protected baseUrl = "https://en.wikipedia.org/w/api.php";

constructor(params: WikipediaQueryRunParams = {}) {
super();

this.topKResults = params.topKResults ?? this.topKResults;
this.maxDocContentLength = params.maxDocContentLength ?? this.maxDocContentLength;
this.baseUrl = params.baseUrl ?? this.baseUrl;
}

async _call(query: string): Promise<string> {
const searchResults = await this._fetchSearchResults(query);
const summaries: string[] = [];

for (let i = 0; i < Math.min(this.topKResults, searchResults.query.search.length); i += 1) {
const page = searchResults.query.search[i].title;
const pageDetails = await this._fetchPage(page, true);

if (pageDetails) {
const summary = `Page: ${page}\nSummary: ${pageDetails.extract}`;
summaries.push(summary);
}
}

if (summaries.length === 0) {
return "No good Wikipedia Search Result was found";
} else {
return summaries.join("\n\n").slice(0, this.maxDocContentLength);
}
}

/**
* Fetches the content of a specific Wikipedia page. It returns the
* extracted content as a string.
* @param page The specific Wikipedia page to fetch its content.
* @param redirect A boolean value to indicate whether to redirect or not.
* @returns The extracted content of the specific Wikipedia page as a string.
*/
public async content(page: string, redirect = true): Promise<string> {
try {
const result = await this._fetchPage(page, redirect);
return result.extract;
} catch (error) {
throw new Error(`Failed to fetch content for page "${page}": ${error}`);
}
}

/**
* Builds a URL for the Wikipedia API using the provided parameters.
* @param parameters The parameters to be used in building the URL.
* @returns A string representing the built URL.
*/
protected buildUrl<P extends UrlParameters>(parameters: P): string {
const nonUndefinedParams: [string, string][] = Object.entries(parameters)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([_, value]) => value !== undefined)
.map(([key, value]) => [key, `${value}`]);
const searchParams = new URLSearchParams(nonUndefinedParams);
return `${this.baseUrl}?${searchParams}`;
}

private async _fetchSearchResults(query: string): Promise<SearchResults> {
const searchParams = new URLSearchParams({
action: "query",
list: "search",
srsearch: query,
origin: "*",
format: "json",
});

const response = await fetch(`${this.baseUrl}?${searchParams.toString()}`);
if (!response.ok) throw new Error("Network response was not ok");

const data: SearchResults = await response.json();

return data;
}

private async _fetchPage(page: string, redirect: boolean): Promise<Page> {
const params = new URLSearchParams({
action: "query",
prop: "extracts",
explaintext: "true",
redirects: redirect ? "1" : "0",
format: "json",
origin: "*",
titles: page,
});

const response = await fetch(`${this.baseUrl}?${params.toString()}`);
if (!response.ok) throw new Error("Network response was not ok");

const data: PageResult = await response.json();
const { pages } = data.query;
const pageId = Object.keys(pages)[0];

return pages[pageId];
}
}
12 changes: 12 additions & 0 deletions dev/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig, mergeConfig } from "vite";
import config from "../vite.config.ts";

export default mergeConfig(
config,
defineConfig({
root: "dev",
server: {
port: 3000,
},
}),
);
Loading