ChatGPTで毎朝arxivのLLM関連論文を要約してSlackに投稿するbot

自分向けメモ。
Cloud functionにデプロイしてる。
コードは下記。

```index.js
const axios = require("axios");
const schedule = require("node-schedule");
const { DOMParser } = require("xmldom");
const { Configuration, OpenAIApi } = require("openai");

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;


const configuration = new Configuration({
  apiKey: OPENAI_API_KEY
});
const openai = new OpenAIApi(configuration);

const slackWebhookUrl = SLACK_WEBHOOK_URL;

const project = 'hogehogehoge'

async function fetchArxivPapers(query) {
  const response = await axios.get("https://export.arxiv.org/api/query", {
    params: {
      search_query: query,
      sortBy: "submittedDate",
      sort_order: "descending",
      max_results: 5
    }
  });

  const data = new DOMParser().parseFromString(response.data, "application/xml");
  const entries = Array.from(data.getElementsByTagName("entry"));

  return entries.map(entry => {
    return {
      id: entry.getElementsByTagName("id")[0].textContent,
      title: entry.getElementsByTagName("title")[0].textContent,
      summary: entry.getElementsByTagName("summary")[0].textContent,
      link: entry.getElementsByTagName("link")[0].getAttribute("href"),
      published: entry.getElementsByTagName("published")[0].textContent
    };
  });

}

async function summarizeAbstract(paper) {
  const prompt = `
    あなたは、${paper.title}という論文を読んでいます。
    この論文の要約を作成してください。
    要約: ${paper.summary}
  `;
  const response = await openai.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [{"role": "user", "content": prompt}],
  });
  return response.data.choices[0].message.content;
}

async function postToSlack(paper, summary) {
  const message = {
    text: `*${paper.title}* \n_URL: ${paper.id} \n要約: ${summary}\n`
  };
  await axios.post(slackWebhookUrl, message);
}

exports.arxivSummaryBot = async (req, res) => {
  const globalLogFields = {};

  // Add log correlation to nest all log messages beneath request log in Log Viewer.
  // (This only works for HTTP-based invocations where `req` is defined.)
  if (typeof req !== 'undefined') {
    const traceHeader = req.header('X-Cloud-Trace-Context');
    if (traceHeader && project) {
      const [trace] = traceHeader.split('/');
      globalLogFields[
        'logging.googleapis.com/trace'
      ] = `projects/${project}/traces/${trace}`;
    }
  }

  const queryLLM = "LLM";
  const queryChatGPT = "ChatGPT";
  
  const papersLLM = await fetchArxivPapers(queryLLM);
  const papersChatGPT = await fetchArxivPapers(queryChatGPT);

  const papers = [...papersLLM, ...papersChatGPT].filter(
    (paper, index, self) => 
        index === self.findIndex((p) => p.id === paper.id)
  );
  const today = new Date();
  const yesterday = new Date(today);
  yesterday.setDate(yesterday.getDate() - 2);


  let postedSummaries = false;
  
  for (const paper of papers) {
    const submittedDate = new Date(paper.published);
    if (submittedDate >= yesterday && submittedDate <= today) {
        const summary = await summarizeAbstract(paper);
        await postToSlack(paper, summary);
        postedSummaries = true;
    }
  }
  if (res) {
    if (postedSummaries) {
      console.log(JSON.stringify(Object.assign(
          {
            severity: 'INFO',
            message: 'Posted summaries to Slack',
          },
          globalLogFields
      )))
      res.status(200).send();
    } else {
      console.log(JSON.stringify(Object.assign(
          {
            severity: 'INFO',
            message: 'No new papers found.',
          },
          globalLogFields
      )))
      res.status(200).send();
    }
  } else {
    if (postedSummaries) {
      console.log(JSON.stringify(Object.assign(
          {
            severity: 'INFO',
            message: 'Posted summaries to Slack',
          },
          globalLogFields
      )))
    } else {
      console.log(JSON.stringify(Object.assign(
          {
            severity: 'INFO',
            message: 'No new papers found.',
          },
          globalLogFields
      )))
    }
  }
};

schedule.scheduleJob("0 8 * * *", () => {
  exports.arxivSummaryBot();
});

// exports.arxivSummaryBot();
```

で、毎朝8時にこれを叩くために、cloud schedulerに設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20014/04a78591-c4bb-d915-eca5-49f262130f04.png)

これで、毎朝8時に論文要約もどきが投稿される。