import json
import os
import requests
import streamlit as st
from anthropic import Anthropic
from anthropic.types import ToolUseBlock
from bs4 import BeautifulSoup
from googleapiclient.discovery import build
from rss_parser import RSSParser
if "rss" not in st.session_state:
rss_url = "https://aws.amazon.com/about-aws/whats-new/recent/feed/"
response = requests.get(rss_url)
st.session_state["rss"] = RSSParser.parse(response.text)
rss = st.session_state["rss"]
items = rss.channel.items
def f1(**kwargs):
st.session_state["selection"] = kwargs
def create_search_query(contents: str):
client = Anthropic(
api_key=os.environ.get("ANTHROPIC_API_KEY"),
)
SYSTEM_PROMPT = """あなたはGoogle検索を支援するアシスタントAIです。ユーザーから提供された資料に基づいて、関連情報を探すための検索クエリを生成する能力があります。"""
USER_PROMPT_TEMPLATE = """
{{ref-doc}}
この記事を読んだ読者に、関連する情報のURLを共有してください。
関連する情報はGoogle検索を利用して収集します。
"""
# 2024/9/29修正
# Toolを使用するように変更
tool = {
"name": "web_search",
"description": "Google検索を行い情報を取得します。",
"input_schema": {
"type": "object",
"properties": {
"search_query": {
"type": "string",
"description": "検索クエリー",
},
},
"required": ["search_query"],
},
}
response = client.messages.create(
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": USER_PROMPT_TEMPLATE.replace("{{ref-doc}}", contents),
}
],
}
],
temperature=0.5,
model="claude-3-haiku-20240307",
tools=[tool],
tool_choice={"type": "tool", "name": "web_search"},
)
tool_use = list(filter(lambda x: x.type == "tool_use", response.content))[0]
search_query = tool_use.input["search_query"]
return search_query
def search(search_query: str):
service = build("customsearch", "v1", developerKey=os.environ.get("GOOGLE_API_KEY"))
cse = service.cse()
res = cse.list(
q=search_query,
cx=os.environ.get("GOOGLE_CSE_ID"),
# lr="lang_ja",
num=3,
).execute()
return res.get("items", [])
st.title("AWSのWhat's Newを解説くん")
st.write("←から選んでね")
with st.sidebar:
for item in items:
with st.container(border=True):
st.write(item.title.content)
# 2024/9/29修正
# item.linkを参照していましたが、item.linksになったようです
items = list(map(lambda x: x.content, item.links))
kwargs = {
"title": item.title.content,
"pub_date": item.pub_date.content,
"link": items[0],
}
st.button("おしえて", key=item.guid.content, on_click=f1, kwargs=kwargs)
# 5/26 https://tech.algomatic.jp/entry/2024/05/23/140219を参考にTypeScript型定義を使用
prompt = """
AWSのWhat's newを提示しますので、注意深く読み込んでください。
{{What's new}}
タスク
- 会話形式でわかりやすく解説してください。
登場人物
- user1: AWSスペシャリスト
- user2: 新入社員
ルール
- 回答は必ず日本語で行ってください。
- ハルシネーションは許されないので、What\'s newの内容からのみ回答を生成してください。
- また、回答付不要な出力は含めず、登場人物の会話のみを出力してください。
- 回答は以下のTypeScript型定義に従ってください。
```typescript
type Conversations = {
conversations: Message[]
}
type Message = {
actor: User,
message: string
}
enum User {
user1 = "user1",
user2 = "user2"
}
```
例
{
"conversations": [
{
"actor": "user1",
"message": "AWSの新機能について説明しましょう。Mistral Smallというファウンデーションモデルが、Amazon Bedrock で利用可能になりました。"
},
{
"actor": "user2",
"message": "Mistral Smallとはどのようなモデルなのでしょうか?"
},
{
"actor": "user1",
"message": "Mistral Smallは、Mistral AIが提供する高性能な大規模言語モデルの1つです。Amazon Bedrock で利用できるようになりました。このモデルは、高速で低コストなパフォーマンスを提供し、大量の言語処理タスクに最適化されています。"
},
{
"actor": "user2",
"message": "Mistral Smallの主な特徴は何ですか?"
},
{
"actor": "user1",
"message": "Mistral Smallの主な特徴は以下の通りです:情報保持能力に優れた retrieval-augmented generation (RAG) 機能を搭載コーディングタスクに強い性能を発揮英語、フランス語、ドイツ語、スペイン語、イタリア語など、多言語に対応安全性の高い機能も備えている"
},
{
"actor": "user2",
"message": "Mistral Smallはどのようなユースケースに適しているのでしょうか?"
},
{
"actor": "user1",
"message": "Mistral Smallは、大量の言語処理タスクを効率的に処理できるため、分類、カスタマーサポート、テキスト生成などの用途に適しています。また、コードの生成、レビュー、コメント付与などのソフトウェア開発タスクにも活用できます。"
},
{
"actor": "user2",
"message": "Mistral Smallはどこで利用できるのですか?"
},
{
"actor": "user1",
"message": "Mistral Smallは、Amazon Bedrock で利用できるようになりました。Amazon Bedrock コンソールからアクセスして、Mistral Smallを使ったアプリケーションの構築を始められます。"
}
]
}
会話形式でわかりやすく解説してください。
"""
if "selection" in st.session_state:
kwargs = st.session_state["selection"]
response = requests.get(kwargs["link"])
soup = BeautifulSoup(response.text, features="html.parser")
# 2024/5/21修正
# What's Newの構成が変わり、mainをチェックするように
contents = soup.main.get_text()
st.subheader(kwargs["title"])
st.write(kwargs["pub_date"])
with st.spinner("ちょっとまってね"):
client = Anthropic(
api_key=os.environ.get("ANTHROPIC_API_KEY"),
)
# 2024/9/29修正
# Toolを使用するように変更
tool = {
"name": "conversation_memory",
"description": "会話履歴をメモリーに保存します",
"input_schema": {
"type": "object",
"properties": {
"conversations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"actor": {
"type": "string",
"description": "会話の発言者。user1またはuser2",
},
"answer": {
"type": "string",
"message": "会話の内容",
},
},
},
},
},
"required": ["conversations"],
},
}
message = client.messages.create(
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": prompt.replace("{{What's new}}", contents),
}
],
}
],
temperature=0.5,
model="claude-3-haiku-20240307",
tools=[tool],
tool_choice={"type": "tool", "name": "conversation_memory"},
)
try:
tool_use = message.content[0]
assert isinstance(tool_use, ToolUseBlock)
conversations = tool_use.input
for c in conversations["conversations"]:
if c["actor"] == "user1":
with st.chat_message("ai"):
st.write(c["message"])
if c["actor"] == "user2":
with st.chat_message("human"):
st.write(c["message"])
except Exception as e:
st.write("ごめんなさい。エラーです。")
with st.expander("Haikuの回答:"):
st.write(message.content)
st.link_button("ブログを確認", kwargs["link"])
st.divider()
search_query = create_search_query(contents=contents)
# st.write(search_query)
if search_query:
search_result = search(search_query=search_query)
# st.write(search_result)
st.markdown("おすすめ記事")
for item in search_result:
with st.container(border=True):
st.markdown(f'##### {item["title"]}')
st.markdown(item["htmlSnippet"], unsafe_allow_html=True)
st.link_button(item["formattedUrl"], item["link"])