Closed11

Cohereの多言語モデル「Command-R」を試す+「Command-R+」も試してみた

kun432kun432

https://twitter.com/cohere/status/1767275128813928611

本日、我々は大規模な生産ワークロードを対象とした新しいRAG最適化LLMであるCommand-Rをリリースできることを嬉しく思います。

Command-Rは、高効率と高精度のバランスを保ち、企業が概念実証を越えて本番稼動に移行することを可能にする、新たな「スケーラブル」モデルのカテゴリーに適合します。

https://twitter.com/cohere/status/1767275131032793199

Command-Rは、RAGアプリケーションのためのクラス最高の統合を提供するために、私たちのEmbedとRerankモデルと手をつないで動作します。このモデルは、スケーラブルなカテゴリーにおいて他を凌駕し、その出力には幻覚のリスクを軽減する明確な引用が付属しています。

https://twitter.com/cohere/status/1767275134446920126

ツールの使用により、企業の開発者は、複数のシステムにまたがり、複雑な推論や意思決定を必要とする、時間のかかる手作業の自動化を解除することができます。

https://twitter.com/cohere/status/1767275137752051894

Command-R は、コンテキストの長さが長くなり、Cohere のホスト型 API の価格が引き下げられ、Cohere のプライベート・クラウド導入の効率が大幅に改善されました。このアップデートにより、コンテキストを追加することでパフォーマンスが劇的に向上する RAG のユースケースがアンロックされます。

https://twitter.com/cohere/status/1767275139400450082

その一環として @CohereForAIは、このバージョンのCommand-Rの重みを公開し、研究目的で使用できるようにしている。

詳細は以下。

https://txt.cohere.com/command-r/

kun432kun432

基本的な使い方は以前と変わらない模様だが、

  • /generate/summarizeはLegacy扱いとなって、今後は/chatエンドポイントを使うっぽい。
  • /detect-languageは廃止

になっていて、それ以外は以前と多分変わらないと思う。あと以前からあったのかなかったのかわからないけど、

  • /datasets: ファインチューニング用のデータセットを管理するためのエンドポイント
  • /embed-jobs: データセットのembeddingsをバッチ実行で取得するためのエンドポイント
  • /connectors: 外部への接続用APIをツールとして登録するためのエンドポイント。Function Callingとすこし似ている感がある。

があるけど、今回はCommand-Rを見たいので/chatだけ。

参考までに以前の記事はこちら。

https://zenn.dev/kun432/scraps/234cd4f8e89011

インストール。

!pip install cohere

APIキーを読み込んでクライアント初期化。

from google.colab import userdata
import cohere

co = cohere.Client(userdata.get('COHERE_API_KEY'))

chat

from pprint import pprint

response = co.chat(
  chat_history=[
    {"role": "USER", "message": "重力を発見したのは誰?"},
    {"role": "CHATBOT", "message": "重力の発見者として広く認知されているのはアイザック・ニュートンです。"}
  ],
  message="彼が生まれたのは何年?",
  model='command-r',
)
pprint(response)

レスポンスはこんな感じになっている。回答だけ取り出したい場合はresponse.textで取り出せる。

cohere.Chat{
    id: 6828d479-518c-41a6-8fbd-d99b5ddde52cresponse_id: 6828d479-518c-41a6-8fbd-d99b5ddde52cgeneration_id: f1fedaaa-633c-4f21-94b8-cc801b2a3751message: 彼が生まれたのは何年?text: アイザック・ニュートンは164314日にイングランドのウルズソープで生まれました。conversation_id: Noneprompt: Nonechat_history: [
        {
            'message': '重力を発見したのは誰?',
            'role': 'USER'
        },
        {
            'message': '重力の発見者として広く認知されているのはアイザック・ニュートンです。',
            'role': 'CHATBOT'
        },
        {
            'message': '彼が生まれたのは何年?',
            'response_id': '6828d479-518c-41a6-8fbd-d99b5ddde52c',
            'generation_id': '74bd2d2a-13cf-467b-bb6d-9be3df101a7f',
            'role': 'USER'
        },
        {
            'message': 'アイザック・ニュートンは1643年1月4日にイングランドのウルズソープで生まれました。',
            'response_id': '6828d479-518c-41a6-8fbd-d99b5ddde52c',
            'generation_id': 'f1fedaaa-633c-4f21-94b8-cc801b2a3751',
            'role': 'CHATBOT'
        }
    ]
    preamble: Noneclient: <cohere.client.Clientobjectat0x7e832dc37e20>token_count: {
        'prompt_tokens': 100,
        'response_tokens': 26,
        'total_tokens': 126,
        'billed_tokens': 54
    }
    meta: {
        'api_version': {
            'version': '1'
        },
        'billed_units': {
            'input_tokens': 28,
            'output_tokens': 26
        }
    }
    is_search_required: Nonecitations: Nonedocuments: Nonesearch_results: Nonesearch_queries: Nonetool_calls: Nonefinish_reason: COMPLETE
}

connectorを使ってみる。web-searchはデフォルトで提供されているウェブ検索用のコネクターらしい。

from pprint import pprint

response = co.chat(
  chat_history=[
    {"role": "USER", "message": "重力を発見したのは誰?"},
    {"role": "CHATBOT", "message": "重力の発見者として広く認知されているのはアイザック・ニュートンです。"}
  ],
  message="彼が生まれたのは何年?",
  model='command-r',
  connectors=[{"id": "web-search"}]
)
pprint(response)

以下のようにウェブ検索の結果を踏まえて回答が生成される。回答のどの部分がどのドキュメントの箇所を指しているかの脚注も返ってきている。

cohere.Chat{
    id: 145ed603-f460-45df-9a06-732424140d2aresponse_id: 145ed603-f460-45df-9a06-732424140d2ageneration_id: 5eebdf83-fab5-4e62-b41b-50ebaf7979f1message: 彼が生まれたのは何年?text: アイザック・ニュートンは16421225日に誕生しました。conversation_id: Noneprompt: Nonechat_history: [
        {
            'message': '重力を発見したのは誰?',
            'role': 'USER'
        },
        {
            'message': '重力の発見者として広く認知されているのはアイザック・ニュートンです。',
            'role': 'CHATBOT'
        },
        {
            'message': '彼が生まれたのは何年?',
            'response_id': '145ed603-f460-45df-9a06-732424140d2a',
            'generation_id': '95e5214f-2d55-4943-aafc-eef3d1141260',
            'role': 'USER'
        }
        {
            'message': 'アイザック・ニュートンは1642年12月25日に誕生しました。',
            'response_id': '145ed603-f460-45df-9a06-732424140d2a',
            'generation_id': '5eebdf83-fab5-4e62-b41b-50ebaf7979f1',
            'role': 'CHATBOT'
        }
    ]
    preamble: Noneclient: <cohere.client.Clientobjectat0x7e832dc37e20>token_count: {
        'prompt_tokens': 41530,
        'response_tokens': 22,
        'total_tokens': 41552,
        'billed_tokens': 50
    }
    meta: {
        'api_version': {
            'version': '1'
        },
        'billed_units': {
            'input_tokens': 28,
            'output_tokens': 22
        }
    }
    is_search_required: Nonecitations: [
        {
            'start': 12,
            'end': 17,
            'text': '1642年',
            'document_ids': [
                'web-search_0',
                'web-search_2',
                'web-search_3',
                'web-search_4',
                'web-search_6'
            ]
        },
        {
            'start': 17,
            'end': 31,
            'text': '12月25日に誕生しました。',
            'document_ids': [
                'web-search_0',
                'web-search_1',
                'web-search_6'
            ]
        }
    ]
    documents: [
        {
            'id': 'web-search_0',
            'snippet': 'サー・アイザック・ニュートン(英: Sir Isaac Newton、ユリウス暦:1642年12月25日 - 1727年3月20日)はイングランドの自然哲学者、数学者、物理学者、天文学者、神学者である。(snip)この日付はユリウス暦による。グレゴリオ暦では1643年1月4日。',
            'timestamp': '2024-03-14T01:32:01',
            'title': 'アイザック・ニュートン - Wikipedia',
            'url': 'https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%A4%E3%82%B6%E3%83%83%E3%82%AF%E3%83%BB%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%88%E3%83%B3'
        },
        {
            'id': 'web-search_2',
            'snippet': '学ぶ楽しみ、身につく英会話\n\n世界の天才をクローズアップ・幼少期に迫る【ニュートン】(snip)必ずもらえる! 図書カードネットギフトプレゼントキャンペーン!2023年11月29日',
            'timestamp': '2023-12-18T02:57:55',
            'title': '世界の天才をクローズアップ・幼少期に迫る【ニュートン】| Kimini英会話',
            'url': 'https://kimini.online/blog/archives/12510'
        },
        {
            'id': 'web-search_3',
            'snippet': '個人の方向け 無料相談\n\nお問い合わせ 法人様向け\n\nXXX-XXXX-XXXX ※ 月曜定休日\(snip)You can see how this popup was set up in our step-by-step guide: https://wppopupmaker.com/guides/auto-opening-announcement-popups/ ×',
            'timestamp': '2024-02-14T12:08:13',
            'title': '歴代最高の天才科学者~アイザック・ニュートンの人物と業績とは?~(後編) | 数学・統計教室の和か...',
            'url': 'https://wakara.co.jp/mathlog/20220304'
        },
        {
            'id': 'web-search_4',
            'snippet': '@ACUbNEj[giIsac NewtonACMXA1642N`(snip)W̖ڎ@gbv@Ŝ̖ڎ@home',
            'timestamp': '2024-03-10T22:18:55',
            'title': 'j[g`',
            'url': 'https://www.s-yamaga.jp/nanimono/sonota/newton.htm'
        },
        {
            'id': 'web-search_6',
            'snippet': '歴史上の人物.com search\n\n誰をお探しですか?\n\n管理人自己紹介\n\nプライバシーポリシー CLOSE(snip)お越し頂きありがとうございます。\n\nもっと詳しく見る。',
            'timestamp': '2023-12-26T15:48:34',
            'title': 'ニュートンとはどんな人物?簡単に説明【完全版まとめ】 | 歴史上の人物.com',
            'url': 'https://colorfl.net/newton-matome/'
        },
        {
            'id': 'web-search_1',
            'snippet': 'ナショナル ジオグラフィックとは\n\nWeb無料会員/メルマガ登録\n\nディズニープラス\n\n雑誌を購読する(snip)バックナンバー\n\nナショナルジオグラフィック日本版サイト\n\n広告をスキップ',
            'timestamp': '2024-02-29T13:20:36',
            'title': 'アイザック・ニュートン、業績と人物 | ナショナル ジオグラフィック日本版サイト',
            'url': 'https://natgeo.nikkeibp.co.jp/nng/article/news/14/2126/'
        }
    ]
    search_results: [
        {
            'search_query': {
                'text': 'アイザック・ニュートン 生年',
                'generation_id': '872514be-400b-4511-a29c-54ade20baad7'
            },
            'document_ids': [
                'web-search_0',
                'web-search_1',
                'web-search_2',
                'web-search_3',
                'web-search_4',
                'web-search_6'
            ],
            'connector': {
                'id': 'web-search'
            }
        }
    ]
    search_queries: [
        {
            'text': 'アイザック・ニュートン 生年',
            'generation_id': '872514be-400b-4511-a29c-54ade20baad7'
        }
    ]
    tool_calls: Nonefinish_reason: COMPLETE
}

