{"flow":{"id":46,"summary":"Generate answer from embeddings with intermediate approval step","versions":[180,181,182],"created_by":"faton ramadani2","created_at":"2023-06-07T13:53:49.991Z","votes":0,"approved":false,"apps":["supabase","openai","discord"],"value":{"modules":[{"id":"b","value":{"lock":"","type":"rawscript","content":"export async function main(interaction: any) {\n  return interaction?.data?.options?.[0]?.value ?? \"No question asked\";\n}\n","language":"deno","input_transforms":{"interaction":{"expr":"flow_input.interaction","type":"javascript"}}},"summary":"Extract Discord Question"},{"id":"c","value":{"lock":"","type":"rawscript","content":"import * as wmill from \"https://deno.land/x/windmill@v1.104.2/mod.ts\";\n\nimport { refreshAndRetryIfExpired } from \"https://deno.land/x/windmill_helpers@v1.1.1/mod.ts\";\nimport { Configuration, OpenAIApi } from \"npm:openai@3.2.1\";\nimport { stripIndent } from \"https://esm.sh/common-tags@1.8.2\";\nimport GPT3Tokenizer from \"https://esm.sh/gpt3-tokenizer@1.1.5\";\n\nexport async function main(\n  query: string,\n  supabaseAuth: wmill.Resource<\"supabase\">,\n  openAiAuth: wmill.Resource<\"openai\">,\n  token?: {\n    access: string;\n    refresh: string;\n    expires_at?: number;\n  },\n  maxToken: number = 5000,\n) {\n  let answer = \"\";\n  const links: string[] = [];\n\n  await refreshAndRetryIfExpired(supabaseAuth, token, async (client) => {\n    // OpenAI recommends replacing newlines with spaces for best results\n    const input = query.replace(/\\n/g, \" \");\n\n    const configuration = new Configuration({\n      apiKey: openAiAuth.api_key,\n      organization: openAiAuth.organization_id,\n    });\n    const openai = new OpenAIApi(configuration);\n\n    // Generate a one-time embedding for the query itself\n    const embeddingResponse = await openai.createEmbedding({\n      model: \"text-embedding-ada-002\",\n      input,\n    });\n\n    const [{ embedding }] = embeddingResponse.data.data;\n    const { data: documents } = await client.rpc(\"match_documents\", {\n      query_embedding: embedding,\n      match_threshold: 0.5, // Choose an appropriate threshold for your data\n      match_count: 5, // Choose the number of matches\n    });\n\n    const tokenizer = new GPT3Tokenizer({ type: \"gpt3\" });\n    let tokenCount = 0;\n\n    let contextText = \"\";\n\n    for (let i = 0; i < documents.length; i++) {\n      const document = documents[i];\n      const content = document.content;\n\n      const encoded = tokenizer.encode(content);\n      tokenCount += encoded.text.length;\n\n      if (tokenCount > maxToken) {\n        contextText += `${content.trim().substring(0, maxToken)}\\n---\\n`;\n        links.push(document.link);\n        break;\n      }\n\n      contextText += `${content.trim()}\\n---\\n`;\n      links.push(document.link);\n    }\n\n    const prompt = stripIndent`\n\t\tYou are an AI assistant providing helpful advice. You are given the following extracted parts of a long document and a question. Provide a conversational answer based on the context provided.\n    If you can't find the answer in the context below, just say \"Sorry, I don't know\" Don't try to make up an answer.\n    Please note that images, figures, or any visual content will not be included in the provided context, so you must not try to include them in an answer.\n    \n    We will provide both the context and the question at the end.\n    Answer as markdown (including related code snippets if available).\n\n    Context:\n    ${contextText}\n    Question: \"\"\"\n    ${query}\n    \"\"\"\n  `;\n\n    try {\n      const completionResponse = await openai.createChatCompletion({\n        model: \"gpt-4\",\n        messages: [{ role: \"user\", content: prompt }],\n        max_tokens: 512, // Choose the max allowed tokens in completion\n        temperature: 0, // Set to 0 for deterministic results\n      });\n      const {\n        id,\n        choices: [{ message }],\n      } = completionResponse.data;\n\n      answer = message?.content ?? \"\";\n    } catch (e) {\n      console.log(e);\n    }\n  });\n\n  return { answer, links };\n}\n","language":"deno","input_transforms":{"query":{"expr":"results.b","type":"javascript"},"token":{"type":"static","value":{"access":"","refresh":""}},"maxToken":{"type":"static","value":5000},"openAiAuth":{"type":"static","value":null},"supabaseAuth":{"type":"static","value":null}}},"summary":"Create answer"},{"id":"e","value":{"lock":"","path":"hub/859/windmill/return_resume_and_cancel_endpoints","type":"rawscript","content":"import * as wmill from \"https://deno.land/x/windmill@v1.85.0/mod.ts\";\nimport { REST } from \"npm:@discordjs/rest@1.7.1\";\nimport { API, ButtonStyle, MessageFlags } from \"npm:@discordjs/core@0.6.0\";\nimport { ActionRowBuilder, ButtonBuilder } from \"npm:@discordjs/builders@1.6.3\";\n\nexport async function main(\n  question: string,\n  answer: string,\n  links: string[],\n  token: string,\n  interaction: any,\n) {\n  const rest = new REST({ version: \"10\" }).setToken(token);\n  const api = new API(rest);\n  const { resume, cancel } = await wmill.getResumeUrls(\"Faton\");\n\n  const confirmButton = new ButtonBuilder()\n    .setLabel(\"Confirm Message\")\n    .setURL(resume)\n    .setStyle(ButtonStyle.Link);\n\n  const row = new ActionRowBuilder<ButtonBuilder>()\n    .addComponents(confirmButton);\n\n  await api.interactions.editReply(\n    interaction.application_id,\n    interaction.token,\n    {\n      content: `## ${question}\\n\\n${answer}\\n## Sources:\\n${\n        links.map((l) => `${l}\\n`).join(\"\")\n      }`,\n      components: [row.toJSON()],\n      flags: MessageFlags.Ephemeral,\n    },\n  );\n}\n","language":"deno","input_transforms":{"links":{"expr":"results.c.links","type":"javascript"},"token":{"type":"static","value":""},"answer":{"expr":"results.c.answer","type":"javascript"},"question":{"expr":"results.b","type":"javascript"},"interaction":{"expr":"flow_input.interaction","type":"javascript"}}},"summary":"Approval step","suspend":{"timeout":1800,"required_events":1}},{"id":"d","value":{"lock":"","type":"rawscript","content":"import { REST } from \"npm:@discordjs/rest@1.7.1\";\nimport { API } from \"npm:@discordjs/core@0.6.0\";\n\ntype DiscordInteraction = {\n  application_id: string;\n  token: string;\n};\n\nexport async function main(\n  interaction: DiscordInteraction,\n  question: string,\n  answer: string,\n  links: string[],\n  token: string,\n) {\n  const rest = new REST({\n    version: \"10\",\n  }).setToken(token);\n  const api = new API(rest);\n\n  await api.interactions.followUp(\n    interaction.application_id,\n    interaction.token,\n    {\n      content: `## ${question}\\n\\n${answer}\\n## Sources:\\n${\n        links.map((l) => `${l}\\n`).join(\"\")\n      }`,\n    },\n  );\n}\n","language":"deno","input_transforms":{"links":{"expr":"results.c.links","type":"javascript"},"token":{"type":"static","value":""},"answer":{"expr":"results.c.answer","type":"javascript"},"question":{"expr":"results.b","type":"javascript"},"interaction":{"expr":"flow_input.interaction","type":"javascript"}}},"summary":"Reply"}]},"schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":[],"properties":{"interaction":{"type":"object","format":"","default":"","description":""}}},"description":"This is a Discord bot flow that uses OpenAI's GPT-4 to answer user queries. It involves question extraction, answer generation given embeddings stored on Supabase, an approval step, and then a reply to the user's query on Discord.","recording":null,"vcreated_at":"2023-06-08T15:02:24.995Z","vcreated_by":"faton ramadani2","comments":[]}}