diff --git a/api/main.py b/api/main.py
index 022ab24..c24e81b 100644
--- a/api/main.py
+++ b/api/main.py
@@ -44,20 +44,19 @@ def semantic():
@app.route('/chat', methods=['POST'])
@cross_origin()
def chat():
-
query = request.json.get('query')
session_id = request.json.get('sessionId')
history = request.json.get('history', [])
- settings = Settings(**request.json.get('settings', {}))
-
- def run(callback):
- return run_query(session_id, query, history, settings, callback)
+ settings = request.json.get('settings', {})
def formatter(item):
if isinstance(item, Exception):
item = {'state': 'error', 'error': str(item)}
return json.dumps(item)
+ def run(callback):
+ return run_query(session_id, query, history, Settings(**settings), callback)
+
return Response(stream_with_context(stream(stream_callback(run, formatter))), mimetype='text/event-stream')
diff --git a/api/src/stampy_chat/chat.py b/api/src/stampy_chat/chat.py
index a88c127..1471595 100644
--- a/api/src/stampy_chat/chat.py
+++ b/api/src/stampy_chat/chat.py
@@ -191,7 +191,12 @@ def run_query(session_id: str, query: str, history: List[Dict], settings: Settin
callbacks = [LoggerCallbackHandler(session_id=session_id, query=query, history=history)]
if callback:
callbacks += [BroadcastCallbackHandler(callback)]
- chat_model = get_model(streaming=True, callbacks=callbacks, max_tokens=settings.max_response_tokens)
+ chat_model = get_model(
+ streaming=True,
+ callbacks=callbacks,
+ max_tokens=settings.max_response_tokens,
+ model=settings.completions
+ )
chain = LLMChain(
llm=chat_model,
diff --git a/web/src/components/html.tsx b/web/src/components/html.tsx
index 8312bb7..35e0a00 100644
--- a/web/src/components/html.tsx
+++ b/web/src/components/html.tsx
@@ -1,8 +1,22 @@
+import { ChangeEvent } from "react";
+import type { Parseable } from "../types";
+
// const Colours = ["blue", "cyan", "teal", "green", "amber"].map(
// colour => `bg-${colour}-100 border-${colour}-300 text-${colour}-800`
// );
// this would be nice, but Tailwind needs te actual string of the class to be in
// the source file for it to be included in the build
+type NumberParser = (v: Parseable) => number;
+type InputFields = {
+ field: string;
+ label: string;
+ value?: Parseable;
+ min?: string | number;
+ max?: string | number;
+ step?: string | number;
+ parser?: NumberParser;
+ updater: (v: any) => any;
+};
export const Colours = [
"bg-red-100 border-red-300 text-red-800",
@@ -30,3 +44,78 @@ export const A: React.FC<{
{children}
);
};
+
+const between =
+ (
+ min: Parseable,
+ max: Parseable,
+ parser: NumberParser,
+ updater: (v: any) => any
+ ) =>
+ (event: ChangeEvent) => {
+ let num = parser((event.target as HTMLInputElement).value);
+ if (isNaN(num)) {
+ return;
+ } else if (min !== undefined && num < parser(min)) {
+ num = parser(min);
+ } else if (max !== undefined && num > parser(max)) {
+ num = parser(max);
+ }
+ updater(num);
+ };
+
+export const SectionHeader = ({ text }: { text: string }) => (
+
{text}
+);
+
+export const NumberInput = ({
+ field,
+ value,
+ label,
+ min,
+ max,
+ updater,
+ // this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
+ parser = (v) => parseInt(v as string, 10),
+}: InputFields) => (
+ <>
+
+
+ >
+);
+
+export const Slider = ({
+ field,
+ value,
+ label,
+ min = 0,
+ max = 1,
+ step = 0.01,
+ // this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
+ parser = (v) => parseFloat(v as string),
+ updater,
+}: InputFields) => (
+ <>
+
+
+ >
+);
diff --git a/web/src/components/settings.tsx b/web/src/components/settings.tsx
new file mode 100644
index 0000000..dfb6b60
--- /dev/null
+++ b/web/src/components/settings.tsx
@@ -0,0 +1,326 @@
+import { ChangeEvent } from "react";
+import TextareaAutosize from "react-textarea-autosize";
+
+import type { Parseable, LLMSettings, Entry, Mode } from "../types";
+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;
+};
+
+export const ChatSettings = ({
+ settings,
+ changeSetting,
+}: ChatSettingsParams) => {
+ const changeVal = (field: string, value: any) =>
+ changeSetting([field], value);
+ const update = (field: string) => (event: ChangeEvent) =>
+ changeVal(field, (event.target as HTMLInputElement).value);
+ const updateNum = (field: string) => (num: Parseable) =>
+ changeVal(field, num);
+
+ return (
+
+ );
+};
diff --git a/web/src/pages/playground.tsx b/web/src/pages/playground.tsx
index bb04f1d..8551d87 100644
--- a/web/src/pages/playground.tsx
+++ b/web/src/pages/playground.tsx
@@ -1,373 +1,29 @@
import type { NextPage } from "next";
-import { useState, useEffect, ChangeEvent } from "react";
-import TextareaAutosize from "react-textarea-autosize";
+import { useRouter } from "next/router";
+import { useState, useEffect } from "react";
import Head from "next/head";
-import Link from "next/link";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import type { Mode, Entry, LLMSettings } from "../types";
import Header from "../components/header";
import Chat from "../components/chat";
import { Controls } from "../components/controls";
-
-const MAX_FOLLOWUPS = 4;
-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",
- },
-};
-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}, */
-};
-const DEFAULT_SETTINGS = {
- prompts: DEFAULT_PROMPTS,
- mode: "default" as Mode,
- completions: "gpt-3.5-turbo",
- encoder: "cl100k_base",
- topKBlocks: MODELS["gpt-3.5-turbo"].topKBlocks, // the number of blocks to use as citations
- maxNumTokens: MODELS["gpt-3.5-turbo"].maxNumTokens,
- tokensBuffer: 50, // the number of tokens to leave as a buffer when calculating remaining tokens
- maxHistory: 10, // the max number of previous items to use as history
- historyFraction: 0.25, // the (approximate) fraction of num_tokens to use for history text before truncating
- contextFraction: 0.5, // the (approximate) fraction of num_tokens to use for context text before truncating
-};
-const ENCODERS = ["cl100k_base"];
-
-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 {
- updateIn(obj[head], rest, val);
- }
- return obj;
-};
-
-type Parseable = string | number | undefined;
-type NumberParser = (v: Parseable) => number;
-type InputFields = {
- field: string;
- label: string;
- value?: Parseable;
- min?: string | number;
- max?: string | number;
- step?: string | number;
- parser?: NumberParser;
- updater: (v: any) => any;
-};
-
-const between =
- (
- min: Parseable,
- max: Parseable,
- parser: NumberParser,
- updater: (v: any) => any
- ) =>
- (event: ChangeEvent) => {
- let num = parser((event.target as HTMLInputElement).value);
- if (isNaN(num)) {
- return;
- } else if (min !== undefined && num < parser(min)) {
- num = parser(min);
- } else if (max !== undefined && num > parser(max)) {
- num = parser(max);
- }
- updater(num);
- };
-
-const SectionHeader = ({ text }: { text: string }) => (
-
{text}
-);
-
-const NumberInput = ({
- field,
- value,
- label,
- min,
- max,
- updater,
- // this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
- parser = (v) => parseInt(v as string, 10),
-}: InputFields) => (
- <>
-
-
- >
-);
-
-const Slider = ({
- field,
- value,
- label,
- min = 0,
- max = 1,
- step = 0.01,
- // this cast is just to satisfy typescript - it can handle numbers, strings and undefined just fine
- parser = (v) => parseFloat(v as string),
- updater,
-}: InputFields) => (
- <>
-
-
- >
-);
-
-type ChatSettingsParams = {
- settings: LLMSettings;
- updateSettings: (updater: (settings: LLMSettings) => LLMSettings) => void;
-};
-
-const ChatSettings = ({ settings, updateSettings }: ChatSettingsParams) => {
- const changeVal = (field: string, value: any) =>
- updateSettings((prev) => ({ ...prev, [field]: value }));
- const update = (setting: string) => (event: ChangeEvent) => {
- changeVal(setting, (event.target as HTMLInputElement).value);
- };
- const updateNum = (field: string) => (num: Parseable) =>
- changeVal(field, num);
-
- return (
-