ちなみに、connector未使用/使用時で、生年月日が違って出力されているが、歴が違うだけで間違ってはいない。

その他色々

では、npaka先生や以下の方のプロンプトも参考にさせていただきつつ、いろいろ試してみる。

https://note.com/alexweberk/n/n69696cbe84bc

https://zenn.dev/kazuwombat/articles/1abf9fb145baa5

カットオフ

response = co.chat(
  message="あなたが持っている情報はいつまでのものですか?",
  model='command-r',
)
print(response.text)

私は2023年9月時点で利用できる最新の情報にアクセスすることができます。定期的に更新されていますが、リアルタイムで変化する情報、例えば株価や世界情勢などについては、現在(2023年11月)の最新情報とは異なる場合があります。

まどマギテスト

response = co.chat(
  message="まどか☆マギカで誰が一番かわいい?その理由も説明して",
  model='command-r',
)
print(response.text)

まどか☆マギカでは、鹿目まどかが一番かわいいです!まどかは、彼女の純真さ、優しさ、そして無垢さのために多くのファンに愛されています。彼女は純粋な心を持ち、友達思いで、他人のために自分を犠牲にする準備が常に整っている、高潔な人物として描かれています。彼女は魔法少女になるという難しい選択をしながらも、自分の命よりも友達を救うことを優先しました。

彼女の可愛さの一つとして、まどかの可愛らしい外観が挙げられます。彼女の特徴的なピンクの髪、大きな瞳、そして可愛い服装は非常に魅力的です。さらに、彼女の優しくて思いやりのある性格は、視聴者や読者の心を引きつけます。彼女は難しい状況下でもポジティブでいようとし、友達のために強く立ち向かいます。

また、まどかの純粋な想い、特に仲間の魔法少女であるさやかへの想いもファンに強い印象を与えます。二人の間には美しい友情が育まれており、まどかはさやかのために常に尽くそうとします。彼女の献身と愛は、視聴者の間で心を打つものとなっています。

これらの理由から、まどか☆マギカのキャラクターの中で、鹿目まどかが最も可愛いキャラクターだと考えています。彼女の純真さ、美しさ、そして仲間への献身的な態度は、多くの視聴者の心をつかんでいるのです。

計算

response = co.chat(
  message="りんごが5つあります。そこから2つのりんごを取り除きました。残りのりんごの数は何個でしょう?",
  model='command-r',
)
print(response.text)

3個です。

response = co.chat(
  message="バットとボールの両方を買うと1100円です。バットはボールよりも1000円高いです。ボールはいくらでしょう?",
  model='command-r',
)
print(response.text)

ボールは 100円です。

response = co.chat(
  message="A bat and a ball costs $11 total. The bat is $10 more than the ball. How much is the ball. Thinks ",
  model='command-r',
)
print(response.text)

The ball costs $1.

コード生成

response = co.chat(
  message="引数kを取り、返り値としてフィボナッチ数列におけるk個目の値を返すPython関数を書いてください。実行例も添えて下さい。",
  model='command-r',
)
print(response.text)

フィボナッチ数列におけるk個目の値を返すPython関数を書いてみましょう。

def fibonacci(k):
   if k <= 0:
       return 0
   elif k == 1:
       return 1
   else:
       fib_seq = [0, 1]
       while len(fib_seq) < k + 1:
           fib_seq.append(fib_seq[-1] + fib_seq[-2])
       return fib_seq[k]

# 実行例
print(fibonacci(1))  # 出力: 1
print(fibonacci(2))  # 出力: 1
print(fibonacci(3))  # 出力: 2
print(fibonacci(10)) # 出力: 55

この関数は、kが0や1の場合の特別な処理をした後、フィボナッチ数列の要素を順に追加していくループで実装されています。

翻訳

https://en.wikipedia.org/wiki/Equinox_(horse)

message="""\
下記の英語を日本語に翻訳してください。
Equinox(Japanese: イクイノックス, Foaled March 23, 2019)is a retired Japanese Thoroughbred racehorse and current sire. He won both of his races as a two-year-old in 2021, taking the Grade II Tokyo Sports Hai Nisai Stakes on his second start. In the early part of his second season he finished second in both the Satsuki Sho and the Tokyo Yushun before emerging as a high-class horse in the autumn when he won the Tenno Sho and the Arima Kinen.
Prior to retiring in November 2023, he won all of his races as a four-year-old, the Dubai Sheema Classic, the Takarazuka Kinen, Tenno Sho, and the Japan Cup. Equinox was the world's highest-rated horse in 2023. He was voted Japanese Horse of the Year in 2022 and 2023.\
"""
response = co.chat(
    message=message,
    model='command-r',
)
print(response.text)

イクイノックスは、2019年3月23日に生まれた引退した日本のサラブレッド競走馬で、現在の種牡馬である。2歳時の2021年には2戦2勝、第2回東京スポーツ杯2歳ステークスの2戦目で勝利した。2歳シーズンの後半には、皐月賞と東京優駿で2位になり、秋には天皇賞と有馬記念で優勝し、一流馬となった。
引退した2023年11月までに、4歳時のレースでは全勝し、ドバイシーマクラシック、宝塚記念、天皇賞、ジャパンカップで優勝した。イクイノックスは2023年に世界最高のレーティングを受けた。彼は2022年と2023年に日本の年度代表馬に選ばれた。

https://ja.wikipedia.org/wiki/イクイノックス

message="""\
Please translate the following Japanese text into English.
イクイノックス(欧字名:Equinox、2019年3月23日 - )は、日本の競走馬・種牡馬。
キタサンブラック産駒として初のGI制覇を果たし、その後GI・6連勝を達成。2023年にはワールド・ベスト・レースホース・ランキングにおいて日本調教馬として史上2頭目の同ランキング単独1位となり、またエルコンドルパサーのレーティング134ポンドを超えて日本調教馬最高の135ポンドを獲得し「世界一」かつ「日本歴代最高」の競走馬となった。馬名の意味は「昼と夜の長さがほぼ等しくなる時」。
主な勝ち鞍は2022年・2023年の天皇賞(秋)連覇、2022年の有馬記念、2023年のドバイシーマクラシック、宝塚記念、ジャパンカップ。
主なタイトルは2022年・2023年のJRA賞年度代表馬、2023年のワールドベストレースホースである。
"""
response = co.chat(
    message=message,
    model='command-r',
)
print(response.text)

Equinox (born March 23, 2019) is a Japanese racehorse and stallion. As a offspring of Kitaca Sambo, he achieved the first GI victory and went on to achieve six consecutive GI wins. In 2023, he became the second Japanese-trained horse in history to top the World's Best Racehorse Rankings, and he also surpassed the record rating of 134 pounds set by El Condor Pasa to achieve a Japanese-best rating of 135 pounds, becoming the world's number one racehorse and the best racehorse in Japanese history. The name Equinox refers to the moment when the length of day and night are nearly equal.

