Skip to main content

Building an Arxiv Paper Summarizer Slack App

In this article, we will create a smart bot that can summarize research papers.

a hero image illustrating the goal of this tutorial

We will build it as a Slack bot which receives the URL of a paper from Arxiv and then uses Cohere’s LLMs to summarize the abstract and return a one-sentence summary.

Get Setup on Cohere#

  1. Register for your account

  2. Click ✨ Create API Key

a screen capture of creating an API key

Create a Slack App#

To start, we familiarize ourselves with the Cohere API, some existing Slack App examples, and the documentation for Slack’s recommended framework for building on their platform.

Co:ntext Gathering#

A couple of options for reaching out to Cohere’s API are the official client and a simple function that wraps a popular HTTP client, axios. See examples below:

const cohere = require('cohere-ai');
cohere.generate('MODEL_NAME', config);
* generate text using Cohere's generation endpoint
* @summary generate text using Cohere's generation endpoint
* @return {string} generated text from Cohere's generation endpoint.
async generate() {
const config = {
headers: {
Authorization: `Bearer ${COHERE_API_TOKEN}`,
"Content-Type": "application/json",
const data = {
prompt: this.prompt(),
max_tokens: 50,
stop_sequences: ["."],
return axios
.post(COHERE_GENERATE_API, data, config)
.then((res) => {
.catch((err) => {

We can also review a dated example of how Slack App authors would previously interact with Slack’s systems. This project for example creates an HTTP server, adds handlers for desired messages that arrive and posts responses back to Slack’s API.

The final bit of context gathering before moving on is the bolt-js starter documentation. This is Slack’s recommended client and our choice for a starting point.

Take a quick skim through the Getting Started documentation, paying attention to the following sections:

Slack-side Co:nfiguration#

We start out by creating a new Slack App via the web ui:

a screen capture of creating a slack app via web ui

A recent Slack API feature allows for passing in App configuration via a YAML manifest. Let's use this starter configuration on our first pass:

name: Cohere slack app
home_tab_enabled: false
messages_tab_enabled: true
messages_tab_read_only_enabled: false
display_name: co
always_online: true
- channels:history
- channels:join
- chat:write
- reactions:read
- app_mentions:read
- app_mention
- reaction_added
- message.channels
is_enabled: false
org_deploy_enabled: true
token_rotation_enabled: false
socket_mode_enabled: true
is_hosted: false

Note the scopes chosen for our bot. We are starting with channels:history, channels:join, chat:write, reactions:read and app_mentions:read.

Other settings are tuned conservatively. We recommend making a second pass to tighten things up after we get things functional by the end of this guide.

After App creation, a redirect will land us on the Basic Information page where a suggestion to install our app to a workspace is shown. This will unblock further configuration, so lets go ahead with the install:

a screen capture of installing a slack app to a workspace

We can now jump over to event subscription configuration. This will configure Slack to send our bot the events it will react to. This bot will filter for only three events: app_mention, reaction_added, and message.channels.

a screen capture of configuring event subscriptions

Our first pass of configuring the Slack-side of our App is mostly complete! We can always return later to finalize a more capable setup. But for now, we grab our App credentials and bot user token from the web ui as a final step.

a screeen capture displaying app credential retrieval

a screen capture displaying bot user token retrieval

To review, we have set up App permissions, bots and event subscriptions.

a screen capture summarizing slack app configuration

We can now move on to coding our bot ✨🤖✨


having trouble? Check out the official getting started tutorial for full details.

Co:ding our Bot#

Starter Project Layout#

Our cohere-slack-starter-app opts for a collaboration-shaped plugin architecture. Each plugin is grouped as either a reaction plugin or mention plugin. Contributions could be as simple as writing a prompt matching a desired persona or as complex as a command-line request-reply sequence.

a screen capture displaying our starter project layout

For an alternate and less abstracted layout, see Slack’s official tutorials.

Navigating the APIs#

As we implement our bot, we become familiar with our dependent APIs: Slack and Cohere.

Slack has an abundance of functionality exposed via its API due to having a few generations of options being available. This makes it tricky to choose the right endpoint as some may seem conveniently suited to a use case but may not be future-proof.

Slack’s API can be distilled into a few links that provide valuable context:

  • the web API: the kitchen sink. most low-level actions related to a wide range of tasks can be found here. if a higher-level API fails to suit your needs, this longer-standing API will be the right tool to reach for.
  • the conversations API: a purpose-fit abstraction that favours fetching messages from public channels. history and replies are particularly helpful for our bot’s likely communication patterns.
  • message events: the subtypes documentation provides great inspiration for where a bot’s behaviour could grow. a likely subtype our bot would interact with is a channel, which encodes a message post to a slack channel.
  • helpful top-level methods: there are many to choose from. users.conversations and chat.postMessage proved helpful for responding to direct messages or forcing threaded messaging. reactions.get is handy for understanding reaction count, which is helpful for tuning configuration of Cohere API requests for text.

Cohere resides on the other side: a slim set of endpoints gate access to powerful LLMs. A quick peek at the API documentation leads to the text generation endpoint, which is all we need for this bot.

The most notable bit of complexity that we may want to take on is tuning request parameters. See the docs for full details, but take note of max_tokens and stop_sequences as they would likely require tuning to support our bot’s eventual behaviour.

We now have a decent shape of how our bot will look, at least at a dependency level. Let's tie this all together by implementing a single feature.

Fetch an Abstract from Arxiv#

We will need to teach our bot how to fetch an abstract before we can summarize it.

A simple example solution found in our cohere-slack-starter-app uses axios to scrape an Arxiv page, followed by jsdom to parse out the abstract found on the page.

* abstract fetches a paper's abstract given its hyperlink url
* @param {string} url - http url for a paper (ex:
* @return {string} abstract parsed from the provided hyperlink
async abstract(url) {
try {
const { data } = await axios.get(url);
const dom = new JSDOM(data);
const { document } = dom.window;
const abstract = document.querySelector("#abs > blockquote");
return abstract.textContent;
} catch (error) {
throw error;

Summarize with Cohere#

Summarization can be achieved using a well-crafted prompt and Cohere’s generation API endpoint.

* prompt specified during a summarize subcommand
* @return {string} all trailing arguments parsed from the entire summarize command
async prompt() {
const urlWithBraces = this.mention.arguments()[0];
const url = urlWithBraces.replace("<", "").replace(">", "");
const abstract = await this.abstract(url);
const prompt = `"Summarize the following paper in 1 sentence:\n
The paper details`;
return prompt;

The snippet here displays how we can embed a sanitized version of our scraped content into a prompt string.

The prompt leaves off where Cohere’s generation feature will pick up. It guides our LLM towards the desired summary.

With a prompt in hand, we simply perform an API request using cohere.generate().

* summarize text using Cohere's generation endpoint
* @return {string} summarized text from Cohere's generation endpoint.
async summarize() {
const prompt = await this.prompt();
const res = await cohere.generate("large", {
prompt: prompt,
stop_sequences: ["."],
max_tokens: 140,
temperature: 1,
if (res.statusCode != 200) {
throw new Error(`${res.statusCode} received from cohere API`);
return res.body.generations[0].text;

Take note of the stop_sequences value here. We are interested in a single sentence summary in this case. However, other use cases may require paragraphs of generated text and a newline would be a more suitable stop sequence to configure.

Final steps from here would be to wire the response all the way back through Slack’s channel messaging features. Again, see our cohere-slack-starter-app for an example of how this is done.


For more on Text Summarization with Cohere’s generative models, see our Text Summarization article.