);
};
diff --git a/web/src/components/controls.tsx b/web/src/components/controls.tsx
index a92ae63..99524e6 100644
--- a/web/src/components/controls.tsx
+++ b/web/src/components/controls.tsx
@@ -1,57 +1,39 @@
import type { Mode } from "../types";
-export const Controls = ({
- mode,
- setMode,
-}: {
- mode: [Mode, boolean];
+const MODES = {
+ rookie:
+ "For people who are new to the field of AI alignment. The " +
+ "answer might be longer, since technical terms will be " +
+ "explained in more detail and less background will be assumed.",
+ concise:
+ "Quick and to the point. Followup questions may need to be " +
+ "asked to get the full picture of what's going on.",
+ default: "A balanced default mode.",
+};
+
+type ControlsType = {
+ mode: Mode;
setMode: (m: any) => void;
-}) => {
+};
+export const Controls = ({ mode, setMode }: ControlsType) => {
{
/* three buttons for the three modes, place far right, 1rem between each */
}
return (
);
};
diff --git a/web/src/components/header.tsx b/web/src/components/header.tsx
index 1941361..641a53c 100644
--- a/web/src/components/header.tsx
+++ b/web/src/components/header.tsx
@@ -3,9 +3,9 @@ import Link from "next/link";
import Image from "next/image";
import logo from "../logo.svg";
-const Header: React.FC<{ page: "index" | "semantic" | "playground" }> = ({
- page,
-}) => {
+export type Page = "index" | "semantic" | "playground" | "tester";
+
+const Header: React.FC<{ page: Page }> = ({ page }) => {
const sidebar =
page === "index" ? (
diff --git a/web/src/components/page.tsx b/web/src/components/page.tsx
index f64337b..8f3a486 100644
--- a/web/src/components/page.tsx
+++ b/web/src/components/page.tsx
@@ -1,17 +1,19 @@
import React, { ReactNode } from "react";
import Head from "next/head";
+import type { Page as PageType } from "./header";
import Header from "./header";
-const Page: React.FC<{ children: ReactNode; page: "index" | "semantic" }> = ({
- page,
- children,
-}) => {
+const Page: React.FC<{
+ children: ReactNode;
+ widescreen?: boolean;
+ page: PageType;
+}> = ({ page, children, widescreen = false }) => {
return (
<>
AI Safety Info
-
+
{children}
diff --git a/web/src/components/searchbox.tsx b/web/src/components/searchbox.tsx
index 1e91c47..0b7bd61 100644
--- a/web/src/components/searchbox.tsx
+++ b/web/src/components/searchbox.tsx
@@ -1,33 +1,10 @@
import React from "react";
import { useState, useEffect } from "react";
+import { initialQuestions } from "../settings";
import type { Followup } from "../types";
import TextareaAutosize from "react-textarea-autosize";
import dynamic from "next/dynamic";
-// initial questions to fill the search box with.
-export const initialQuestions: string[] = [
- "Are there any regulatory efforts aimed at addressing AI safety and alignment concerns?",
- "How can I help with AI safety and alignment?",
- "How could a predictive model - like an LLM - act like an agent?",
- "How could an AI possibly be an x-risk when some populations aren't even connected to the internet?",
- "I'm not convinced, why is this important?",
- "Summarize the differences in opinion between Eliezer Yudkowsky and Paul Christiano.",
- 'What are "RAAPs"?',
- 'What are "scaling laws" and how are they relevant to safety?',
- "What are some of the different research approaches?",
- "What are the differences between Inner and Outer alignment?",
- 'What does the term "x-risk" mean?',
- 'What is "FOOM"?',
- 'What is "instrumental convergence"?',
- "What is a hard takeoff?",
- "What is a mesa-optimizer?",
- "What is AI safety and alignment?",
- "What is an AI arms race?",
- "What is an Intelligence Explosion?",
- 'What is the "orthogonality thesis"?',
- 'Why would we expect AI to be "misaligned by default"?',
-];
-
const SearchBoxInternal: React.FC<{
search: (
query: string,
diff --git a/web/src/components/settings.tsx b/web/src/components/settings.tsx
index dfb6b60..2b3ffc5 100644
--- a/web/src/components/settings.tsx
+++ b/web/src/components/settings.tsx
@@ -2,134 +2,9 @@ import { ChangeEvent } from "react";
import TextareaAutosize from "react-textarea-autosize";
import type { Parseable, LLMSettings, Entry, Mode } from "../types";
+import { MODELS, ENCODERS } from "../hooks/useSettings";
import { SectionHeader, NumberInput, Slider } from "../components/html";
-type LLMSettingsParsers = {
- [key: string]:
- | ((v: number | undefined) => any)
- | ((v: string | undefined) => any)
- | ((v: object | undefined) => any);
-};
-
-const DEFAULT_PROMPTS = {
- context:
- "You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
- 'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
- "using the following sources. Each source is labeled with a letter. Feel free to " +
- "use the sources in any order, and try to use multiple sources in your answers.\n\n",
- history:
- "\n\n" +
- 'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
- "These sources only apply to the last question. any sources used in previous answers " +
- "are invalid.",
- question:
- "In your answer, please cite any claims you make back to each source " +
- "using the format: [a], [b], etc. If you use multiple sources to make a claim " +
- 'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
- modes: {
- default: "",
- concise:
- "Answer very concisely, getting to the crux of the matter in as " +
- "few words as possible. Limit your answer to 1-2 sentences.\n\n",
- rookie:
- "This user is new to the field of AI Alignment and Safety - don't " +
- "assume they know any technical terms or jargon. Still give a complete answer " +
- "without patronizing the user, but take any extra time needed to " +
- "explain new concepts or to illustrate your answer with examples. " +
- "Put extra effort into explaining the intuition behind concepts " +
- "rather than just giving a formal definition.\n\n",
- },
-};
-export const MODELS = {
- "gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
- "gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
- "gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
- /* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
-};
-export const ENCODERS = ["cl100k_base"];
-
-/** Update the given `obj` so that it has `val` at the given path.
- *
- * e.g.
- * updateIn({a: {b: 123}}, ['a', 'b', 'c'], 42) == {a: {b: 123, c: 42}}
- * updateIn({a: {b: 123}}, ['z', 'y', 'x'], 42) == {a: {b: 123}, z: {y: {x: 42}}}
- */
-export const updateIn = (
- obj: { [key: string]: any },
- [head, ...rest]: string[],
- val: any
-) => {
- if (!head) {
- // No path provided - do nothing
- } else if (!rest || rest.length == 0) {
- obj[head] = val;
- } else {
- if (obj[head] === undefined) {
- obj[head] = {};
- }
- updateIn(obj[head], rest, val);
- }
- return obj;
-};
-
-/** Create a settings object in which all items in the `overrides` object will be parsed appropriately
- *
- * `parsers` should be an object mapping settings fields to functions that will return a valid setting.
- * The parser functions should have default values that will be used if the provided value is undefined.
- */
-const parseSettings = (overrides: LLMSettings, parsers: LLMSettingsParsers) =>
- Object.entries(parsers).reduce(
- (settings, [key, parser]) =>
- updateIn(settings, [key], parser(overrides[key])),
- {}
- );
-
-/** Make a parser function from the provided `defaultVal`.
- *
- * If the parsed value is undefined, `defaultVal` will be returned, otherwise it will be parsed as
- * a value of the same type as `defaultVal`.
- * If `defaultVal` is an object, it will return a parser that will recursively search for appropriate keys.
- */
-const withDefault = (defaultVal: any) => {
- if (typeof defaultVal === "number" && defaultVal % 1 === 0) {
- return (v: string | undefined): number =>
- v !== undefined ? parseInt(v, 10) : defaultVal;
- } else if (typeof defaultVal === "number") {
- return (v: string | undefined): number =>
- v !== undefined ? parseFloat(v) : defaultVal;
- } else if (typeof defaultVal === "object") {
- const parsers = Object.entries(defaultVal).reduce(
- (parsers, [key, val]) => updateIn(parsers, [key], withDefault(val)),
- {}
- );
- return (v: object | undefined): object => parseSettings(v || {}, parsers);
- } else {
- return (v: any | undefined): any => v || defaultVal;
- }
-};
-
-const SETTINGS_PARSERS = {
- prompts: withDefault(DEFAULT_PROMPTS),
- mode: (v: string | undefined) => (v || "default") as Mode,
- completions: withDefault("gpt-3.5-turbo"),
- encoder: withDefault("cl100k_base"),
- topKBlocks: withDefault(MODELS["gpt-3.5-turbo"].topKBlocks), // the number of blocks to use as citations
- maxNumTokens: withDefault(MODELS["gpt-3.5-turbo"].maxNumTokens),
- tokensBuffer: withDefault(50), // the number of tokens to leave as a buffer when calculating remaining tokens
- maxHistory: withDefault(10), // the max number of previous items to use as history
- historyFraction: withDefault(0.25), // the (approximate) fraction of num_tokens to use for history text before truncating
- contextFraction: withDefault(0.5), // the (approximate) fraction of num_tokens to use for context text before truncating
-};
-
-export const makeSettings = (overrides: LLMSettings) =>
- parseSettings(
- Object.entries(overrides).reduce(
- (acc, [key, val]) => updateIn(acc, key.split("."), val),
- {}
- ),
- SETTINGS_PARSERS
- );
-
type ChatSettingsParams = {
settings: LLMSettings;
changeSetting: (path: string[], value: any) => void;
diff --git a/web/src/hooks/useCitations.ts b/web/src/hooks/useCitations.ts
new file mode 100644
index 0000000..4017859
--- /dev/null
+++ b/web/src/hooks/useCitations.ts
@@ -0,0 +1,47 @@
+import { useState } from "react";
+
+import type { CurrentSearch, Citation } from "../types";
+
+const updateCitations = (
+ allCitations: Citation[],
+ setCitations: (citations: Citation[]) => any,
+ entry?: CurrentSearch
+) => {
+ if (!entry) return entry;
+
+ const entryCitations = Array.from(entry.citationsMap.values());
+ if (!entryCitations.some((c) => !c.index)) {
+ // All of the entries citations have indexes, so there weren't any changes since the last check
+ return entry;
+ }
+
+ // Get a mapping of all known citations, so as to reuse them if they appear again
+ const citationsMapping = Object.fromEntries(
+ allCitations.map((c) => [c.title + c.url, c.index])
+ );
+
+ entryCitations.forEach((c) => {
+ const hash = c.title + c.url;
+ const index = citationsMapping[hash];
+ if (!index) {
+ c.index = allCitations.length + 1;
+ allCitations.push(c);
+ } else {
+ c.index = index;
+ }
+ });
+ setCitations(allCitations);
+ return entry;
+};
+
+export default function useCitations() {
+ const [citations, setCitations] = useState([]);
+
+ const setEntryCitations = (entry: CurrentSearch) =>
+ updateCitations(citations, setCitations, entry);
+
+ return {
+ citations,
+ setEntryCitations,
+ };
+}
diff --git a/web/src/hooks/useSearch.ts b/web/src/hooks/useSearch.ts
index de0a62a..9c7b166 100644
--- a/web/src/hooks/useSearch.ts
+++ b/web/src/hooks/useSearch.ts
@@ -57,16 +57,19 @@ export async function* iterateData(res: Response) {
}
}
-export const extractAnswer = async (
- res: Response,
- setCurrent: (e: CurrentSearch) => void
-): Promise => {
- var result: AssistantEntry = {
+const makeEntry = () =>
+ ({
role: "assistant",
content: "",
citations: [],
citationsMap: new Map(),
- };
+ } as AssistantEntry);
+
+export const extractAnswer = async (
+ res: Response,
+ setCurrent: (e: CurrentSearch) => void
+): Promise => {
+ var result: AssistantEntry = makeEntry();
var followups: Followup[] = [];
for await (var data of iterateData(res)) {
switch (data.state) {
@@ -135,6 +138,7 @@ export const queryLLM = async (
sessionId: string,
controller: AbortController
): Promise => {
+ setCurrent({ ...makeEntry(), phase: "started" });
// do SSE on a POST request.
const res = await fetchLLM(sessionId, query, settings, history, controller);
diff --git a/web/src/hooks/useSettings.ts b/web/src/hooks/useSettings.ts
new file mode 100644
index 0000000..e96b159
--- /dev/null
+++ b/web/src/hooks/useSettings.ts
@@ -0,0 +1,172 @@
+import { useRouter } from "next/router";
+import { useState, useEffect } from "react";
+
+import type { CurrentSearch, Mode, Entry, LLMSettings } from "../types";
+
+type LLMSettingsParsers = {
+ [key: string]:
+ | ((v: number | undefined) => any)
+ | ((v: string | undefined) => any)
+ | ((v: object | undefined) => any);
+};
+
+const DEFAULT_PROMPTS = {
+ context:
+ "You are a helpful assistant knowledgeable about AI Alignment and Safety. " +
+ 'Please give a clear and coherent answer to the user\'s questions.(written after "Q:") ' +
+ "using the following sources. Each source is labeled with a letter. Feel free to " +
+ "use the sources in any order, and try to use multiple sources in your answers.\n\n",
+ history:
+ "\n\n" +
+ 'Before the question ("Q: "), there will be a history of previous questions and answers. ' +
+ "These sources only apply to the last question. any sources used in previous answers " +
+ "are invalid.",
+ question:
+ "In your answer, please cite any claims you make back to each source " +
+ "using the format: [a], [b], etc. If you use multiple sources to make a claim " +
+ 'cite all of them. For example: "AGI is concerning [c, d, e]."\n\n',
+ modes: {
+ default: "",
+ concise:
+ "Answer very concisely, getting to the crux of the matter in as " +
+ "few words as possible. Limit your answer to 1-2 sentences.\n\n",
+ rookie:
+ "This user is new to the field of AI Alignment and Safety - don't " +
+ "assume they know any technical terms or jargon. Still give a complete answer " +
+ "without patronizing the user, but take any extra time needed to " +
+ "explain new concepts or to illustrate your answer with examples. " +
+ "Put extra effort into explaining the intuition behind concepts " +
+ "rather than just giving a formal definition.\n\n",
+ },
+};
+export const MODELS = {
+ "gpt-3.5-turbo": { maxNumTokens: 4095, topKBlocks: 10 },
+ "gpt-3.5-turbo-16k": { maxNumTokens: 16385, topKBlocks: 30 },
+ "gpt-4": { maxNumTokens: 8192, topKBlocks: 20 },
+ /* 'gpt-4-32k': {maxNumTokens: 32768, topKBlocks: 30}, */
+};
+export const ENCODERS = ["cl100k_base"];
+
+/** Update the given `obj` so that it has `val` at the given path.
+ *
+ * e.g.
+ * updateIn({a: {b: 123}}, ['a', 'b', 'c'], 42) == {a: {b: 123, c: 42}}
+ * updateIn({a: {b: 123}}, ['z', 'y', 'x'], 42) == {a: {b: 123}, z: {y: {x: 42}}}
+ */
+export const updateIn = (
+ obj: { [key: string]: any },
+ [head, ...rest]: string[],
+ val: any
+) => {
+ if (!head) {
+ // No path provided - do nothing
+ } else if (!rest || rest.length == 0) {
+ obj[head] = val;
+ } else {
+ if (obj[head] === undefined) {
+ obj[head] = {};
+ }
+ updateIn(obj[head], rest, val);
+ }
+ return obj;
+};
+
+/** Create a settings object in which all items in the `overrides` object will be parsed appropriately
+ *
+ * `parsers` should be an object mapping settings fields to functions that will return a valid setting.
+ * The parser functions should have default values that will be used if the provided value is undefined.
+ */
+const parseSettings = (overrides: LLMSettings, parsers: LLMSettingsParsers) =>
+ Object.entries(parsers).reduce(
+ (settings, [key, parser]) =>
+ updateIn(settings, [key], parser(overrides[key])),
+ {}
+ );
+
+/** Make a parser function from the provided `defaultVal`.
+ *
+ * If the parsed value is undefined, `defaultVal` will be returned, otherwise it will be parsed as
+ * a value of the same type as `defaultVal`.
+ * If `defaultVal` is an object, it will return a parser that will recursively search for appropriate keys.
+ */
+const withDefault = (defaultVal: any) => {
+ if (typeof defaultVal === "number" && defaultVal % 1 === 0) {
+ return (v: string | undefined): number =>
+ v !== undefined ? parseInt(v, 10) : defaultVal;
+ } else if (typeof defaultVal === "number") {
+ return (v: string | undefined): number =>
+ v !== undefined ? parseFloat(v) : defaultVal;
+ } else if (typeof defaultVal === "object") {
+ const parsers = Object.entries(defaultVal).reduce(
+ (parsers, [key, val]) => updateIn(parsers, [key], withDefault(val)),
+ {}
+ );
+ return (v: object | undefined): object => parseSettings(v || {}, parsers);
+ } else {
+ return (v: any | undefined): any => v || defaultVal;
+ }
+};
+
+const SETTINGS_PARSERS = {
+ prompts: withDefault(DEFAULT_PROMPTS),
+ mode: (v: string | undefined) => (v || "default") as Mode,
+ completions: withDefault("gpt-3.5-turbo"),
+ encoder: withDefault("cl100k_base"),
+ topKBlocks: withDefault(MODELS["gpt-3.5-turbo"].topKBlocks), // the number of blocks to use as citations
+ maxNumTokens: withDefault(MODELS["gpt-3.5-turbo"].maxNumTokens),
+ tokensBuffer: withDefault(50), // the number of tokens to leave as a buffer when calculating remaining tokens
+ maxHistory: withDefault(10), // the max number of previous items to use as history
+ historyFraction: withDefault(0.25), // the (approximate) fraction of num_tokens to use for history text before truncating
+ contextFraction: withDefault(0.5), // the (approximate) fraction of num_tokens to use for context text before truncating
+};
+
+export const makeSettings = (overrides: LLMSettings) =>
+ parseSettings(
+ Object.entries(overrides).reduce(
+ (acc, [key, val]) => updateIn(acc, key.split("."), val),
+ {}
+ ),
+ SETTINGS_PARSERS
+ );
+
+type ChatSettingsParams = {
+ settings: LLMSettings;
+ changeSetting: (path: string[], value: any) => void;
+};
+
+export default function useSettings() {
+ const [settings, updateSettings] = useState(makeSettings({}));
+ const router = useRouter();
+
+ const updateInUrl = (path: string[], value: any) =>
+ router.replace({
+ pathname: router.pathname,
+ query: {
+ ...router.query,
+ [path.join(".")]: value.toString(),
+ },
+ });
+
+ const changeSetting = (path: string[], value: any) => {
+ updateInUrl(path, value);
+ updateSettings((settings) => ({ ...updateIn(settings, path, value) }));
+ };
+
+ const setMode = (mode: Mode | undefined) => {
+ if (mode) {
+ updateSettings({ ...settings, mode: mode });
+ localStorage.setItem("chat_mode", mode);
+ }
+ };
+
+ useEffect(() => {
+ const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
+ updateSettings(makeSettings({ ...router.query, mode: mode }));
+ }, [updateSettings, router]);
+
+ return {
+ settings,
+ changeSetting,
+ setMode,
+ };
+}
diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx
index 41482d1..d1f9071 100644
--- a/web/src/pages/index.tsx
+++ b/web/src/pages/index.tsx
@@ -3,6 +3,7 @@ import { useState, useEffect } from "react";
import Link from "next/link";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
+import useSettings from "../hooks/useSettings";
import type { Mode } from "../types";
import Page from "../components/page";
import Chat from "../components/chat";
@@ -12,23 +13,16 @@ const MAX_FOLLOWUPS = 4;
const Home: NextPage = () => {
const [sessionId, setSessionId] = useState("");
- const [mode, setMode] = useState<[Mode, boolean]>(["default", false]);
-
- // store mode in localstorage
- useEffect(() => {
- if (mode[1]) localStorage.setItem("chat_mode", mode[0]);
- }, [mode]);
+ const { settings, setMode } = useSettings();
// initial load
useEffect(() => {
- const mode = (localStorage.getItem("chat_mode") as Mode) || "default";
- setMode([mode, true]);
setSessionId(crypto.randomUUID());
}, []);
return (
-
+
WARNING: This is a very early prototype.{" "}
@@ -38,7 +32,7 @@ const Home: NextPage = () => {
welcomed.
+
);
};
diff --git a/web/src/settings.ts b/web/src/settings.ts
index 5dd7442..e1aa14a 100644
--- a/web/src/settings.ts
+++ b/web/src/settings.ts
@@ -3,3 +3,27 @@ export const API_URL =
export const STAMPY_URL = process.env.STAMPY_URL || "https://aisafety.info";
export const STAMPY_CONTENT_URL =
process.env.STAMPY_CONTENT_URL || `${API_URL}/human`;
+
+// initial questions to fill the search box with.
+export const initialQuestions: string[] = [
+ "Are there any regulatory efforts aimed at addressing AI safety and alignment concerns?",
+ "How can I help with AI safety and alignment?",
+ "How could a predictive model - like an LLM - act like an agent?",
+ "How could an AI possibly be an x-risk when some populations aren't even connected to the internet?",
+ "I'm not convinced, why is this important?",
+ "Summarize the differences in opinion between Eliezer Yudkowsky and Paul Christiano.",
+ 'What are "RAAPs"?',
+ 'What are "scaling laws" and how are they relevant to safety?',
+ "What are some of the different research approaches?",
+ "What are the differences between Inner and Outer alignment?",
+ 'What does the term "x-risk" mean?',
+ 'What is "FOOM"?',
+ 'What is "instrumental convergence"?',
+ "What is a hard takeoff?",
+ "What is a mesa-optimizer?",
+ "What is AI safety and alignment?",
+ "What is an AI arms race?",
+ "What is an Intelligence Explosion?",
+ 'What is the "orthogonality thesis"?',
+ 'Why would we expect AI to be "misaligned by default"?',
+];
diff --git a/web/src/types.ts b/web/src/types.ts
index 1b4dc5a..e751c66 100644
--- a/web/src/types.ts
+++ b/web/src/types.ts
@@ -23,7 +23,7 @@ export type UserEntry = {
export type AssistantEntry = {
role: "assistant";
content: string;
- citations: Citation[];
+ citations?: Citation[];
citationsMap: Map;
deleted?: boolean;
};