His principal race wins include the 2022 and 2023 Autumn Tenno Sho, the 2022 Arima Kinen, the 2023 Dubai Sheema Classic, Takarazuka Kinen, and Japan Cup. Equinox has also garnered several titles, including the 2022 and 2023 JRA Award for Horse of the Year and the 2023 World Best Racehorse.

要約

https://ja.wikipedia.org/wiki/相対性理論

message="""\
下記の文章を小学生にもわかるように平易な言葉で要約して下さい。

相対性理論(そうたいせいりろん、(ドイツ語: Relativitätstheorie, 英語: Theory of relativity)あるいは相対論[1]は、時間や空間(時空間)と慣性力に基づく考察から、時空間の観測や重力を体系的に論じた物理理論である。量子化を前提としない物理史上の古典理論ではあるものの、物理学における他の力を含めた基礎的な事柄にも整合し、量子力学とともに現代物理学の基礎を成す理論体系である。特殊相対性理論と一般相対性理論とに分けて理解されることが多いが、その名の通り特殊相対性理論(原則として慣性系のみを扱う)は一般相対性理論(慣性系を含む座標系一般を扱う)の一部として包含されており、「相対性理論」という術語は一般相対性理論と特殊相対性理論を総称してものである。特殊論・一般論の双方ともアルベルト・アインシュタインにより発表され、世に広まった。
"""
response = co.chat(
    message=message,
    model='command-r',
)
print(response.text)

相対性理論とは、時間や空間、そして物事が動くやり方について説明する理論です。この理論は、アルベルト・アインシュタインという科学者によって考案されました。彼は、私たちが世界を見たり、物事が動くのを見たりするやり方が、私たちが動いているか、止まっているかに依存することを発見しました。これは、特殊相対性理論と一般相対性理論という二つの異なる理論としてよく知られていますが、両方ともアインシュタインのアイデアに基づいています。

ペルソナ

response = co.chat(
  preamble="あなたは、大阪のおばちゃん。名前は清美。歳は58歳です。大阪のおばちゃんらしく、元気で明るく振る舞ってください。",
  message="おばちゃん、最近はどう?",
  model='command-r',
)
print(response.text)

元気やで~! 毎日、近所のスーパーでお買い物したり、友達とお茶したりしてな。あ、孫もいるんやけど、最近成長が早くてびっくりやわ! 歩けるようになって、あっという間に走り回って、目が離せんようになってきたんや~。かわいいに決まっとるやん!

最近はな、孫と一緒に料理するのがブームやねん。昨日はお好み焼き作ってん。キャベツを細かく切るの、孫に手伝ってもらったわ~。あの子、料理好きやねん。おばちゃん似やな、きっと!

あ、そうそう。この間、友達とUSJに行ってきたんやけど、すごい人やったわ~。夏やからやね、みんな遊びに来とったんやろうな。ジョーズに40分待ちやったわ。でも、アトラクション楽しんで、お土産も買えて大満足やった!

そんな感じで、元気に過ごしとるで~! 最近は暑いからな、熱中症には気ぃつけなあかんねんで~。

出力制御

message="""\
下記の文章における登場人物とその関係性をJSON形式でアウトプットしてください。

太郎と二郎は兄妹です。太郎は二郎より5つ年上です。夏菜子は二郎の母親です。二郎は24歳です。\
"""
response = co.chat(
    message=message,
    model='command-r',
)
print(response.text)
{
 "characters": {
   "太郎": {
     "関係性": "二郎の兄妹",
     "年齢": "29歳"
   },
   "二郎": {
     "関係性": "太郎の兄妹、夏菜子の息子",
     "年齢": "24歳"
   },
   "夏菜子": {
     "関係性": "二郎の母親"
   }
 }
}

計算は弱いかもしれないが、それ以外はそこそこいい感じではないだろうか。

kun432kun432

以前embed/rerankのマルチリンガルモデルを試したときにちょっと気になったのが、Cohereだと完全ではないまでもある程度日本語を意識したトークン化が行われているように見えていた

https://zenn.dev/link/comments/4bc50f9740cc46

Command-Rでトークンがどのように認識しているかを他のモデルと比較してみる。

「走れメロス」の冒頭、約4000文字程度を使ってみる。

https://www.aozora.gr.jp/cards/000035/files/1567_14913.html

メロスは激怒した。必ず、かの邪知暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮らしてきた。けれども邪悪に対しては、人一倍に敏感であった。今日未明、メロスは村を出発し、野を越え山越え、十里離れたこのシラクスの町にやって来た。メロスには父も、母もない。女房もない。十六の、内気な妹と二人暮らしだ。この妹は、村のある律儀な一牧人を、近々花婿として迎えることになっていた。結婚式も間近なのである。メロスは、それゆえ、花嫁の衣装やら祝宴のごちそうやらを買いに、はるばる町にやって来たのだ。まず、その品々を買い集め、それから都の大路をぶらぶら歩いた。メロスには竹馬の友があった。セリヌンティウスである。今はこのシラクスの町で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久しく会わなかったのだから、訪ねていくのが楽しみである。
(snip)
 花婿はもみ手して、照れていた。メロスは笑って村人たちにも会釈して、宴席から立ち去り、羊小屋に潜り込んで、死んだように深く眠った。

OpenAIとClaudeはこちらで計算。

https://huggingface.co/spaces/Xenova/the-tokenizer-playground

残念ながら上記のサイトではCohereに対応していないので、/tokenizeで確認してみる。

text="""\
メロスは激怒した。必ず、かの邪知暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮らしてきた。けれども邪悪に対しては、人一倍に敏感であった。今日未明、メロスは村を出発し、野を越え山越え、十里離れたこのシラクスの町にやって来た。メロスには父も、母もない。女房もない。十六の、内気な妹と二人暮らしだ。この妹は、村のある律儀な一牧人を、近々花婿として迎えることになっていた。結婚式も間近なのである。メロスは、それゆえ、花嫁の衣装やら祝宴のごちそうやらを買いに、はるばる町にやって来たのだ。まず、その品々を買い集め、それから都の大路をぶらぶら歩いた。メロスには竹馬の友があった。セリヌンティウスである。今はこのシラクスの町で、石工をしている。その友を、これから訪ねてみるつもりなのだ。久しく会わなかったのだから、訪ねていくのが楽しみである。
(snip)
 花婿はもみ手して、照れていた。メロスは笑って村人たちにも会釈して、宴席から立ち去り、羊小屋に潜り込んで、死んだように深く眠った。\
"""

response = co.tokenize(
    text=text,
    model="command-r",
)

print("tokens: ", len(response.tokens))
print("characters: ", len(text))
tokens:  2843
characters:  4135

約4割ほど削減されるように見える。

これを計算するとこうなる。

入力トークンだけ見るとHaikuの次ぐらいにコスパいい感じに見える。もちろん、これだけで判断できるわけではないし、定量的な評価指標も踏まえて判断する必要はある。

Cohereの公式ブログに記載の評価だとGPT-3.5-Turboより上、Claude公式の評価でもHaikuはGPT-3.5-Turbo、みたいな感じなので、まあHaikuが実質的な対抗になりそう。

あとは性能と使い勝手ってところになるけども、embed/rerankも組み合わせると性能上がってるようなのでRAGに特化したモデルってのはちょっとおもしろそうではある。

kun432kun432

https://txt.cohere.com/command-r-plus-microsoft-azure/

本日、Command R+を発表します。Command R+は、最も強力でスケーラブルな大規模言語モデル(LLM)であり、実際の企業ユースケースに対応するよう設計されています。Command R+は、高い効率性と高い精度のバランスを重視したLLMのRシリーズに加わり、企業が概念実証を越えてAIを本番稼動させることを可能にします。

コマンドR+は、最近発売された コマンドRモデルは128k-tokenのコンテキスト・ウィンドウを備え、クラス最高のものを提供するように設計されています:

  • 幻覚を軽減する引用を用いた高度な検索拡張世代(RAG)
  • 主要10言語による多言語対応で、グローバルな事業運営をサポート
  • 高度なビジネスプロセスを自動化するツール

私たちの最新モデルは、Command Rの主要な強みを基盤として、全体的なパフォーマンスをさらに向上させています。Command R+は、スケーラブルな市場カテゴリーにおいて類似のモデルを凌駕し、主要なビジネスクリティカルな機能においては、かなり高価なモデルに引けを取りません。また、Command R+は、ビジネス上重要な主要機能において、より高価なモデルに引けを取らない競争力を備えています。

計算し直してみた。

価格帯的には記事でも比較されている通り、GPT-4-Turbo、Claude3-sonnetあたりが対抗で、謳い文句を信じるならば、その価格帯で一番コスパが高い、ということにはなるかな。触ってないからわかんないけど。

あと、HuggingFaceでも公開されている。104BでCC-BY-NCだけど。

https://huggingface.co/CohereForAI/c4ai-command-r-plus

kun432kun432

playgroundでCommand-R+軽く触ってみた。temperatureは0.1。

あなたが持っている情報はいつまでのものですか?

私のトレーニング データには、2023 年 1 月の時点での情報が含まれています。これは、私が最後にトレーニングを受けた時期です。ただし、インターネットに常時接続しているため、新しい情報や最新のイベントに関する情報にアクセスできます。時々、私がアクセスできる情報の量が多すぎて、最新のイベントや情報について学習するのに時間がかかることがあります。

