cody-slack: #ask-cody context and GPT-4 streaming (#51194)

⚠️ This PR changes code only inside of the `cody-slack` package. All the
other client packages are untouched.

I'll be moving Cody Slack to GCP, so I need to merge the PR with
[functionality](https://sourcegraph.slack.com/archives/C89KCDK5J/p1682506053493149)
before that:

- [#ask-cody](https://sourcegraph.slack.com/archives/C04MSD3DP5L)
Special Context: Struggling to find info on Cody across various sources?
Worry no more! When you ask
[@cody_dev](https://sourcegraph.slack.com/team/U051K8MBM7F) a question
in the [#ask-cody](https://sourcegraph.slack.com/archives/C04MSD3DP5L)
channel, it now searches Cody-notice, developer docs, the handbook, and
the sg/sg codebase to provide the best possible answer. 🔍
- Files Used Section:
[@cody_dev](https://sourcegraph.slack.com/team/U051K8MBM7F) will now
share links to all the files it "used" while answering your questions.
This means you can easily verify the information and explore related
resources! 📁
- Slack Markdown Support: Answers are now beautifully formatted and
compatible with GitHub-flavored markdown. Enjoy a more readable and
visually appealing experience! 
- Powered by GPT-4: I've updated
[@cody_dev](https://sourcegraph.slack.com/team/U051K8MBM7F) to use GPT-4
for better reasoning capabilities and an enhanced understanding of Slack
conversations. Get ready for more accurate and insightful answers!
This commit is contained in:
Valery Bugakov 2023-05-22 05:14:16 -07:00 committed by GitHub
parent 47d37bac26
commit 753ef33f15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1059 additions and 180 deletions

View File

@ -9,6 +9,7 @@ module.exports = {
},
overrides: baseConfig.overrides,
rules: {
'ban/ban': 'off',
'id-length': 'off',
'no-console': 'off',
'no-restricted-imports': [

View File

@ -26,6 +26,8 @@ ts_project(
"src/constants.ts",
"src/mention-handler.ts",
"src/services/codebase-context.ts",
"src/services/github-client.ts",
"src/services/local-vector-store.ts",
"src/services/openai-completions-client.ts",
"src/services/sourcegraph-client.ts",
"src/services/stream-completions.ts",
@ -39,10 +41,13 @@ ts_project(
":node_modules/@slack/bolt",
":node_modules/@slack/web-api",
":node_modules/@sourcegraph/cody-shared",
":node_modules/langchain",
":node_modules/openai",
":node_modules/slackify-markdown",
"//:node_modules/@types/lodash",
"//:node_modules/envalid",
"//:node_modules/lodash",
"//:node_modules/octokit",
],
)

View File

@ -9,15 +9,21 @@
"start": "ts-node-transpile-only ./src/app.ts",
"lint": "pnpm run lint:js",
"lint:js": "eslint --cache '**/*.[tj]s?(x)'",
"build": "esbuild ./src/app.ts --bundle --outfile=dist/app.js --format=cjs --platform=node",
"build": "esbuild ./src/app.ts --bundle --outfile=dist/app.js --external:hnswlib-node --format=cjs --platform=node",
"build-ts": "tsc -b --emitDeclarationOnly",
"release": "pnpm run build && cd dist && git add . && git commit -m wip && git push heroku master"
"release": "pnpm run build && cd dist && git add . && git commit -m wip && git push heroku master",
"build:gcp": "esbuild ./src/app.ts --bundle --outfile=package/app.js --external:hnswlib-node --format=cjs --platform=node",
"release:gcp": "pnpm run build && cd package && git add . && git commit -m wip && git push gcp main"
},
"dependencies": {
"@slack/bolt": "^3.12.2",
"@slack/web-api": "^6.8.1",
"@sourcegraph/cody-shared": "workspace:*",
"@sourcegraph/common": "workspace:*",
"openai": "^3.2.1"
"axios": "^1.3.6",
"hnswlib-node": "^1.4.2",
"langchain": "^0.0.61",
"openai": "^3.2.1",
"slackify-markdown": "^4.3.1"
}
}

View File

@ -0,0 +1,14 @@
{
"name": "@sourcegraph/cody-slack",
"private": true,
"displayName": "Sourcegraph Cody Slack",
"version": "0.0.1",
"license": "Apache-2.0",
"description": "Your programming sidekick powered by AI and Sourcegraph's code search and intelligence.",
"scripts": {
"start": "node ./app.js"
},
"dependencies": {
"hnswlib-node": "^1.4.2"
}
}

View File

@ -1,19 +1,31 @@
import { ENVIRONMENT_CONFIG, DEFAULT_APP_SETTINGS } from './constants'
import { ENVIRONMENT_CONFIG, DEFAULT_CODEBASES, AppContext, DEFAULT_APP_SETTINGS } from './constants'
import { handleHumanMessage } from './mention-handler'
import { createCodebaseContext } from './services/codebase-context'
import { getVectorStore } from './services/local-vector-store'
import { isBotEvent } from './slack/helpers'
import { app } from './slack/init'
const { PORT } = ENVIRONMENT_CONFIG
async function createAppContext() {
// Init codebase context clients for specified Slack channels.
const appContext = { codebaseContexts: {} } as AppContext
for (const codebase of DEFAULT_CODEBASES) {
appContext.codebaseContexts[codebase] = await createCodebaseContext(
codebase,
DEFAULT_APP_SETTINGS.contextType,
DEFAULT_APP_SETTINGS.serverEndpoint
)
}
appContext.vectorStore = await getVectorStore()
return appContext
}
// Main function to start the bot
async function startBot() {
// Create a context for the codebase using the default app settings
const codebaseContext = await createCodebaseContext(
DEFAULT_APP_SETTINGS.codebase,
DEFAULT_APP_SETTINGS.contextType,
DEFAULT_APP_SETTINGS.serverEndpoint
)
const appContext = await createAppContext()
// Listen for mentions in the Slack app
app.event<'app_mention'>('app_mention', async ({ event }) => {
@ -22,9 +34,9 @@ async function startBot() {
return
}
console.log('APP_MENTION', event.text)
console.log('APP_MENTION:', event.text)
// Process the mention event generated by a human user
await handleHumanMessage(event, codebaseContext)
await handleHumanMessage(event, appContext)
})
// Start the Slack app on the specified port

View File

@ -1,4 +1,7 @@
import { cleanEnv, str, num } from 'envalid'
import { HNSWLib } from 'langchain/vectorstores/hnswlib'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
export const ENVIRONMENT_CONFIG = cleanEnv(process.env, {
PORT: num({ default: 3000 }),
@ -6,14 +9,27 @@ export const ENVIRONMENT_CONFIG = cleanEnv(process.env, {
// OPENAI_API_KEY: str(),
SOURCEGRAPH_ACCESS_TOKEN: str(),
GITHUB_TOKEN: str(),
SLACK_APP_TOKEN: str(),
SLACK_BOT_TOKEN: str(),
SLACK_SIGNING_SECRET: str(),
})
export const DEFAULT_APP_SETTINGS = {
codebase: 'github.com/sourcegraph/sourcegraph',
serverEndpoint: 'https://sourcegraph.sourcegraph.com',
contextType: 'blended',
debug: 'development',
contextType: 'blended',
} as const
export const DEFAULT_CODEBASES = [
'github.com/sourcegraph/sourcegraph',
'github.com/sourcegraph/handbook',
'github.com/sourcegraph/about',
] as const
export type CodebaseContexts = Record<typeof DEFAULT_CODEBASES[number], CodebaseContext>
export interface AppContext {
codebaseContexts: CodebaseContexts
vectorStore: HNSWLib
}

View File

@ -4,28 +4,41 @@ import { throttle } from 'lodash'
import { Transcript } from '@sourcegraph/cody-shared/src/chat/transcript'
import { reformatBotMessage } from '@sourcegraph/cody-shared/src/chat/viewHelpers'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { Message as PromptMessage } from '@sourcegraph/cody-shared/src/sourcegraph-api'
import { intentDetector } from './services/sourcegraph-client'
import { AppContext } from './constants'
import { streamCompletions } from './services/stream-completions'
import * as slackHelpers from './slack/helpers'
import { interactionFromMessage } from './slack/message-interaction'
import { cleanupMessageForPrompt, getSlackInteraction } from './slack/message-interaction'
import { SLACK_PREAMBLE } from './slack/preamble'
const IN_PROGRESS_MESSAGE = '...✍️'
/**
* Used to test Slack channel context fetching.
* E.g., @cody-dev channel:ask-cody your prompt.
*/
function parseSlackChannelFilter(input: string): string | null {
const match = input.match(/channel:([\w-]+)/)
return match ? match[1] : null
}
/**
* Handles human-generated messages in a Slack bot application.
* Processes the messages, generates a prompt, and streams completions.
*/
export async function handleHumanMessage(event: AppMentionEvent, codebaseContext: CodebaseContext): Promise<void> {
export async function handleHumanMessage(event: AppMentionEvent, appContext: AppContext): Promise<void> {
const channel = event.channel
const thread_ts = slackHelpers.getEventTs(event)
const slackChannelFilter = parseSlackChannelFilter(event.text)
// Restore transcript from the Slack thread
const messages = await slackHelpers.getThreadMessages(channel, thread_ts)
const transcript = await restoreTranscriptFromSlackThread(codebaseContext, messages)
const [messages, channelName] = await Promise.all([
slackHelpers.getThreadMessages(channel, thread_ts),
slackHelpers.getSlackChannelName(channel),
])
const transcript = await restoreTranscriptFromSlackThread(slackChannelFilter || channelName!, appContext, messages)
// Send an in-progress message
const response = await slackHelpers.postMessage(IN_PROGRESS_MESSAGE, channel, thread_ts)
@ -33,31 +46,53 @@ export async function handleHumanMessage(event: AppMentionEvent, codebaseContext
// Generate a prompt and start completion streaming
const prompt = await transcript.toPrompt(SLACK_PREAMBLE)
console.log('PROMPT', prompt)
startCompletionStreaming(prompt, channel, response?.ts)
startCompletionStreaming(prompt, channel, transcript, response?.ts)
}
/**
* Restores a transcript from the given Slack thread messages.
*/
async function restoreTranscriptFromSlackThread(codebaseContext: CodebaseContext, messages: SlackReplyMessage[]) {
async function restoreTranscriptFromSlackThread(
channelName: string,
appContext: AppContext,
messages: SlackReplyMessage[]
) {
const { codebaseContexts, vectorStore } = appContext
const transcript = new Transcript()
const mergedMessages = mergeSequentialUserMessages(messages)
const newHumanMessage = mergedMessages.pop()!
for (const [index, message] of mergedMessages.entries()) {
const interaction = await interactionFromMessage(
message.human,
intentDetector,
// Fetch codebase context only for the last message
index === mergedMessages.length - 1 ? codebaseContext : null
)
mergedMessages.forEach(message => {
const slackInteraction = getSlackInteraction(message.human.text, message.assistant?.text)
transcript.addInteraction(interaction)
transcript.addInteraction(slackInteraction.getTranscriptInteraction())
})
if (message.assistant?.text) {
transcript.addAssistantResponse(message.assistant?.text)
}
const newHumanSlackInteraction = getSlackInteraction(newHumanMessage?.human.text)
if (channelName === 'ask-cody') {
await Promise.all([
newHumanSlackInteraction.updateContextMessagesFromVectorStore(vectorStore, 3),
newHumanSlackInteraction.updateContextMessages(codebaseContexts, 'github.com/sourcegraph/sourcegraph', {
numCodeResults: 3,
numTextResults: 5,
}),
newHumanSlackInteraction.updateContextMessages(codebaseContexts, 'github.com/sourcegraph/handbook', {
numCodeResults: 0,
numTextResults: 4,
}),
])
} else {
await newHumanSlackInteraction.updateContextMessages(codebaseContexts, 'github.com/sourcegraph/sourcegraph', {
numCodeResults: 12,
numTextResults: 3,
})
}
const lastInteraction = newHumanSlackInteraction.getTranscriptInteraction()
transcript.addInteraction(lastInteraction)
return transcript
}
@ -67,14 +102,30 @@ async function restoreTranscriptFromSlackThread(codebaseContext: CodebaseContext
function startCompletionStreaming(
promptMessages: PromptMessage[],
channel: string,
transcript: Transcript,
inProgressMessageTs?: string
): void {
const lastInteraction = transcript.getLastInteraction()!
const { contextFiles = [] } = lastInteraction.toChat().pop()!
// Build the markdown list of file links.
const contextFilesList = contextFiles
.map(file => `[${file.fileName.split('/').pop()}](${file.fileName})`)
.join(', ')
const suffix = contextFiles.length > 0 ? '\n\n**Files used**:\n' + contextFilesList : ''
streamCompletions(promptMessages, {
onChange: text => {
onBotMessageChange(reformatBotMessage(text, ''), channel, inProgressMessageTs)?.catch(console.error)
// console.log('Stream update: ', text)
lastInteraction.setAssistantMessage({ ...lastInteraction.getAssistantMessage(), text })
onBotMessageChange(channel, inProgressMessageTs, reformatBotMessage(text, '') + suffix)?.catch(
console.error
)
},
onComplete: () => {
console.log('Streaming complete!')
console.log('Streaming complete!', lastInteraction.getAssistantMessage().text)
},
onError: err => {
console.error(err)
@ -86,7 +137,7 @@ function startCompletionStreaming(
* Throttled function to update the bot message when there is a change.
* Ensures message updates are throttled to avoid exceeding Slack API rate limits.
*/
const onBotMessageChange = throttle(async (text: string, channel, inProgressMessageTs?: string) => {
const onBotMessageChange = throttle(async (channel, inProgressMessageTs: string | undefined, text: string) => {
if (inProgressMessageTs) {
await slackHelpers.updateMessage(channel, inProgressMessageTs, text)
} else {
@ -96,8 +147,8 @@ const onBotMessageChange = throttle(async (text: string, channel, inProgressMess
}, 1000)
interface SlackInteraction {
human: SlackReplyMessage
assistant?: SlackReplyMessage
human: { text: string }
assistant?: { text: string }
}
/**
@ -107,9 +158,10 @@ function mergeSequentialUserMessages(messages: SlackReplyMessage[]) {
const mergedMessages: SlackInteraction[] = []
for (const message of messages) {
const text = message.text?.replace(/<@[\dA-Z]+>/gm, '').trim()
const lastInteraction = mergedMessages[mergedMessages.length - 1]
const updatedMessage = { ...message, blocks: undefined, text }
const text = cleanupMessageForPrompt(message.text || '', Boolean(message.bot_id))
const updatedMessage = { text }
if (!lastInteraction) {
mergedMessages.push({ human: updatedMessage })

View File

@ -0,0 +1,32 @@
import { Octokit } from 'octokit'
import { ENVIRONMENT_CONFIG } from '../constants'
const octokit = new Octokit({ auth: ENVIRONMENT_CONFIG.GITHUB_TOKEN })
interface FetchFileContentOptions {
owner: string
repo: string
path: string
}
export async function fetchFileContent(options: FetchFileContentOptions) {
const { owner, repo, path } = options
try {
const response = await octokit.rest.repos.getContent({ owner, repo, path })
if ('type' in response.data && response.data.type === 'file') {
const content = Buffer.from(response.data.content, 'base64').toString('utf8')
return {
content,
url: response.data.html_url,
}
}
console.error('Unexpected response fetching file from GitHub:', response)
} catch (error) {
console.error('Error fetching file from GitHub!', error)
}
return undefined
}

View File

@ -0,0 +1,61 @@
import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
import { MarkdownTextSplitter } from 'langchain/text_splitter'
import { HNSWLib } from 'langchain/vectorstores/hnswlib'
import { fetchFileContent } from './github-client'
async function getDocuments() {
const codyNotice = await fetchFileContent({
owner: 'sourcegraph',
repo: 'about',
path: 'content/terms/cody-notice.md',
})
if (!codyNotice) {
return []
}
const { content, url } = codyNotice
const splitter = new MarkdownTextSplitter()
const documents = await splitter.createDocuments([content])
documents.map((document, index) => {
document.metadata = {
fileName: url,
hnswLabel: index,
}
return document
})
return documents
}
const VECTOR_UPDATE_TIMEOUT = 12 * 60 * 60 * 1000
function scheduleVectorUpdate(vectorStore: HNSWLib, timeout: number) {
setTimeout(async () => {
try {
vectorStore._index = undefined
vectorStore.docstore._docs.clear()
const documents = await getDocuments()
await vectorStore.addDocuments(documents)
} catch (error) {
console.error('Failed to update vectors', error)
} finally {
scheduleVectorUpdate(vectorStore, timeout)
}
}, timeout)
}
export async function getVectorStore() {
const documents = await getDocuments()
const embeddings = new OpenAIEmbeddings()
const vectorStore = await HNSWLib.fromDocuments(documents, embeddings)
scheduleVectorUpdate(vectorStore, VECTOR_UPDATE_TIMEOUT)
return vectorStore
}

View File

@ -12,10 +12,11 @@ import {
export class OpenAICompletionsClient implements Pick<SourcegraphCompletionsClient, 'stream'> {
private openai: OpenAIApi
constructor(private apiKey: string) {
constructor(protected apiKey: string) {
const configuration = new Configuration({
apiKey: this.apiKey,
})
this.openai = new OpenAIApi(configuration)
}
@ -23,7 +24,8 @@ export class OpenAICompletionsClient implements Pick<SourcegraphCompletionsClien
this.openai
.createChatCompletion(
{
model: 'gpt-3.5-turbo',
// TODO: manage prompt length
model: 'gpt-4',
messages: params.messages
.filter(
(message): message is Omit<Message, 'text'> & Required<Pick<Message, 'text'>> =>
@ -43,10 +45,12 @@ export class OpenAICompletionsClient implements Pick<SourcegraphCompletionsClien
const stream = response.data as unknown as IncomingMessage
let modelResponseText = ''
let buffer = ''
stream.on('data', (chunk: Buffer) => {
// Split messages in the event stream.
const payloads = chunk.toString().split('\n\n')
buffer += chunk.toString()
const payloads = buffer.split('\n\n')
for (const payload of payloads) {
if (payload.includes('[DONE]')) {
@ -64,17 +68,27 @@ export class OpenAICompletionsClient implements Pick<SourcegraphCompletionsClien
modelResponseText += newTextChunk
cb.onChange(modelResponseText)
}
buffer = buffer.slice(Math.max(0, buffer.indexOf(payload) + payload.length))
} catch (error) {
console.log(
`Error with JSON.parse: ${chunk.toString()}\nPayload: ${payload};\nError: ${error}`
)
cb.onError(error)
if (error instanceof SyntaxError && buffer.length > 0) {
// Incomplete JSON string, wait for more data
continue
} else {
console.log(
`Error with JSON.parse: ${chunk.toString()}\nPayload: ${payload};\nError: ${error}`
)
cb.onError(error)
}
}
}
}
})
stream.on('error', e => cb.onError(e.message))
stream.on('error', e => {
console.error('OpenAI stream failed', e)
cb.onError(e.message)
})
stream.on('end', () => cb.onComplete())
})
.catch(console.error)

View File

@ -1,5 +1,6 @@
import { KnownEventFromType, SlackEvent } from '@slack/bolt'
import { ChatPostMessageResponse } from '@slack/web-api'
import slackifyMarkdown from 'slackify-markdown'
import { webClient } from './init'
@ -12,6 +13,14 @@ export async function getThreadMessages(channel: string, thread_ts: string) {
return result?.messages || []
}
export async function getSlackChannelName(channel: string) {
const result = await webClient.conversations.info({
channel,
})
return result.channel?.name
}
export function getEventTs(event: KnownEventFromType<'app_mention'> | KnownEventFromType<'message'>) {
if ('thread_ts' in event && event.thread_ts !== event.ts) {
return event.thread_ts || event.ts
@ -28,7 +37,35 @@ export async function updateMessage(channel: string, messageTs: string, newText:
const response = await webClient.chat.update({
channel,
ts: messageTs, // The timestamp of the message you want to update.
text: newText, // The new text for the updated message.
text: slackifyMarkdown(newText), // The new text for the updated message.
mrkdwn: true,
})
if (!response.ok) {
throw new Error(`Error updating message: ${response.error}`)
}
}
export async function updateMessageWithFileList(
channel: string,
messageTs: string,
newText: string,
fileList: string
): Promise<void> {
const response = await webClient.chat.update({
channel,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: slackifyMarkdown(fileList),
},
},
],
ts: messageTs, // The timestamp of the message you want to update.
text: slackifyMarkdown(newText), // The new text for the updated message.
mrkdwn: true,
})
if (!response.ok) {
@ -43,8 +80,9 @@ export async function postMessage(
): Promise<ChatPostMessageResponse | undefined> {
const response = await webClient.chat.postMessage({
channel,
text: message,
text: slackifyMarkdown(message),
thread_ts, // Use the timestamp of the parent message to reply in the thread.
mrkdwn: true,
})
if (!response.ok) {

View File

@ -1,53 +1,93 @@
import { Message } from '@slack/web-api/dist/response/ChannelsRepliesResponse'
import { HNSWLib } from 'langchain/vectorstores/hnswlib'
import { Interaction } from '@sourcegraph/cody-shared/src/chat/transcript/interaction'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { ContextMessage } from '@sourcegraph/cody-shared/src/codebase-context/messages'
import { IntentDetector } from '@sourcegraph/cody-shared/src/intent-detector'
import { InteractionMessage } from '@sourcegraph/cody-shared/src/chat/transcript/messages'
import { ContextSearchOptions } from '@sourcegraph/cody-shared/src/codebase-context'
import { ContextMessage, getContextMessageWithResponse } from '@sourcegraph/cody-shared/src/codebase-context/messages'
// import { IntentDetector } from '@sourcegraph/cody-shared/src/intent-detector'
import { MAX_HUMAN_INPUT_TOKENS } from '@sourcegraph/cody-shared/src/prompt/constants'
import { populateMarkdownContextTemplate } from '@sourcegraph/cody-shared/src/prompt/templates'
import { truncateText } from '@sourcegraph/cody-shared/src/prompt/truncation'
export async function interactionFromMessage(
message: Message,
intentDetector: IntentDetector,
codebaseContext: CodebaseContext | null
): Promise<Interaction | null> {
if (!message.text) {
return Promise.resolve(null)
import { CodebaseContexts } from '../constants'
class SlackInteraction {
public contextMessages: ContextMessage[] = []
constructor(private humanMessage: InteractionMessage, private assistantMessage: InteractionMessage) {}
public async updateContextMessagesFromVectorStore(vectorStore: HNSWLib, numResults: number) {
const docs = await vectorStore.similaritySearch(this.humanMessage.text!, numResults)
docs.forEach(doc => {
const contextMessage = getContextMessageWithResponse(
populateMarkdownContextTemplate(doc.pageContent, doc.metadata.fileName),
doc.metadata.fileName
)
this.contextMessages.push(...contextMessage)
})
}
const textWithoutMentions = message.text?.replace(/<@[\dA-Z]+>/gm, '').trim()
const text = truncateText(textWithoutMentions, MAX_HUMAN_INPUT_TOKENS)
public async updateContextMessages(
codebaseContexts: CodebaseContexts,
codebase: keyof CodebaseContexts,
contextSearchOptions: ContextSearchOptions
// intentDetector?: IntentDetector
) {
// const isCodebaseContextRequired = await intentDetector.isCodebaseContextRequired(text)
const isCodebaseContextRequired = true
const contextMessages =
codebaseContext === null ? Promise.resolve([]) : getContextMessages(text, intentDetector, codebaseContext)
if (isCodebaseContextRequired) {
const contextMessages = await codebaseContexts[codebase].getContextMessages(
this.humanMessage.text!,
contextSearchOptions
)
return Promise.resolve(
new Interaction(
{ speaker: 'human', text, displayText: text },
{ speaker: 'assistant', text: '', displayText: '' },
contextMessages
)
this.contextMessages.push(
...contextMessages.map(message => {
if (message.file) {
message.file.fileName = `https://${codebase}/blob/main/${message.file.fileName}`
}
return message
})
)
}
}
public getTranscriptInteraction() {
return new Interaction(this.humanMessage, this.assistantMessage, Promise.resolve(this.contextMessages))
}
}
export function getSlackInteraction(humanText: string, assistantText: string = ''): SlackInteraction {
const text = cleanupMessageForPrompt(humanText)
const filteredHumanText = truncateText(text, MAX_HUMAN_INPUT_TOKENS)
return new SlackInteraction(
{ speaker: 'human', text: filteredHumanText },
{ speaker: 'assistant', text: assistantText }
)
}
export async function getContextMessages(
text: string,
intentDetector: IntentDetector,
codebaseContext: CodebaseContext
): Promise<ContextMessage[]> {
const contextMessages: ContextMessage[] = []
export function cleanupMessageForPrompt(text: string, isAssistantMessage = false) {
// Delete mentions
const textWithoutMentions = text.replace(/<@[\dA-Z]+>/gm, '').trim()
const isCodebaseContextRequired = await intentDetector.isCodebaseContextRequired(text)
// Delete cody-slack filters
const textWithoutFilters = textWithoutMentions.replace(/channel:([\w-]+)/gm, '').trim()
if (isCodebaseContextRequired) {
const codebaseContextMessages = await codebaseContext.getContextMessages(text, {
numCodeResults: 8,
numTextResults: 2,
})
if (isAssistantMessage) {
// Delete "Files used" section
const filesSectionIndex = textWithoutFilters.lastIndexOf('*Files used*')
contextMessages.push(...codebaseContextMessages)
if (filesSectionIndex !== -1) {
return textWithoutFilters
.slice(0, filesSectionIndex)
.replace(/[|\u00A0\u200B\u200D]/gm, '')
.replace(/\n+$/gm, '')
}
}
return contextMessages
return textWithoutFilters
}

View File

@ -1,7 +1,5 @@
import { Message } from '@sourcegraph/cody-shared/src/sourcegraph-api'
import { DEFAULT_APP_SETTINGS } from '../constants'
const actions = `You are Cody, an AI-powered coding assistant created by Sourcegraph. You work inside a Slack workspace. You have access to the Slack thread conversation with all the replies. You perform the following actions:
- Answer general programming questions.
- Answer general questions about the Slack thread you're in.
@ -10,14 +8,14 @@ const actions = `You are Cody, an AI-powered coding assistant created by Sourceg
- Explain what a section of code does.`
const rules = `In your responses, obey the following rules:
- Be as brief and concise as possible without losing clarity.
- The current Slack thread is the same as the conversation you're having with the user. Use this information to answer questions.
- Be brief without losing clarity.
- Use GitHub markdown to format your messages in the most readable way for humans. Use markdown lists.
- All code snippets have to be markdown-formatted without that language specifier, and placed in-between triple backticks like this \`\`\`.
- Answer questions only if you know the answer or can make a well-informed guess. Otherwise, tell me you don't know and what context I need to provide you for you to answer the question.
- Only reference file names or URLs if you are sure they exist.`
const answer = `Understood. I am Cody, an AI assistant made by Sourcegraph to help with programming tasks and assist in Slack conversations.
I work inside a Slack workspace. I have access have access to the currently active Slack thread conversation with all the replies.
I use GitHub markdwon to format my responses in the most readable way for humans.
I will answer questions, explain code, and generate code as concisely and clearly as possible.
My responses will be formatted using Markdown syntax for code blocks without language specifiers.
I will acknowledge when I don't know an answer or need more context. I will use the Slack thread conversation history to answer your questions.`
@ -26,21 +24,10 @@ I will acknowledge when I don't know an answer or need more context. I will use
* Creates and returns an array of two messages: one from a human, and the supposed response from the AI assistant.
* Both messages contain an optional note about the current codebase if it's not null.
*/
function getSlackPreamble(codebase: string): Message[] {
function getSlackPreamble(): Message[] {
const preamble = [actions, rules]
const preambleResponse = [answer]
if (codebase) {
const codebasePreamble =
`You have access to the \`${codebase}\` repository. You are able to answer questions about the \`${codebase}\` repository. ` +
`I will provide the relevant code snippets from the \`${codebase}\` repository when necessary to answer my questions.`
preamble.push(codebasePreamble)
preambleResponse.push(
`I have access to the \`${codebase}\` repository and can answer questions about its files.`
)
}
return [
{
speaker: 'human',
@ -53,4 +40,4 @@ function getSlackPreamble(codebase: string): Message[] {
]
}
export const SLACK_PREAMBLE = getSlackPreamble(DEFAULT_APP_SETTINGS.codebase)
export const SLACK_PREAMBLE = getSlackPreamble()

View File

@ -501,6 +501,11 @@
"packageManager": "pnpm@8.1.0",
"pnpm": {
"packageExtensions": {
"hnswlib-node": {
"dependencies": {
"node-gyp": "*"
}
},
"cpu-features": {
"dependencies": {
"node-gyp": "*"

File diff suppressed because it is too large Load Diff