Mix.install(
[
{:req, "~> 0.4.4"},
{:kino_bumblebee, "~> 0.4.0"},
{:exla, ">= 0.0.0"}
],
config: [nx: [default_backend: EXLA.Backend]]
)
defmodule Article do
defstruct [
:author,
:content,
:description,
:published_at,
:source,
:title,
:url,
:url_to_image,
:entities,
:sentiment
]
def parse(body) do
%Article{
author: body["author"],
content: body["content"],
description: body["description"],
published_at: body["publishedAt"],
source: get_in(body, ["source", "id"]),
title: body["title"],
url: body["url"],
url_to_image: body["urlToImage"]
}
end
end
{:ok, %{status: 200, body: body}} =
"https://newsapi.org/v2/top-headlines"
|> Req.get(params: %{country: "us", apiKey: System.get_env("LB_NEWS_API_KEY")})
articles =
body["articles"]
|> Enum.reject(&(&1["content"] == nil))
|> Enum.map(&Article.parse/1)
{:ok, model_info} = Bumblebee.load_model({:hf, "dslim/bert-base-NER"})
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "bert-base-cased"})
serving =
Bumblebee.Text.token_classification(model_info, tokenizer,
aggregation: :same,
compile: [batch_size: 1, sequence_length: 128],
defn_options: [compiler: EXLA]
)
entities = Nx.Serving.run(serving, Enum.map(articles, & &1.title))
articles =
articles
|> Enum.zip_with(entities, fn article, %{entities: entities} ->
%{article | entities: entities}
end)
{:ok, model_info} = Bumblebee.load_model({:hf, "ProsusAI/finbert"})
{:ok, tokenizer} = Bumblebee.load_tokenizer({:hf, "bert-base-uncased"})
finbert_serving =
Bumblebee.Text.text_classification(model_info, tokenizer,
compile: [batch_size: 1, sequence_length: 128],
defn_options: [compiler: EXLA]
)
predictions = Nx.Serving.run(finbert_serving, Enum.map(articles, & &1.title))
articles =
articles
|> Enum.zip_with(predictions, fn article, %{predictions: prediction} ->
%{label: max_label} = Enum.max_by(prediction, & &1.score)
%{article | sentiment: max_label}
end)
frame = Kino.Frame.new()
articles
|> Enum.each(fn article ->
Kino.Frame.append(frame, Kino.Bumblebee.HighlightedText.new(article.title, article.entities))
Kino.Frame.append(frame, Kino.Text.new(article.sentiment))
end)
Kino.Layout.grid([frame], boxed: true, gap: 16)