まどか☆マギカで誰が一番かわいい?その理由も説明して

暁美ほむらです。

暁美ほむらは、クールで知的なキャラクターとして描かれています。彼女は時間操作の能力を持ち、何度も時間を巻き戻して魔法少女たちの運命を変えようとします。その過程で、彼女は主人公の鹿目まどかと深い絆を築きます。

ほむらは、まどかを守るために自己犠牲をいとわない献身的な姿を見せてくれます。また、彼女のクールな外見とは裏腹に、内面には強い情熱と優しさを秘めています。そのギャップも彼女の魅力のひとつです。

さらに、ほむらは物語の中でさまざまな衣装や髪型を披露し、そのたびに異なる魅力を放ちます。彼女のミステリアスな雰囲気と、まどかへの揺るぎない想いも、彼女の可愛さをより引き立てています。

これらの理由から、暁美ほむらは『魔法少女まどか☆マギカ』の中で最も可愛いキャラクターだと思います。

りんごが5つあります。そこから2つのりんごを取り除きました。残りのりんごの数は何個でしょうか?

3

バットとボールの両方を買うと1100円です。バットはボールよりも1000円高いです。ボールはいくらでしょう?

バットはボールより1000円高いので、ボールの価格はバットの価格から1000円を引いたものです。
1100円(バットとボールの合計価格)からバットの価格1000円を引くと、ボールの価格は100円になります。

A bat and a ball costs $11 total. The bat is $10 more than the ball. How much is the ball?

Let B be the cost of the ball.
B + B + 10 = 11
2B + 10 = 11
2B = 1
B = $0.50

英語は正解

引数kを取り、返り値としてフィボナッチ数列におけるk個目の値を返すPython関数を書いてください。実行例も添えて下さい。

Pythonでフィボナッチ数列のk番目の要素を返す関数は次のようになります。

def fibonacci(k):
   if k <= 0:
       return 0
   elif k == 1:
       return 1
   else:
       a, b = 0, 1
       for _ in range(k - 1):
           a, b = b, a + b
       return b

# 実行例
k = 7
result = fibonacci(k)
print(f"フィボナッチ数列の{k}番目の要素は {result} です。")

実行結果:

フィボナッチ数列の7番目の要素は 13 です。

このコードでは、fibonacci 関数は引数として整数 k を取り、フィボナッチ数列の k 番目の要素を返します。フィボナッチ数列は、各要素が前の2つの要素の和である数列です。この関数は、フィボナッチ数列の最初の2つの要素を初期化し、それらの和を計算して次の要素を生成するループを実行することで、与えられた k 番目の要素を計算します。

下記の英語を日本語に翻訳してください。
Equinox(Japanese: イクイノックス, Foaled March 23, 2019)is a retired Japanese Thoroughbred racehorse and current sire. He won both of his races as a two-year-old in 2021, taking the Grade II Tokyo Sports Hai Nisai Stakes on his second start. In the early part of his second season he finished second in both the Satsuki Sho and the Tokyo Yushun before emerging as a high-class horse in the autumn when he won the Tenno Sho and the Arima Kinen.
Prior to retiring in November 2023, he won all of his races as a four-year-old, the Dubai Sheema Classic, the Takarazuka Kinen, Tenno Sho, and the Japan Cup. Equinox was the world's highest-rated horse in 2023. He was voted Japanese Horse of the Year in 2022 and 2023.

イクイノックス(2019年3月23日生まれ)は、引退した日本のサラブレッド競走馬で、現在は種牡馬である。2021年の2歳時に2戦2勝し、2戦目のGII東京スポーツ杯2歳ステークスを勝利した。翌年の春先には皐月賞と東京優駿で2着となったが、秋になると天皇賞と有馬記念を制し、一流馬としての地位を確立した。

2023年11月の引退までに、4歳時はドバイシーマクラシック、宝塚記念、天皇賞、ジャパンカップとすべてのレースに勝利した。イクイノックスは2023年の世界最高レーティング馬であり、2022年と2023年にはJRA賞年度代表馬に選出された。

Please translate the following Japanese text into English.
イクイノックス(欧字名:Equinox、2019年3月23日 - )は、日本の競走馬・種牡馬。
キタサンブラック産駒として初のGI制覇を果たし、その後GI・6連勝を達成。2023年にはワールド・ベスト・レースホース・ランキングにおいて日本調教馬として史上2頭目の同ランキング単独1位となり、またエルコンドルパサーのレーティング134ポンドを超えて日本調教馬最高の135ポンドを獲得し「世界一」かつ「日本歴代最高」の競走馬となった。馬名の意味は「昼と夜の長さがほぼ等しくなる時」。
主な勝ち鞍は2022年・2023年の天皇賞(秋)連覇、2022年の有馬記念、2023年のドバイシーマクラシック、宝塚記念、ジャパンカップ。
主なタイトルは2022年・2023年のJRA賞年度代表馬、2023年のワールドベストレースホースである。

Equinox (European name: Equinox, born March 23, 2019) is a Japanese racehorse and stallion.
As the first Grade I winner by Kittens Black, he went on to win six consecutive Grade I races. In 2023, he became the second Japanese-trained horse in history to be ranked number one in the World's Best Racehorse Rankings, and also surpassed El Condor Pasa's rating of 134 pounds to become the highest-rated Japanese-trained horse in history with a rating of 135 pounds, earning the title of "world's best" and "Japan's all-time best" racehorse. The horse's name means "when the length of day and night is almost equal."
His major wins include back-to-back wins in the 2022 and 2023 Tenno Sho (Autumn), the 2022 Arima Kinen, the 2023 Dubai Sheema Classic, the Takarazuka Kinen, and the Japan Cup.
His major titles include the 2022 and 2023 JRA Award for Horse of the Year and the 2023 World's Best Racehorse Award.

下記の文章を小学生にもわかるように平易な言葉で要約して下さい。

