diff --git a/api/chat.py b/api/chat.py index 81dcae8..db32d46 100644 --- a/api/chat.py +++ b/api/chat.py @@ -48,7 +48,7 @@ def cap(text: str, max_tokens: int) -> str: -def construct_prompt(query: str, history: List[Dict[str, str]], context: List[Block]) -> List[Dict[str, str]]: +def construct_prompt(query: str, mode: str, history: List[Dict[str, str]], context: List[Block]) -> List[Dict[str, str]]: prompt = [] @@ -114,7 +114,24 @@ def construct_prompt(query: str, history: List[Dict[str, str]], context: List[Bl question_prompt = "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\nQ: " + query + "cite all of them. For example: \"AGI is concerning [c, d, e].\"\n\n" + + if mode == "concise": + question_prompt += "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" + + elif mode == "rookie": + question_prompt += "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" + + elif mode != "default": raise ValueError("Invalid mode: " + mode) + + + question_prompt += "Q: " + query prompt.append({"role": "user", "content": question_prompt}) @@ -124,7 +141,7 @@ def construct_prompt(query: str, history: List[Dict[str, str]], context: List[Bl import time import json -def talk_to_robot_internal(index, query: str, history: List[Dict[str, str]], k: int = STANDARD_K, log: Callable = print): +def talk_to_robot_internal(index, query: str, mode: str, history: List[Dict[str, str]], k: int = STANDARD_K, log: Callable = print): try: # 1. Find the most relevant blocks from the Alignment Research Dataset yield {"state": "loading", "phase": "semantic"} @@ -134,7 +151,7 @@ def talk_to_robot_internal(index, query: str, history: List[Dict[str, str]], k: # 2. Generate a prompt yield {"state": "loading", "phase": "prompt"} - prompt = construct_prompt(query, history, top_k_blocks) + prompt = construct_prompt(query, mode, history, top_k_blocks) # 3. Count number of tokens left for completion (-50 for a buffer) max_tokens_completion = NUM_TOKENS - sum([len(ENCODER.encode(message["content"]) + ENCODER.encode(message["role"])) for message in prompt]) - 50 @@ -187,14 +204,14 @@ def talk_to_robot_internal(index, query: str, history: List[Dict[str, str]], k: yield {'state': 'error', 'error': str(e)} # convert talk_to_robot_internal from dict generator into json generator -def talk_to_robot(index, query: str, history: List[Dict[str, str]], k: int = STANDARD_K, log: Callable = print): - yield from (json.dumps(block) for block in talk_to_robot_internal(index, query, history, k, log)) +def talk_to_robot(index, query: str, mode: str, history: List[Dict[str, str]], k: int = STANDARD_K, log: Callable = print): + yield from (json.dumps(block) for block in talk_to_robot_internal(index, query, mode, history, k, log)) # wayyy simplified api def talk_to_robot_simple(index, query: str, log: Callable = print): res = {'response': ''} - for block in talk_to_robot_internal(index, query, [], log = log): + for block in talk_to_robot_internal(index, query, "default", [], log = log): if block['state'] == 'loading' and block['phase'] == 'semantic' and 'citations' in block: citations = {} for i, c in enumerate(block['citations']): diff --git a/api/main.py b/api/main.py index ec664b0..88c079e 100644 --- a/api/main.py +++ b/api/main.py @@ -41,9 +41,10 @@ def semantic(): def chat(): query = request.json['query'] + mode = request.json['mode'] history = request.json['history'] - return Response(stream(talk_to_robot(PINECONE_INDEX, query, history, log = log)), mimetype='text/event-stream') + return Response(stream(talk_to_robot(PINECONE_INDEX, query, mode, history, log = log)), mimetype='text/event-stream') # ------------- simplified non-streaming chat for internal testing ------------- diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index 1a0a168..a9f4114 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -3,7 +3,7 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:3000"; import Head from "next/head"; import React from "react"; import { type NextPage } from "next"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Link from "next/link"; import Image from 'next/image'; @@ -242,6 +242,8 @@ type State = { response: AssistantEntry; }; +type Mode = "rookie" | "concise" | "default"; + // smooth-scroll to the bottom of the window if we're already less than 30% a screen away // note: finicky interaction with "smooth" - maybe fix later. @@ -256,6 +258,21 @@ const Home: NextPage = () => { const [ runningIndex, setRunningIndex ] = useState(0); const [ loadState, setLoadState ] = useState({state: "idle"}); + // [state, ready to save to localstorage] + const [ mode, setMode ] = useState<[Mode, boolean]>(["default", false]); + + // store mode in localstorage + useEffect(() => { + if (mode[1]) localStorage.setItem("chat_mode", mode[0]); + }, [mode]); + + // initial load + useEffect(() => { + const mode = localStorage.getItem("chat_mode") as Mode || "default"; + setMode([mode, true]); + }, []); + + const search = async ( query: string, query_source: "search" | "followups", @@ -287,7 +304,7 @@ const Home: NextPage = () => { "Allow-Control-Allow-Origin": "*" }, - body: JSON.stringify({query: query, history: + body: JSON.stringify({query: query, mode: mode[0], history: old_entries.filter((entry) => entry.role !== "error") .map((entry) => { return { @@ -434,7 +451,7 @@ const Home: NextPage = () => { const data = (await res.json()).data; setEntries([...new_entries, { - role: "stampy", + role: "stampy", content: data.text, url: "https://aisafety.info/?state=" + data.pageid, }]); @@ -453,7 +470,7 @@ const Home: NextPage = () => { enable((f_old: Followup[]) => { const f_old_filtered = f_old.filter((f) => f.pageid !== data.pageid && !fpids.has(f.pageid)); - return [...f_new, ...f_old_filtered].slice(0, MAX_FOLLOWUPS); // this is correct, it's N and not N-1 in javascript fsr + return [...f_new, ...f_old_filtered].slice(0, MAX_FOLLOWUPS); // this is correct, it's N and not N-1 in javascript fsr }); scroll30(); @@ -467,8 +484,38 @@ const Home: NextPage = () => {
+ {/* three buttons for the three modes, place far right, 1rem between each */} +
+ + // + + // + +
+ +

WARNING: This is a very early prototype using data through June 2022. Feedback welcomed.

+
    {entries.map((entry, i) => { switch (entry.role) {