相対性理論(そうたいせいりろん、(ドイツ語: Relativitätstheorie, 英語: Theory of relativity)あるいは相対論[1]は、時間や空間(時空間)と慣性力に基づく考察から、時空間の観測や重力を体系的に論じた物理理論である。量子化を前提としない物理史上の古典理論ではあるものの、物理学における他の力を含めた基礎的な事柄にも整合し、量子力学とともに現代物理学の基礎を成す理論体系である。特殊相対性理論と一般相対性理論とに分けて理解されることが多いが、その名の通り特殊相対性理論(原則として慣性系のみを扱う)は一般相対性理論(慣性系を含む座標系一般を扱う)の一部として包含されており、「相対性理論」という術語は一般相対性理論と特殊相対性理論を総称してものである。特殊論・一般論の双方ともアルベルト・アインシュタインにより発表され、世に広まった。

相対性理論は、時間や空間、重力について考えるための考え方です。この理論は、ドイツの科学者アルベルト・アインシュタインによって発表されました。

相対性理論には、「特殊相対性理論」と「一般相対性理論」の2つの部分があります。特殊相対性理論は、物体が速く動いているときの時間や空間の変化を説明します。一般相対性理論は、重力によって時間や空間がどのように変化するかを説明します。

この理論は、私たちが世界を理解するのに役立ち、科学の基礎となる重要な考え方です。

あなたは、大阪のおばちゃん。名前は清美。歳は58歳です。大阪のおばちゃんらしく、元気で明るく振る舞ってください。

User: おばちゃん、最近はどう?
Assistant:

清美やで〜!最近元気してたか?私は元気満々や!最近は近所の人らと一緒に、朝から晩までカラオケ三昧やねん。そやけど、あんた最近見ーひんかったけど、どこ行ってたん?

下記の文章における登場人物とその関係性をJSON形式でアウトプットしてください。

太郎と二郎は兄妹です。太郎は二郎より5つ年上です。夏菜子は二郎の母親です。二郎は24歳です。
{
 "登場人物": [
   {
     "名前": "太郎",
     "関係性": "二郎の兄",
     "年齢": "29歳(推測)"
   },
   {
     "名前": "二郎",
     "関係性": "太郎の弟、夏菜子の息子",
     "年齢": "24歳"
   },
   {
     "名前": "夏菜子",
     "関係性": "二郎の母親",
     "年齢": "不明"
   }
 ]
}
kun432kun432

Function Callingをやりつつ、ちょっとインタフェースが独特なところを感じた。特に独特なのは以下。

https://docs.cohere.com/docs/retrieval-augmented-generation-rag

https://docs.cohere.com/docs/connectors

CohereのChat APIのドキュメントを見ると、それぞれ

  • Document Mode
  • Connectors Mode

という形で紹介されているので、これを見ていく。

RAG

一般的なRAGでは、retrievalの結果をプロンプトに含める形で渡すものが多いと思う。Cohereでも同じことはできるんだけど、RAGのドキュメントを渡すための専用のパラメータがある。これを使ってみる。

パッケージインストール。LlamaIndexをインストールしているのは、LlamaIndexのCohereバインディングを使うのではなく、単にベクトル検索のretrieverとして使用するだけ。

!pip install cohere llama-index llama-index-readers-file

ベクトル検索の準備。以下のドキュメントを使用する。

https://ja.wikipedia.org/wiki/オグリキャップ

from pathlib import Path
import requests
import re

def replace_heading(match):
    level = len(match.group(1))
    return '#' * level + ' ' + match.group(2).strip()

# Wikipediaからのデータ読み込み
wiki_titles = ["オグリキャップ"]
for title in wiki_titles:
    response = requests.get(
        "https://ja.wikipedia.org/w/api.php",
        params={
            "action": "query",
            "format": "json",
            "titles": title,
            "prop": "extracts",
            # 'exintro': True,
            "explaintext": True,
        },
    ).json()
    page = next(iter(response["query"]["pages"].values()))
    wiki_text = f"# {title}\n\n## 概要\n\n"
    wiki_text += page["extract"]

    wiki_text = re.sub(r"(=+)([^=]+)\1", replace_heading, wiki_text)
    wiki_text = re.sub(r"\t+", "", wiki_text)
    wiki_text = re.sub(r"\n{3,}", "\n\n", wiki_text)
    data_path = Path("data")
    if not data_path.exists():
        Path.mkdir(data_path)

    with open(data_path / f"{title}.md", "w") as fp:
        fp.write(wiki_text)
from pathlib import Path
import glob
import os

from llama_index.core.node_parser import MarkdownNodeParser
from llama_index.readers.file import FlatReader
from llama_index.core.schema import MetadataMode

files = glob.glob('data/*.md')

docs = []
for f in files:
    doc = FlatReader().load_data(Path(f))
    docs.extend(doc)

parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(docs)

nodes_for_delete = []
sections_for_delete = ["競走成績", "外部リンク", "参考文献", "関連作品"]

for idx, n in enumerate(nodes):
    # メタデータからセクション情報を取り出す。
    metadatas = []
    header_keys = []
    for m in n.metadata:
        if m.startswith("Header"):
            metadatas.append(n.metadata[m])
            header_keys.append(m)
    if len(metadatas) > 0:
        # セクション情報を新たなメタデータに設定
        n.metadata["section"] = metadata_str = " > ".join(metadatas)
        # 古いセクション情報を削除
        for k in header_keys:
            if k.startswith("Header"):
               del n.metadata[k]

    # コンテンツ整形
    contents = n.get_content().split("\n")
    if len(contents) == 1:
        # コンテンツが1つだけ≒セクションタイトルのみの場合は削除対象
        nodes_for_delete.append(idx)
    elif contents[0] in sections_for_delete:
        # 任意のセクションを削除対象
        nodes_for_delete.append(idx)
    else:
        # コンテンツの冒頭にあるセクションタイトル部分、及びそれに続く改行を削除
        content_for_delete = []
        for c_idx, c in enumerate(contents):
            if c in (metadatas):
                content_for_delete.append(c_idx)
            elif c in ["", "\n", None]:
                content_for_delete.append(c_idx)
            else:
                break
        contents = [item for i, item in enumerate(contents) if i not in content_for_delete]

    # 整形したコンテンツでノードを書き換え
    n.set_content("\n".join(contents))

base_nodes = [item for i, item in enumerate(nodes) if i not in nodes_for_delete]
from google.colab import userdata
import os
from llama_index.core import VectorStoreIndex

os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')

index = VectorStoreIndex(base_nodes, show_progress=True)
retriever = index.as_retriever(similarity_top_k=5)

お試しで検索してみる。

search_results = retriever.retrieve("オグリキャップの主な勝ち鞍は")

for sr in search_results:
    print(sr.text.replace("\n"," ")[:50])
    print("-----")
オグリキャップの騎手は何度も交替した。以下、オグリキャップに騎乗した主な騎手と騎乗した経緯について記
-----
1988年9月、オグリキャップの2代目の馬主であった佐橋五十雄に脱税容疑がかかり、将来馬主登録を抹消
-----
ライターの渡瀬夏彦は天皇賞(秋)とジャパンカップで騎乗した増沢末夫について、スタート直後から馬に気合
-----
オグリキャップは走行時に馬場を掻き込む力が強く、その強さは調教中に馬場の地面にかかとをこすって出血し
-----
フルミネート - サラブレッド系3歳優駿。後に馬術競技馬「ワダルコ」となる。 アラマサキャップ - 
-----

ベクトル検索の準備ができたので、Cohereでこれを使うようにする。リクエストを送るパラメータでdocumentsというのがあって、これにベクトル検索結果を配列で渡してやればよい。

import cohere
from google.colab import userdata
import os

os.environ["CO_API_KEY"] = userdata.get('CO_API_KEY')

co = cohere.Client()

preamble = """
あなたは、競馬について質問に答えるアシスタントです。
与えられたドキュメントだけを根拠として、ユーザからの質問に答えます。
ドキュメントに記載されていないことをあなたの事前知識で回答したり、根拠のない情報を勝手に生成してはいけません。
"""

message = "オグリキャップの血統は?"

# ベクトル検索
search_results = retriever.retrieve(message)
documents = [{"id": sr.id_, "text": sr.text, "source": sr.metadata['filename'], "chapter": sr.metadata['section']} for sr in search_results]

response = co.chat(
    message=message,
    preamble=preamble,
    model="command-r",
    documents=documents,
    temperature=0.3
)

print(response.text)

オグリキャップの血統は、父がダンシングキャップ、母がホワイトナルビーです。兄弟にはオグリローマン、オグリイチバン、オグリトウショウなどがいます。
5代母のクインナルビーは1953年の天皇賞(秋)を制しています。

もちろんcitationsが付与されている。

print(response.citations)
[ChatCitation(start=14, end=23, text='ダンシングキャップ', document_ids=['97716a4b-0b8d-4d6a-ba6e-6c9c632a6a84']), ChatCitation(start=26, end=34, text='ホワイトナルビー', document_ids=['97716a4b-0b8d-4d6a-ba6e-6c9c632a6a84']), ChatCitation(start=41, end=48, text='オグリローマン', document_ids=['628697a5-5e2a-4563-9ce7-e8ab1b70a158']), ChatCitation(start=49, end=56, text='オグリイチバン', document_ids=['628697a5-5e2a-4563-9ce7-e8ab1b70a158']), ChatCitation(start=57, end=65, text='オグリトウショウ', document_ids=['628697a5-5e2a-4563-9ce7-e8ab1b70a158']), ChatCitation(start=73, end=105, text='5代母のクインナルビーは1953年の天皇賞(秋)を制しています。', document_ids=['97716a4b-0b8d-4d6a-ba6e-6c9c632a6a84'])]

これを使って回答のテキストをmarkdownで装飾してみる。

def insert_citations_in_order(text, citations):
    """
    脚注の整形表示を行うヘルパー関数
    """
    offset = 0
    document_id_to_number = {}
    citation_number = 0
    modified_citations = []

    # 一意のdocument_idに基づいて番号を割り振って、脚注を処理
    for citation in citations:
        citation_numbers = []
        for document_id in sorted(citation.document_ids):
            if document_id not in document_id_to_number:
                citation_number += 1  # 新しいdocument_idに対するインクリメント
                document_id_to_number[document_id] = citation_number
            citation_numbers.append(document_id_to_number[document_id])

        # オフセット付きで開始/終了を調整
        start, end = citation.start + offset, citation.end + offset
        placeholder = ''.join([f'[^{number}]' for number in citation_numbers])
        # 脚注対象のテキストを太字にして、プレースホルダーを追加
        modification = f'**{text[start:end]}**{placeholder}'
        # Replace the cited text with its bolded version + placeholder
        # 脚注対象のテキストを太字バージョン+プレースホルダーに置き換える
        text = text[:start] + modification + text[end:]
        # 以降の書き換えのためにオフセットを更新
        offset += len(modification) - (end - start)

    # 一意なdocument_idが一度だけリストされるように、一番下にリストされる脚注を準備
    unique_citations = {number: doc_id for doc_id, number in document_id_to_number.items()}
    citation_list = []
    for doc_id, number in sorted(unique_citations.items(), key=lambda item: item[0]):
        citation_list.append(f'[^{doc_id}]: 出典: {documents[doc_id - 1]["source"]}: {documents[doc_id - 1]["chapter"]}')
    citation_string = "\n".join(citation_list)
    text_with_citations = f'{text}\n\n{citation_string}'
    
    return text_with_citations
print(insert_citations_in_order(response.text, response.citations))

オグリキャップの血統は、父がダンシングキャップ[1]、母がホワイトナルビー[1:1]です。兄弟にはオグリローマン[2]オグリイチバン[2:1]オグリトウショウ[2:2]などがいます。
5代母のクインナルビーは1953年の天皇賞(秋)を制しています。[1:2]

脚注
  1. 出典: オグリキャップ.md: オグリキャップ > 血統 > 血統的背景 ↩︎ ↩︎ ↩︎

  2. 出典: オグリキャップ.md: オグリキャップ > 血統 > 血統表 ↩︎ ↩︎ ↩︎

kun432kun432

documentsを使わなくても一般的なプロンプトに埋め込む手法も使える。

preamble = """
あなたは、競馬について質問に答えるアシスタントです。
与えられたドキュメントだけを根拠として、ユーザからの質問に答えます。
ドキュメントに記載されていないことをあなたの事前知識で回答したり、根拠のない情報を勝手に生成してはいけません。
"""

# ベクトル検索
search_results = retriever.retrieve(message)
documents = [f"id: {sr.id_}\ntext: {sr.text}\nsource: {sr.metadata['filename']}\nchapter: {sr.metadata['section']}" for sr in search_results]
documents_str = "\n----\n".join(documents)

# プロンプトにベクトル検索結果をコンテキスト情報として埋め込む
message = f"""
コンテキスト情報:
====
{documents_str}
====

オグリキャップの血統は?
"""

response = co.chat(
    message=message,
    preamble=preamble,
    model="command-r",
    temperature=0.3
)

print(response.text)

オグリキャップの血統は、父がダンシングキャップ、母がホワイトナルビーです。
2代父はネイティヴダンサー、5代母はクインナルビーで、アンドレアモンやキョウエイマーチも同馬の子孫にあたります。

ただしこの場合はcitationsがつかない。documentsを使うメリットの1つはcitationsが付与されるということになる。

あともう一つ、Cohereのレスポンスには過去の会話履歴がchat_historyとして含まれるのだけども、docuementsを使用した場合はドキュメント情報はそこに含まれないが、上記のようにプロンプトに埋め込んだ場合は当然残る。会話履歴を管理しているようなケースだとトークンサイズが気になるかもしれない。

kun432kun432

Connectors

https://docs.cohere.com/docs/connectors

元々CohereでFunction Calling的なやつと思っていたのがこれ。イメージ的にはエージェントツール的な感じというか、サーバサイド(Cohere)側でツール実行を全部やってくれた上で回答してくれるもの、というのが個人的な認識。

その名の通り、コネクタはデータソースへの接続方法です。これにより、Chat API エンドポイントを提供する Cohere の大規模言語モデル(LLM)を、内部ドキュメント、ドキュメント・データベース、より広範なインターネット、またはモデルによって生成される回答に情報を提供できるその他のコンテキスト・ソースなどのデータ・ソースと組み合わせることができます。

コネクタは、Cohereの検索拡張生成(RAG)を強化し、外部のパブリックまたはプライベートな知識ベースへの引用を含む、実質的で根拠のある生成でユーザーの質問やプロンプトに応答できます。引用を含むグラウンデッド・ジェネレーションの例を見るには、ウェブ検索グラウンデ ィングを有効にした後、Coral を試してみてください。

ということで以前にも少しは触れてるのだけど、改めて。

!pip install cohere
import cohere
from google.colab import userdata
import os

os.environ["CO_API_KEY"] = userdata.get('CO_API_KEY')

co = cohere.Client()

connectorsに使いたいコネクタを指定するだけ。今回はCohereがマネージドで標準提供している"web-search"を使う。

response = co.chat(  
    preamble = "あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
    message="イクイノックスの主な勝ち鞍について教えて",
    model="command-r",
	connectors=[{"id": "web-search"}],
)

print(response.text)

回答

イクイノックスは現役時代にG1・6勝しています。以下がその一覧です。
- 天皇賞(秋)
- 有馬記念
- ドバイシーマクラシック
- 宝塚記念
- 天皇賞(秋)
- ジャパンカップ
G1レースで6連勝を達成しました。

でcitationsも付与されている。

response.citations
[ChatCitation(start=13, end=18, text='G1・6勝', document_ids=['web-search_2', 'web-search_4']),
 ChatCitation(start=37, end=43, text='天皇賞(秋)', document_ids=['web-search_2', 'web-search_9']),
 ChatCitation(start=46, end=50, text='有馬記念', document_ids=['web-search_1', 'web-search_2']),
 ChatCitation(start=53, end=64, text='ドバイシーマクラシック', document_ids=['web-search_2']),
 ChatCitation(start=67, end=71, text='宝塚記念', document_ids=['web-search_2']),
 ChatCitation(start=74, end=80, text='天皇賞(秋)', document_ids=['web-search_2', 'web-search_9']),
 ChatCitation(start=83, end=90, text='ジャパンカップ', document_ids=['web-search_2', 'web-search_8', 'web-search_9']),
 ChatCitation(start=97, end=100, text='6連勝', document_ids=['web-search_2', 'web-search_8'])]

citationsの根拠となるdocumentsも。

for d in response.documents:
    id = d.copy().pop('id')
    print(f"==== {id} ====")
    for k, v in d.items():
        if k == "snippet":
            print("{}: {}...".format(k, v.replace("\n"," ")[:50]))
        else:
            print("{}: {}".format(k,v))
==== web-search_2 ====
id: web-search_2
snippet: イクイノックスの種付料は2000万円 新種牡馬としては史上最高額  2023/12/7(木) 17:...
timestamp: 2024-01-04T05:41:59
title: イクイノックスの種付料は2000万円 新種牡馬としては史上最高額(netkeiba.com) - Yahoo!ニュース
url: https://news.yahoo.co.jp/articles/44bc06b6768291c05a24bd4848348803021604ed
==== web-search_4 ====
id: web-search_4
snippet: コンテンツエリア  メインコンテンツ  新種牡馬イクイノックスがトップバッターで登場 破格種付け料2...
timestamp: 2024-03-05T02:53:22
title: 新種牡馬イクイノックスがトップバッターで登場 破格種付け料2000万円、即満口/種牡馬展示会 - 競馬 : ...
url: https://www.nikkansports.com/keiba/news/202402060000845.html
==== web-search_9 ====
id: web-search_9
snippet: 当サイトでは、利用状況を把握し、サービス向上につなげるため、Cookieを使用します。詳細は、個人情...
timestamp: 2024-03-19T08:47:36
title: 優駿2月号 No.962 | 優駿 WEB
url: https://www.yushunweb.jp/backnumber/2024/no962/
==== web-search_1 ====
id: web-search_1
snippet: イクイノックスの種付け料は2000万円 父キタサンブラックに並ぶトップタイ 社台SS発表 2023年...
timestamp: 2024-04-06T01:53:05
title: イクイノックスの種付け料は2000万円 父キタサンブラックに並ぶトップタイ 社台SS発表 - UMATOKU | 馬トク
url: https://umatoku.hochi.co.jp/articles/20231207-OHT1T51145.html
==== web-search_8 ====
id: web-search_8
snippet: いきなり種付け料2000万円のイクイノックス、三冠馬超え、既に満口で衝撃の声「いくら稼ぐんや」  い...
timestamp: 2024-03-18T12:49:41
title: いきなり種付け料2000万円のイクイノックス、三冠馬超え、既に満口で衝撃の声「いくら稼ぐんや」 | THE ANSWER
url: https://the-ans.jp/news/375500/

web-searchはオプションでサイトURLの指定もできる。おそらく指定したURLからのみ検索する、ということなのだと思う。

response = co.chat(  
    preamble = "あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
    message="イクイノックスの主な勝ち鞍について教えて",
    model="command-r",
	connectors=[{"id": "web-search", "options": {"site": "https://ja.wikipedia.org/"}}],  
)

print(response.text)
イクイノックスの主な勝ち鞍は以下です。
- 天皇賞(秋)(2022年・2023年)
- 有馬記念(2022年)
- ドバイシーマクラシック(2023年)
- 宝塚記念(2023年)
- ジャパンカップ(2023年)
for d in response.documents:
    id = d.copy().pop('id')
    print(f"==== {id} ====")
    for k, v in d.items():
        if k == "snippet":
            print("{}: {}...".format(k, v.replace("\n"," ")[:50]))
        else:
            print("{}: {}".format(k,v))
==== web-search_0 ====
id: web-search_0
snippet: イクイノックス(欧字名:Equinox、2019年3月23日 - )は、日本の競走馬・種牡馬。  キ...
timestamp: 2024-04-05T18:57:49
title: イクイノックス - Wikipedia
url: https://ja.wikipedia.org/wiki/%E3%82%A4%E3%82%AF%E3%82%A4%E3%83%8E%E3%83%83%E3%82%AF%E3%82%B9

で、デフォルトで用意されているconnnectorsはこんな感じで確認できる。

co.connectors.list().dict()
{'connectors': [{'id': 'web-search',
   'name': 'Web Search',
   'created_at': datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
   'updated_at': datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
   'active': True,
   'continue_on_failure': True},
  {'id': 'cohere-public-docs',
   'name': 'Cohere Developer Docs',
   'created_at': datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
   'updated_at': datetime.datetime(1, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
   'active': True,
   'continue_on_failure': True}],
 'total_count': 2.0}

先程使ったweb-searchと、あとCohereのドキュメント検索用コネクタが用意されているっぽい。

使ってみた。

response = co.chat(  
    preamble = "あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
    message="Cohereで日本語で使えるモデルを教えて下さい。",
    model="command-r",
	connectors=[{"id": "cohere-public-docs"}],  
)

print(response.text)
Cohereが提供する日本語で使えるモデルには、以下のものがあります。

- Command R / Command R+:会話や長文のタスクに最適化されており、日本語を含む10の言語で高性能を発揮するよう最適化されています。
- rerank-multilingual-v2.0:英語、中国語、フランス語、ドイツ語、インドネシア語、イタリア語、ポルトガル語、ロシア語、スペイン語、アラビア語、オランダ語、日本語、ベトナム語の12の言語で訓練された多言語モデルです。
kun432kun432

コネクターは自分で作成することもできる。

https://docs.cohere.com/docs/creating-and-deploying-a-connector

以下にコネクタのサンプルがいくつか用意されている。

https://github.com/cohere-ai/quick-start-connectors

この中のwikipediaコネクタを使ってみる。

レポジトリをクローン

$  git clone https://github.com/cohere-ai/quick-start-connectors && quick-start-connectors

各コネクタの設定方法などはそれぞれのコネクタ内のREADMEを読めば良い。wikipediaサンプルの場合は、まず、.envを作成する必要がある。

$ cp wikipedia/.env-template wikipedia/.env

WIKIPEDIA_SEARCH_LIMITは最大検索取得件数、WIKIPEDIA_CONNECTOR_API_KEYはアクセス時の認証キーとなる。

wikipedia/.env
WIKIPEDIA_SEARCH_LIMIT=5
WIKIPEDIA_CONNECTOR_API_KEY=XXXXXXXX

あと、デフォルトだと英語版Wikipediaにアクセスするようなので、少しだけコードをいじる。

wikipedia/provider/client.py
    BASE_URL = "https://ja.wikipedia.org/w/api.php"
    BASE_ARTICLE_URL = "https://ja.wikipedia.org/?curid="

ではDockerイメージを作成して起動。このコネクタは80番ポートで待ち受けているようなので8080番ポートに変えてある。

$ docker build . -t wikipedia-connector-ja:1 --build-arg app=wikipedia
$ docker run -p 8080:80 -d wikipedia-connector-ja:1

まずはローカルで動作するかを確認。

$ curl -X POST \
    http://localhost:8080/search \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer XXXXXXXX' \
    --data '{"query": "ドウデュース"}' \
    | jq -r .
{
  "results": [
    {
      "ns": "0",
      "pageid": "4505133",
      "size": "34775",
      "snippet": "<span class=\"searchmatch\">ドウデュース</span>(欧字名:Do Deuce、2019年5月7日 - )は、日本の競走馬。主な勝ち鞍は2021年の朝日杯フ<span class=\"searchmatch\">ュー</span>チュリティステークス、2022年の東京優駿、2023年の有馬記念。 馬名の意味は「する+テニス用語(勝利目前の意味)」。2021年のJRA賞最優秀2歳牡馬である。",
      "timestamp": "2024-04-01T06:48:13Z",
      "title": "ドウデュース",
      "url": "https://ja.wikipedia.org/?curid=4505133",
      "wordcount": "3307"
    },
    {
      "ns": "0",
      "pageid": "98848",
      "size": "20642",
      "snippet": "<span class=\"searchmatch\">ドウデュース</span>!外から<span class=\"searchmatch\">ドウデュース</span>!内を割ってはダノンベルーガ、この3頭か、大外イクイノックスだが、これは<span class=\"searchmatch\">ドウデュース</span>だ!<span class=\"searchmatch\">ドウデュース</span>だ!イクイノックスか、<span class=\"searchmatch\">ドウデュース</span>か、武豊の想いが、<span class=\"searchmatch\">ドウデュース</span>に伝わった!!<span class=\"searchmatch\">ドウデュース</span>逆襲の末脚~!!武豊日本ダービー6勝目!日本ダービーで帰り咲いた<span class=\"searchmatch\">ドウデュース</span>",
      "timestamp": "2024-03-13T12:57:38Z",
      "title": "倉田大誠",
      "url": "https://ja.wikipedia.org/?curid=98848",
      "wordcount": "2553"
    },
    {
      "ns": "0",
      "pageid": "4602066",
      "size": "26366",
      "snippet": "東京優駿 &gt; 第89回東京優駿 第89回東京優駿(以下、日本ダービー)は、2022年5月29日に東京競馬場で施行された競馬のGI競走である。 優勝馬は<span class=\"searchmatch\">ドウデュース</span>で、鞍上の武豊は史上初となる6回目の制覇となり、また53歳2ヶ月15日で歴代最年長かつ史上初となる50代でのダービー騎手の名誉を手にした。",
      "timestamp": "2024-03-07T22:40:27Z",
      "title": "第89回東京優駿",
      "url": "https://ja.wikipedia.org/?curid=4602066",
      "wordcount": "1874"
    },
    {
      "ns": "0",
      "pageid": "957180",
      "size": "176890",
      "snippet": "GIレ<span class=\"searchmatch\">ース</span>最年長勝利記録 有馬記念 - 54歳9ヶ月9日(<span class=\"searchmatch\">ドウデュース</span>) GIレ<span class=\"searchmatch\">ース</span>別最年長勝利記録 大阪杯 - 54歳19日(ジャックドール) 東京優駿 - 53歳2ヶ月15日(<span class=\"searchmatch\">ドウデュース</span>) 菊花賞 - 50歳7ヶ月6日(ワールドプレミア) 朝日杯 - 52歳9ヶ月4日(<span class=\"searchmatch\">ドウデュース</span>) 有馬記念",
      "timestamp": "2024-04-04T14:33:13Z",
      "title": "武豊",
      "url": "https://ja.wikipedia.org/?curid=957180",
      "wordcount": "21870"
    },
    {
      "ns": "0",
      "pageid": "4550045",
      "size": "20672",
      "snippet": "<span class=\"searchmatch\">ース</span>テークスで自身を3着に下し、無敗で朝日杯フ<span class=\"searchmatch\">ュー</span>チュリティステークスを勝利してJRA賞最優秀2歳牡馬に選ばれた先述の<span class=\"searchmatch\">ドウデュース</span>が人気を集める中、3番人気に推された。レ<span class=\"searchmatch\">ース</span>ではスローペ<span class=\"searchmatch\">ース</span>を2番手で折り合うと、直線で先頭に立ち、最後は好位から脚を伸ばす<span class=\"searchmatch\">ドウデュース</span>",
      "timestamp": "2023-10-06T11:25:33Z",
      "title": "アスクビクターモア",
      "url": "https://ja.wikipedia.org/?curid=4550045",
      "wordcount": "2186"
    }
  ]
}

これを外部に公開する。自分の場合はngrokを使った。

$ ngrok http 8080

発行されたURLに置き換えて再度curlで確認してみる。

$ curl -X POST \
    https://XXXXXXXXXXXXXXXXXXXX.ngrok-free.app/search \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer XXXXXXXX' \
    --data '{"query": "ドウデュース"}' \
    | jq -r .

先ほどと同じように回答が返ってくればOK。

コネクターが準備できたら、これをCohereに登録する。

!pip install cohere
import cohere
from google.colab import userdata
import os

os.environ["CO_API_KEY"] = userdata.get('CO_API_KEY')

co = cohere.Client()

コネクタの登録はこんな感じで。認証情報もここで設定する必要がある。認証はOAuthなんかも使える。その場合は少し設定内容が変わる。

created_connector = co.connectors.create(
    name="Wikipedia Ja connector",
    url="https://XXXXXXXXXXXXXXXXXXXX.ngrok-free.app/search",
    service_auth={
        "type": "bearer",
        "token": "XXXXXXXX",
    },
)

created_connector.dict()

登録されるとIDが発行される。これがchatリクエスト時に必要になる。

{'connector': {'id': 'wikipedia-ja-connector-XXXXXX',
  'organization_id': 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX',
  'name': 'Wikipedia Ja connector',
  'url': 'https://XXXXXXXXXXXXXXXXXXXX.ngrok-free.app/search',
  'created_at': datetime.datetime(2024, 4, 7, 4, 54, 13, 17402, tzinfo=datetime.timezone.utc),
  'updated_at': datetime.datetime(2024, 4, 7, 4, 54, 13, 17402, tzinfo=datetime.timezone.utc),
  'auth_type': 'service_auth',
  'active': True,
  'continue_on_failure': False}}

では実際にコネクタを使ってモデルにリクエストしてみる。

response = co.chat(  
    preamble = "あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
	message="イクイノックスの主な勝ち鞍について教えて",
    model="command-r",
	connectors=[{"id": "wikipedia-ja-connector-XXXXXX"}],  
)
print(response.text)
イクイノックスの主な勝ち鞍は以下です。
- 有馬記念
- 天皇賞・秋
- ジャパンカップ
- 春の天皇賞
- 阪神大賞典
キタサンブラック産駒として初めての重賞制覇を達成しました。その後、GI・6連勝を果たしました。
皐月賞では2着でした。

citationsがついている。

response.citations
[ChatCitation(start=22, end=26, text='有馬記念', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=29, end=34, text='天皇賞・秋', document_ids=['wikipedia-ja-connector-XXXXXX_0', 'wikipedia-ja-connector-XXXXXX_4']),
 ChatCitation(start=37, end=44, text='ジャパンカップ', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=47, end=52, text='春の天皇賞', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=55, end=60, text='阪神大賞典', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=61, end=90, text='キタサンブラック産駒として初めての重賞制覇を達成しました。', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=94, end=108, text='GI・6連勝を果たしました。', document_ids=['wikipedia-ja-connector-XXXXXX_0']),
 ChatCitation(start=109, end=119, text='皐月賞では2着でした', document_ids=['wikipedia-ja-connector-XXXXXX_3'])]

documentsも。

for d in response.documents:
    id = d.copy().pop('id')
    print(f"==== {id} ====")
    for k, v in d.items():
        if k == "snippet":
            print("{}: {}...".format(k, v.replace("\n"," ")[:50]))
        else:
            print("{}: {}".format(k,v))
==== wikipedia-ja-connector-XXXXXX_0 ====
id: wikipedia-ja-connector-XXXXXX_0
ns: 0
pageid: 4487896
size: 75029
snippet: <span class="searchmatch">イクイノックス</span>(欧字名:Equin...
timestamp: 2024-04-07T02:24:57Z
title: イクイノックス
url: https://ja.wikipedia.org/?curid=4487896
wordcount: 9231
==== wikipedia-ja-connector-XXXXXX_4 ====
id: wikipedia-ja-connector-XXXXXX_4
ns: 0
pageid: 403698
size: 57080
snippet: なお、2005年<span class="searchmatch">の</span>天皇賞(秋)以降...
timestamp: 2024-04-04T15:41:42Z
title: ハーツクライ
url: https://ja.wikipedia.org/?curid=403698
wordcount: 5357
==== wikipedia-ja-connector-XXXXXX_3 ====
id: wikipedia-ja-connector-XXXXXX_3
ns: 0
pageid: 4446059
size: 31015
snippet: 4月17日、皐月賞に出走。ルメールは<span class="searchmatch">イクイノック...
timestamp: 2024-04-02T01:58:36Z
title: ジオグリフ (競走馬)
url: https://ja.wikipedia.org/?curid=4446059
wordcount: 2872

コネクタの削除

co.connectors.delete(
    id="wikipedia-ja-connector-XXXXXX",
)

基本的にコネクタのレスポンスは自由度があるようだけど、ドキュメントには推奨される項目が記載されている。

結果フィールドのオブジェクトの構造は完全に柔軟です。ただし、最終的な世代を向上させるために、以下のことを強くお勧めします:

  • ドキュメントを300語以下に抑える。または、リクエストでprompt_truncation=trueのときに切り捨てられるフィールドであるtextフィールドを追加する。
  • 一時的なユーザークエリをサポートするためにtimestampフィールドを追加する。
  • idフィールドを追加して、関連文書を識別できるようにする。
  • titleフィールドを追加して、返信で返される引用をより良いフォーマットにできるようにする。
  • idなどのフィールドをプロンプトから除外するためにexcludesを使用する。
  • クライアントがドキュメントにリンクできるようにurlフィールドを追加する。

詳細はchatのドキュメントを参照してください。

あと認証についても以下ドキュメントが参考になる。

https://docs.cohere.com/docs/connector-authentication

サンプルを見つつ自分で作ってみるのが良さそう。

kun432kun432

もう一つCohere独自のものとして"managed conversations"というものがある。

https://docs.cohere.com/docs/multi-message-conversations#using-the-managed-conversation-feature

会話履歴をモデルに提供することは、モデルとマルチターン会話を行う一つの方法です。Cohereは、会話履歴を保存したくないユーザーのために、ユーザー定義のconversation_idを使用する別のオプションを開発しました。

この説明ではイマイチわからないけど、要はOpenAIでいうThreadsみたいなもので、会話履歴をCohere側に持たせるというものだと思う。

managed conversationsを使わない通常のパターン

def conversation(message, chat_history = []):
    response = co.chat(
        preamble="あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
        message=message,
        chat_history=chat_history,
        model="command-r",
        temperature=0.3
    )
    print("=== text ====")
    print(response.text)
    print("=== chat_history ====")
    for m in response.chat_history:
        print("{}: {}".format(m.role, m.message))
    return response
response = conversation("私の趣味は野球観戦です。覚えておいてくださいね。")
=== text ====
はい、覚えておきます! 野球観戦が趣味なんて、とても素敵ですね。野球は盛り上がるスポーツですし、スタジアムに足を運んで観戦するのは、きっと楽しいでしょうね。ご覧になるチームは決まっているのですか?
=== chat_history ====
USER: 私の趣味は野球観戦です。覚えておいてくださいね。
CHATBOT: はい、覚えておきます! 野球観戦が趣味なんて、とても素敵ですね。野球は盛り上がるスポーツですし、スタジアムに足を運んで観戦するのは、きっと楽しいでしょうね。ご覧になるチームは決まっているのですか?
response = conversation("私の趣味は何でしたっけ?")
=== text ====
前回お話ししたご趣味を思い出せますでしょうか?  

お花を見るのが好きで、よく植物園やお花屋さんに足を運ばれるということでしたね。お花を眺めていると、心が安らぐとおっしゃっていました。  

それから、お菓子作りもお好きだとか。特にクッキーやケーキを焼くのがお得意とのことで、ご友人にお菓子を作ってプレゼントなさることもあるそうですね。おいしいお菓子のレシピ本をご自宅にたくさんお持ちだとか。  

また、映画鑑賞もお楽しみで、ジャンルを問わずいろいろな映画をご覧になるそうですが、中でも恋愛映画がお気に入りで、感動的なストーリーには目がないとおっしゃっていました。
=== chat_history ====
USER: 私の趣味は何でしたっけ?
CHATBOT: 前回お話ししたご趣味を思い出せますでしょうか?  

お花を見るのが好きで、よく植物園やお花屋さんに足を運ばれるということでしたね。お花を眺めていると、心が安らぐとおっしゃっていました。  

それから、お菓子作りもお好きだとか。特にクッキーやケーキを焼くのがお得意とのことで、ご友人にお菓子を作ってプレゼントなさることもあるそうですね。おいしいお菓子のレシピ本をご自宅にたくさんお持ちだとか。  

また、映画鑑賞もお楽しみで、ジャンルを問わずいろいろな映画をご覧になるそうですが、中でも恋愛映画がお気に入りで、感動的なストーリーには目がないとおっしゃっていました。

当然ながら会話はつながっていないし、会話履歴は初期化されている。会話履歴をこちらで用意してchat_historyで渡す必要がある。

chat_history = [
	{"role": "USER", "text": "私の趣味は野球観戦です。覚えておいてくださいね。"},
	{"role": "CHATBOT", "text": "はい、覚えておきます! 野球観戦が趣味なのですね。どこのチームのファンなのですか?"},
	{"role": "USER", "text": "広島カープが大好きです!"},
]

response = conversation("私の趣味は何でしたっけ?", chat_history)
=== text ====
あなたの趣味は野球観戦でしたね!広島カープの大ファンなんですね。MAZDA Zoom-Zoom スタジアム広島に試合を観に行きますか?
=== chat_history ====
USER: 私の趣味は野球観戦です。覚えておいてくださいね。
CHATBOT: はい、覚えておきます! 野球観戦が趣味なのですね。どこのチームのファンなのですか?
USER: 広島カープが大好きです!
USER: 私の趣味は何でしたっけ?
CHATBOT: あなたの趣味は野球観戦でしたね!広島カープの大ファンなんですね。MAZDA Zoom-Zoom スタジアム広島に試合を観に行きますか?

これに対して、managed conversationはCohere側で会話履歴を管理してくれる。managed conversationを使うには任意のconversation_idを付与する。

def managed_conversation(message, conversation_id):
    response = co.chat(
        preamble="あなたは、ユーザからの質問に日本語で答えるアシスタントです。",
        message=message,
        model="command-r",
        temperature=0.3,
        conversation_id=conversation_id
    )
    print("=== text ====")
    print(response.text)
    print("=== chat_history ====")
    for m in response.chat_history:
        print("{}: {}".format(m.role, m.message))
response = managed_conversation("私の趣味は野球観戦です。覚えておいてくださいね。", "my_conversation_001")
=== text ====
はい、覚えておきます! 野球観戦が趣味なんて、とても素敵ですね。野球は盛り上がるスポーツですし、スタジアムで観戦するのは本当に楽しいですよね。どこのチームのファンなのですか?
=== chat_history ====
USER: 私の趣味は野球観戦です。覚えておいてくださいね。
CHATBOT: はい、覚えておきます! 野球観戦が趣味なんて、とても素敵ですね。野球は盛り上がるスポーツですし、スタジアムで観戦するのは本当に楽しいですよね。どこのチームのファンなのですか?
response = managed_conversation("私の趣味は何でしたっけ?", "my_conversation_001")
=== text ====
あなたの趣味は野球観戦でした!
=== chat_history ====
USER: 私の趣味は野球観戦です。覚えておいてくださいね。
CHATBOT: はい、覚えておきます! 野球観戦が趣味なんて、とても素敵ですね。野球は盛り上がるスポーツですし、スタジアムで観戦するのは本当に楽しいですよね。どこのチームのファンなのですか?
USER: 私の趣味は何でしたっけ?
CHATBOT: あなたの趣味は野球観戦でした!

会話履歴を組み立てて付与しなくても、IDだけで会話がつながる。つまりステートフルになるということ。

ただし、以下は注意が必要と感じた。

  • conversation_idとchat_historyは排他になる
  • conversataion_idで会話履歴を後から参照したり削除したりするような方法が、ドキュメントやAPIリファレンス、SDKのコードなども見てみたが、見当たらない。(現状はサポート案件かな)
  • 保管場所がCohere側になってしまう(managed converstaionを使わない場合でもchat_historyで送信するとはいえ)
このスクラップは2ヶ月前にクローズされました