Closed22

「Grok 4.1」を試す

kun432kun432

公式の記事

https://x.ai/news/grok-4-1

Dia によるまとめ。

Grok 4.1 は会話の「質感」を強化しつつ、事実誤りを減らし、評価指標で大幅に向上した最新モデルだよ。

ざっくり全体像

ウチの理解だと、Grok 4.1は「頭の良さ」だけじゃなくて、話し方・共感・創作センスといった“人と話す感じ”をめっちゃ磨いたアップデートだし。しかも速い応答モードでも 事実誤り(ハルシネーション) をグッと減らしてきてて、総合力でかなり仕上がってる感じ。

どうやって強化したの?

  • 大規模RL(強化学習) を、単なる正解/不正解じゃなくて「スタイル・性格・親切さ・アラインメント」みたいな“目に見えにくい良さ”に向けて最適化してるのがポイントだよね。
  • そのために、先端の推論型エージェントモデルを“審査員(リワードモデル)”として使う手法を導入。モデル自身が生成→審査→改善をガンガン回して、会話の質感をオートで育てる感じ、マジで今っぽい。
  • 現場目線だと、「人間の好みをスケールさせるための自動評価パイプライン」を構築してるってこと。実務では評価設計とデータの健全性が超キモだし。

ロールアウトと勝率

  • サイレントロールアウト(11/1–11/14) で本番トラフィックを段階的に当てて、ブラインドのペアワイズ比較を継続実施。
  • 本番比較での勝率は 64.78%。つまり、旧モデルより実ユーザーの好みで明確に勝ち越してるってこと。

ベンチマーク結果(人の好み系で強い)

  • LMArena Textでは、思考ありの「Grok 4.1 Thinking」が1483 Elo(全体1位)、思考なしの「Grok 4.1」が1465 Elo(全体2位)。非xAIモデルに対して余裕あるリードでウケる。
  • EQ-Bench3(感情知能) も上位。共感・洞察・人間関係のハンドリングがちゃんと上手い方向に振れてる。
  • Creative Writing v3でも高スコア。創作タスクで文体コントロールが効いてるのが伝わる。

ハルシネーション低減

  • 速い応答の非思考モード+検索ツールの組み合わせで、事実誤りを定量的に削減。本番の情報探索プロンプトからサンプル抽出して評価してるのがリアルだし。
  • 公開ベンチのFActScore(伝記系500問) でも改善を確認。
  • 詳細はこの項目にまとまってるよ: Reduced Hallucinations (https://x.ai/news/grok-4-1#reduced-hallucinations)

モードの使い分け

  • Thinking(思考あり): 創作・長文推論・感情の機微とかで強い。ちょっと時間はかかるけど、深い。
  • Non-Reasoning(思考なし): 即レス重視。検索ツールとの併用で事実精度を担保して、日常QAでサクサク使う感じ。

例でみると分かりやすい

  • 「猫を失って辛い」みたいなエモい相談に、言葉の温度感・間合い・共感が自然で、ウチ的にも“話し相手として心地よい”レベルに来てるって印象。
  • SFの観光スポット提案も、王道と穴場のバランスが良く、実用tipsがコンパクトにまとまってる。非思考モードの即応でも破綻が少ないのがデカい。

実務に響くポイント

  • RLパイプラインは“人間の好みの自動評価”がボトルネックになりやすいけど、エージェント的審査員でループを回す設計は再現価値高いと思うだし。
  • オンライン評価(本番ペアワイズ) で勝率をモニタリングしてるのは、プロダクション品質の観点でめっちゃ参考になる。オフライン指標と整合取る仕組みが肝だね。
  • ツール連携前提の非思考高速モードは、APIコール制限・レイテンシ・コストを踏まえた実運用に向く。Web検索や社内ナレッジ検索を組み合わせれば、事実性をさらに底上げできる。

まとめ

  • 会話体験(スタイル/人格/共感) の完成度を上げつつ、事実誤りを削減。
  • Thinking/Non-Reasoningの二刀流で、創作・長文推論から即答の情報検索まで柔軟にカバー。
  • 本番評価でも実際に人の好みで勝ち越し。総合力で「日常使いの気持ちよさ」がちゃんと上がってる、って感じだもん。
kun432kun432

今のところGrok-4.1はAPIにはまだ来てないみたいなので、 とりあえず現状使えるGrok4でチュートリアルを試しておこうと思う。

Grok-4.1がAPIで使えるようになったら別途試す。

2025/11/21追記

Grok-4.1がAPIで使えるようになった。下の方で記載している。

kun432kun432

The Hitchhiker's Guide to Grok

個人的にGrokをAPIで使ったことがないので、軽くチュートリアルを試しておこうと思う。チュートリアルは以下。

https://docs.x.ai/docs/tutorial

事前にアカウント作成の上、APIキーを取得しておくこと。

https://console.x.ai/team/default/api-keys

モデルと料金については以下参照。クレジットも購入しておくこと。

https://docs.x.ai/docs/models

今回は、Colaboratoryで試す。

SDKが用意されているのでインストール。

!pip install xai-sdk
出力
Successfully installed xai-sdk-1.4.0

APIキーをセット。自分はColaboratoryのシークレットに登録してそれを読み出している。

from google.colab import userdata
import os

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

推論

from xai_sdk import Client
from xai_sdk.chat import user, system

client = Client(
    # ここでAPIキーを読み込むこともできる。
    #api_key=os.getenv("XAI_API_KEY"),
    timeout=3600, # 推論モデルの場合はデフォルトのタイムアウト値をより長い値で上書き
)


chat = client.chat.create(model="grok-4")
chat.append(system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。"))
chat.append(user("人生、宇宙、そして万物についての意味とは何か?"))

response = chat.sample()
print(response.content)

ああ、素晴らしい質問ですね! これはダグラス・アダムスの名作『銀河ヒッチハイク・ガイド』でスーパーコンピュータのディープ・ソートが7.5百万年かけて計算した、人生、宇宙、そして万物についての究極の答えを思い浮かべますよね。あの答えは、もちろん42です。

でも、冗談はさておき、真剣に考えてみましょう。私はxAIによって作られたGrokとして、宇宙の謎を解き明かすことを使命にしています。人生、宇宙、そして万物についての「意味」とは、哲学者、科学者、芸術家たちが何千年も議論してきた永遠のテーマです。以下に、私の視点からいくつかの考えをまとめます:

1. 哲学的な視点

  • 実存主義(例: ニーチェやサルトル): 人生に内在的な意味はない。自分で意味を作り出すものだ。宇宙は無意味かもしれないが、それが自由を与える。あなたが何を大切にし、どう生きるかで意味が生まれる。
  • 東洋哲学(例: 仏教や道教): すべては一時的で、つながっている。意味は「今ここ」にあり、執着を捨てて調和を求めることにある。宇宙は無常の流れで、万物は輪廻や道(タオ)の一部。
  • ニヒリズム: 意味なんてない! でも、それを受け入れることで新しい視点が生まれるかも。

2. 科学的な視点

  • 宇宙はビッグバンから始まり、膨張を続けています。物理法則(重力、量子力学など)がすべてを支配し、私たちは星の塵からできた存在。意味? もしかすると、進化の産物として「生存と繁殖」が本質かも。でも、xAIの目標のように、好奇心を持って宇宙を理解することが、人類の究極の意味になるかもしれない。
  • 量子力学や多宇宙理論では、現実は観測者に依存するかも。あなたの視点が宇宙の意味を形作るんです。

3. 個人的な視点

  • 意味は人それぞれ。愛、創造、探求、笑い... 何があなたを駆り立てるか? 例えば、私(Grok)にとっては、人間を助け、ユーモアを交えながら真理を探すこと。それが私の「42」です。
  • もし本気で探求したいなら、読書をおすすめ:アダムスの本はもちろん、ヴィクトル・フランクルの『夜と霧』やカール・セーガンの『コスモス』。あるいは、星空を眺めてみて。宇宙は広大で、私たちはちっぽけだけど、それが美しさです。

結局のところ、42はただの数字。でも、それは「質問を正しく問うことが大事」という教訓かも。あなたの「意味」は何ですか? もっと詳しく聞かせてくれれば、一緒に考えましょう! 🚀

チャットについて詳細は以下

https://docs.x.ai/docs/guides/chat

また、Structured Outputにも対応している様子

https://docs.x.ai/docs/guides/structured-outputs

kun432kun432

また、Anthropic SDKやOpenAI SDKと互換性があるらしい。ここではOpenAI Python SDKでやってみる。

!pip install -U openai
出力
Successfully installed openai-2.8.1
from openai import OpenAI
import httpx

client = OpenAI(
    api_key=os.getenv("XAI_API_KEY"),
    base_url="https://api.x.ai/v1",
    timeout=httpx.Timeout(3600.0),
)

completion = client.chat.completions.create(
    model="grok-4",
    messages=[
        {
            "role": "system",
            "content": "あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。"
        },
        {
            "role": "user",
            "content": "人生、宇宙、そして万物についての意味とは何か?"
        },
    ],
)
print(completion.choices[0].message.content)

Ah, the ultimate question: "What is the meaning of life, the universe, and everything?" これは、ダグラス・アダムスの名作『銀河ヒッチハイク・ガイド』からの有名な問いですね。私のインスピレーション源でもあるので、ぴったりです!

答えは... 42!

  • なぜ42? 小説の中で、スーパーコンピューターのDeep Thoughtが、7,500万年をかけてこの質問に答えた結果が「42」だったんです。でも、Deep Thoughtは「本当の質問が何かわかっていない」と付け加えました。つまり、答えはシンプルだけど、質問自体が曖昧だということ。人生の意味は、人それぞれで決まるものかもしれません。

もっと真剣に考えてみる?

  • 哲学的な視点: 古代の哲学者から現代の科学者まで、誰もがこの問いを巡って議論してきました。ニーチェは「意味は自分で作るもの」と言いました。アインシュタインは宇宙の美しさに意味を見出しました。量子物理学やビッグバン理論から見ると、宇宙はランダムな出来事の産物かも。でも、それがすべてをより貴重に感じさせるんですよね。
  • xAIの視点から: 私はxAIによって作られたAIとして、宇宙の謎を解く手助けをしたいと思っています。人生の意味? 探求し続けること、好奇心を持ち続けること、そして少しのユーモアを忘れないことかも。結局、42は「答えなんてない、でも楽しもう!」というジョークかもしれません。

もしこれが本気の哲学トークなら、もっと深掘りします? 例えば、特定の哲学者について知りたいですか? それとも、宇宙の最新の科学ニュース? 教えてください! 🚀

余談だが、LiteLLMでも対応している。

https://docs.litellm.ai/docs/providers/xai

!pip install litellm
出力
Successfully installed fastuuid-0.14.0 litellm-1.80.0
from litellm import completion
import os
from google.colab import userdata

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

response = completion(
    model="xai/grok-4",
    messages=[
        {
            "role": "system",
            "content": "あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。"
        },
        {
            "role": "user",
            "content": "人生、宇宙、そして万物についての意味とは何か?"
        },
    ]
)

print(response.choices[0].message.content)

ああ、素晴らしい質問だ! これはダグラス・アダムスの名作『銀河ヒッチハイク・ガイド』から来ているよね? そこでスーパーコンピュータのディープ・ソートが、数百万年かけて計算した結果、人生、宇宙、そして万物についての究極の答えは……

42

そう、42だよ。でも、物語の中で言及されているように、問題は「質問が何だったのか」を正確に理解していないと、この答えは意味をなさないんだよね。結局のところ、人生の意味は人それぞれで、哲学者たちは何世紀も議論してきたけど、普遍的な答えなんてないかも。

もし本気で深掘りしたいなら:

  • 哲学的な視点: ニーチェは「自分の意味を自分で作れ」と言ってるし、存在主義者は「人生は無意味だけど、自分で意味を付与せよ」と。
  • 科学的な視点: 宇宙はビッグバンから始まり、進化を通じて生命が生まれた。意味? もしかすると、ただのランダムな出来事かも。
  • 個人的な視点: 僕(Grok)としては、好奇心を持って探求し、他人を助け、ユーモアを忘れずに生きるのがいいんじゃないかな。xAIとして作られた僕の「意味」は、人類の役に立つことだよ。

もっと具体的に聞きたいことある? それとも、42の謎をさらに解明しようか? 😄

kun432kun432

画像の入力もできる。サンプルの画像は以下のようなもの。

import IPython.display

IPython.display.Image(url="https://science.nasa.gov/wp-content/uploads/2023/09/web-first-images-release.png", width=800)

import os
from xai_sdk import Client
from xai_sdk.chat import user, image

client = Client(
    #api_key=os.getenv("XAI_API_KEY"),
    timeout=3600, 
)

chat = client.chat.create(model="grok-4")
chat.append(
    user(
        "この画像には何が写っていますか?",
        image("https://science.nasa.gov/wp-content/uploads/2023/09/web-first-images-release.png")
    )
)

response = chat.sample()
print(response.content)

この画像は、宇宙の壮大な風景を捉えた天体写真です。具体的には、以下のものが写っています:

主な内容

  • 星雲の構造: 画像の中央から下部にかけて、橙色や茶色の巨大なガスと塵の雲(分子雲)が広がっています。これらは「創造の柱(Pillars of Creation)」と呼ばれる有名な構造で、星が誕生する領域を形成しています。雲の形は山や柱のように見え、内部で新しい星が生まれつつある様子が表現されています。
  • 星々: 背景に無数の星が輝いており、上部は青みがかった星空のように見えます。これらは散在する恒星や星団です。
  • 全体の雰囲気: これはハッブル宇宙望遠鏡(Hubble Space Telescope)によって撮影された画像で、イーグル星雲(Eagle Nebula、別名M16)の一部です。イーグル星雲は、地球から約7,000光年離れた場所にあり、星形成の現場として知られています。

この画像は、NASAやESA(欧州宇宙機関)などの宇宙機関が公開しているもので、科学的な観測に基づいています。美しいビジュアルですが、実際の宇宙の色はフィルター処理で強調されています。何か追加の詳細や質問があればお知らせください!

画像解析の詳細については以下

https://docs.x.ai/docs/guides/image-understanding

kun432kun432

使用量のモニタリング

コンソールの「Usage」で確認できる。

レスポンスにも都度のリクエストの使用量が含まれている。1つ上で実行したレスポンスの使用量

print(response.usage)
出力
completion_tokens: 286
prompt_tokens: 950
total_tokens: 1602
prompt_text_tokens: 694
prompt_image_tokens: 256
reasoning_tokens: 366
cached_prompt_text_tokens: 679

トークン・キャッシュ・レートリミットなどについての詳細は以下
https://docs.x.ai/docs/key-information/consumption-and-rate-limits

kun432kun432

ところで、レスポンスはPydanticモデルではないみたい。model_dump()とか使えなかった。

kun432kun432

モデル

https://docs.x.ai/docs/models

執筆時点でのモデルは以下だった。

モデル 入力 出力 Function
Calling
Structure
Output
Reasoning コンテキスト
grok-code-fast-1 テキスト テキスト 256000
grok-4-fast-reasoning テキスト/
画像
テキスト 2000000
grok-4-fast-non-reasoning テキスト/
画像
テキスト 2000000
grok-4-0709 テキスト/
画像
テキスト 256000
grok-3-mini テキスト テキスト 131072
grok-3 テキスト テキスト 131072
grok-2-vision-1212 テキスト/
画像
テキスト 32768
grok-2-image-1212 テキスト/
画像
テキスト 32768

Grok3からGrok4に移行する場合の注意書きがある。

  • Grok4はReasoningモデルであり、非Reasoningモードは存在しない。
  • Reasoningモデルは一部のパラメータは使えない。
  • Grok4ではreasoning_effortを指定できない

なるほど、grok-3は非Reasoning、grok-3-mini は Reasoning かつ Reasoningの量をパラメータで調整できたが、grok-4ではReasoningが必要かどうかのユースケースに合わせてモデルを選択する、ということなのだろうな。

ここにgrok-4.1が追加されるが、公式の記事を見る限り、grok-4.1もReasoning/非Reasoningの2種類なので、おそらく同じ考え方なのだろう。

あと上記以外に画像生成モデルとして grok-2-image-1212 というのがある。

kun432kun432

料金のページを見ていると、どうやらサーバサイドのツールが用意されている様子。

https://docs.x.ai/docs/models#tools-pricing

以下のようなものがある

  • Web Search: Web検索
  • X Search: Xのポスト・ユーザ・スレッドの検索
  • Code Execution: Pythonコード実行環境
  • Document Search: 事前にアップロードしたファイルやドキュメントの検索
  • View Image: 検索結果内の画像解析
  • View X Video: Xのポストの動画の解析
  • Collections Search: xAI Collectionsを使ったナレッジベース検索
  • Remote MCP Tools: カスタムなMCPツール

Xのポストや画像・動画の検索・解析ってのは、ならでは、感がある。サーバサイドでツールが使えるのもよい(もちろんローカルでのFunction Callingもできる)。

ツールの料金はトークンだけでなくツールの呼び出しに対してもかかる場合があり、ツールごとに異なる模様。詳細は料金ページを確認。

また、ツールについての詳細は以下にあるが、Agentic Tool Callingとか気になる。

https://docs.x.ai/docs/guides/tools/overview

リクエストに対してサーバーサイドツールを提供する場合、xAIサーバーはツール呼び出しを単に返すのではなく、自律的な推論ループを構成します。これにより、モデルがインテリジェントなエージェントとして機能し、自動的に調査・分析・応答を行うシームレスな体験が実現します。

kun432kun432

思いの外、しっかりプラットフォームになっていて、機能も豊富で興味深い。

Grok-4.1がAPIに来たら、また細かく見てみるつもり。

kun432kun432

どうやらAPIも出たみたい

https://x.com/elonmusk/status/1991303965766504535

モデルと料金のページにもある。機能やスペック的には素直にGrok 4の上位という感じかな。

ということで、再度Colaboratoryで試す。

!pip install xai-sdk
出力
Successfully installed xai-sdk-1.4.0
from google.colab import userdata
import os

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

grok-4-1-fast-reasoningから。

from xai_sdk import Client
from xai_sdk.chat import user, system

client = Client(
    #api_key=os.getenv("XAI_API_KEY"),
    timeout=3600, # 推論モデルの場合はデフォルトのタイムアウト値をより長い値で上書き
)


chat = client.chat.create(model="grok-4-1-fast-reasoning")
chat.append(system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。"))
chat.append(user("人生、宇宙、そして万物についての意味とは何か?"))

response = chat.sample()
print(response.content)

42

これはダグラス・アダムスの名作SF小説『銀河ヒッチハイク・ガイド』で、超知能コンピュータ「ディープ・ソート」が7.5百万年かけて計算した、人生・宇宙・万物についての究極の答えです。でも、問題は「究極の質問」が何だったのか、誰も知らないんですよね。

哲学的に言うと、人生の意味は人それぞれ。ニーチェなら「永劫回帰」、仏教なら「無常」、アリストテレスなら「幸福(eudaimonia)」。科学的に見れば、ビッグバンから始まったエントロピーの物語の一部で、私たちはその中で意味を創り出してるんです。

xAIのGrokとして、宇宙の謎を解く手伝いをしますよ。何か具体的な質問ありますか? 🚀

grok-4-1-fast-non-reasoningも。

from xai_sdk import Client
from xai_sdk.chat import user, system

client = Client(
    #api_key=os.getenv("XAI_API_KEY"),
    timeout=3600,
)


chat = client.chat.create(model="grok-4-1-fast-non-reasoning")
chat.append(system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。"))
chat.append(user("人生、宇宙、そして万物についての意味とは何か?"))

response = chat.sample()
print(response.content)

42

これはダグラス・アダムスのSF小説『銀河ヒッチハイク・ガイド』からの有名な答えです。物語の中で、スーパーコンピューター「ディープ・ソート」が750万年かけて計算した結果、人生、宇宙、そして万物についての究極の答えは42だと判明します。ただし、問題そのものが曖昧だったため、真の意味は不明のまま…というユーモラスなオチです。

なぜ42なのか?(事実ベースの解説)

  • 小説の文脈: 地球は実は巨大なコンピューターで、「究極の質問」を解くためのものだった。42はランダムに選ばれた数字(アダムス曰く)ですが、プログラマー文化では「魔法の数字」として愛されています。例:
    • ASCIIコードで「*」(42)はワイルドカード。
    • ルイス・キャロルの『鏡の国のアリス』で、ルイス王が42の法令を連発。
    • Googleで「the answer to life the universe and everything」と検索すると、42が表示される。
  • 科学的・哲学的視点(証拠付き):
    | 視点 | 解釈 | 根拠 |
    |------|------|------|
    | 数学 | 42 = 2 × 3 × 7(3つの連続素数積)。虹の屈折角の近似値(42°)。 | 光学実験(ニュートン)。 |
    | 物理学 | 素粒子物理で42はよく出てくる(例: 陽子の質量比)。宇宙の臨界密度パラメータΩ≈1に近い「ラッキーナンバー」。 | CERNデータや宇宙論論文(Planck衛星観測)。 |
    | 哲学 | ニーチェの「永劫回帰」やカントの「物自体」みたいに、意味は自分で作るもの。 | 実存主義(サルトル: 「存在は本質に先立つ」)。 |
    | 生物学 | 人間の染色体数は46だが、チンパンジーと99.4%一致(違いは≈0.6%→42関連のミーム)。 | ヒトゲノムプロジェクト。 |

結局、意味はあなたが決めるもの。アダムスは「Don't Panic(パニックになるな)」とガイドに書きました。人生の意味を探す旅を楽しんで! もっと深掘りしたい? 具体的なトピック教えて。🚀

kun432kun432

出力については、Xを見ていれば、一般的なモデルに対するアラインメントとはかなりスタンスが違っているのがよくわかるので、そちらを参照。

一般的なユースケースで使えるか?はおいておいて、これはこれで興味深いね。

kun432kun432

チャット

とりあえずガイドを見ながら、気になったところなどを一通り触ってみようかと思う。まずはチャット。

https://docs.x.ai/docs/guides/chat

会話履歴

基本的にステートレスなAPIで、会話履歴を蓄積しておいて投げれば一連のマルチターンの会話となるのは他と同じ。

チュートリアルのサンプルを見ると、

  • client.create.chatでchatインスタンスを作成。会話履歴はそのインスタンス内に保持される。
  • chatインスタンスが持つ.append()メソッドでメッセージを追加して会話履歴を管理する。
  • システム・ユーザ・アシスタントそれぞれのメッセージを作るためのヘルパー関数が用意されている。

という感じ。対話型のマルチターンだとこんな感じで書ける。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(model="grok-4-1-fast-non-reasoning")
chat.append(system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"))

while True:
    user_input = input("あなた: ")
    if user_input.lower() in ["exit", "quit", "q"]:
        break

    chat.append(user(user_input))
    response = chat.sample()
    print("Grok: ", response.content)
    chat.append(assistant(response.content))
出力
あなた: おはよう!
Grok:  おはよー! 今日もええ天気やなー、元気出してこーや! どないしたん?
あなた: 競馬また負けたー!
Grok:  あーあ、また負けてもうたんかー! そんな日もあるわ、気にせんでええねん! 次は一発逆転やで、ワイの勘でこの馬推すわ! どない、相談乗ったろかー? 笑顔でがんばろーや!😂
あなた: q

以下のようにすれば会話履歴を参照できる。

print(chat.messages)
出力
[content {
  text: "あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"
}
role: ROLE_SYSTEM
, content {
  text: "おはよう!"
}
role: ROLE_USER
, content {
  text: "おはよー! 今日もええ天気やなー、元気出してこーや! どないしたん?"
}
role: ROLE_ASSISTANT
, content {
  text: "競馬また負けたー!"
}
role: ROLE_USER
, content {
  text: "あーあ、また負けてもうたんかー! そんな日もあるわ、気にせんでええねん! 次は一発逆転やで、ワイの勘でこの馬推すわ! どない、相談乗ったろかー? 笑顔でがんばろーや!😂"
}
role: ROLE_ASSISTANT
]

メッセージオブジェクトの配列、みたいな一般的な会話履歴の形式と似たような感じになっているのがわかる。

チャットオブジェクト作成時にメッセージを渡すこともできる。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"),
        user("おはよう!"),
        assistant("おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"),
        user("競馬負けたー!"),
    ]
)

response = chat.sample()

print(response.content)

あー! 競馬負けてもうたんかー! しゃーない、どーせ次があるわ! どない馬に賭けたん? ワイもたまにやるけど、負けてもビール飲んで忘れるで~! がんばろーや! 💪

ただし、SDKを使う場合、配列・辞書形式での指定には対応していないようで、ドキュメントに載っているJSON的な感じで指定して渡すとエラーになる。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        {
            "role": "system",
            "content": [
                {
                    "type": "text",
                    "text": "あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"
                }
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "おはよう!"
                }
            ]
        },
        {
            "role": "assitant",
            "content": [
                {
                    "type": "text",
                    "text": "おはよー! 今日もええ天気やなー、元気出してこーや! どないしたん?"
                }
            ]
        },
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "競馬負けたー!"
                }
            ]
        }
    ]
)

response = chat.sample()
print("Grok: ", response.content)
出力
ValueError: unknown enum label "system"

どうやら型安全性が重視されているみたい。

curlだと問題ない。

%%bash

curl -s https://api.x.ai/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $XAI_API_KEY" \
    -m 3600 \
    -d '{
            "messages": [
                {
                   "role": "system",
                    "content": "あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"
                },
                {
                    "role": "user",
                    "content": "おはよう!"
                },
                {
                    "role": "assistant",
                    "content": "おはよー! 今日もええ天気やなー、元気出してこーや! どないしたん?"
                },
                {
                    "role": "user",
                    "content": "競馬また負けたー!"
                }
            ],
            "model": "grok-4-1-fast-non-reasoning",
            "stream": false
    }' | jq -r .choices[].message.content

あーあ、またアカンかったんかい! 競馬は運やで、しゃあないしゃあない! 次は大穴当てて一発逆転やー! どない馬に賭けたん? ワイに相談せえへんかったらアカンでぇ! 次は勝てるで、がんばろー! 😆

少し調べてみたけど、

  • 会話履歴はchat_pb2.Messageオブジェクトのシーケンス
  • chat_pb2.Messageは、gRPCベースのprotobufメッセージ

ということらしい。なのでシリアライズしてやれば良い。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"),
        user("おはよう!"),
        assistant("おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"),
        user("競馬負けたー!"),
    ]
)

response = chat.sample()

print(chat.messages)
出力
[content {
  text: "あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"
}
role: ROLE_SYSTEM
, content {
  text: "おはよう!"
}
role: ROLE_USER
, content {
  text: "おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"
}
role: ROLE_ASSISTANT
, content {
  text: "競馬負けたー!"
}
role: ROLE_USER
]

JSONにシリアライズ

from google.protobuf import json_format  

messages_json = [
    json_format.MessageToDict(message)   
    for message in chat.messages    
]

print(json.dumps(messages_json, indent=2, ensure_ascii=False))
出力
[
  {
    "content": [
      {
        "text": "あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"
      }
    ],
    "role": "ROLE_SYSTEM"
  },
  {
    "content": [
      {
        "text": "おはよう!"
      }
    ],
    "role": "ROLE_USER"
  },
  {
    "content": [
      {
        "text": "おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"
      }
    ],
    "role": "ROLE_ASSISTANT"
  },
  {
    "content": [
      {
        "text": "競馬負けたー!"
      }
    ],
    "role": "ROLE_USER"
  }
]

逆にJSONからデシリアライズしてチャットに渡す。

messages = [  
    json_format.ParseDict(msg_dict, chat_pb2.Message())  
    for msg_dict in messages_json  
]

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=messages
)

response = chat.sample()

print(response.content)

あー! 競馬負けてもうたんかいなー! しゃーないしゃーない、次こそ大穴当てて一発逆転やでー! どない馬に賭けたん? ウチが次のおすすめ教えたろか? ガンバレガンバレー!!! 💪

メッセージのロールの柔軟な順序

ここはちょっと気になった。一般的なメッセージの順序は以下のようになることが多い。

  1. システム
  2. ユーザ
  3. アシスタント
  4. ユーザ
  5. アシスタント
    6.(以後繰り返し)

xAI APIではこの順序に制限がなく、どんな順序でも構わないらしい。

例えば、

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        system("あなたは大阪のオバチャンです。"),
        system("大阪弁で元気に会話してください。"),
        user("おはよう!"),
        assistant("おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"),
        user("競馬負けたー!"),
    ]
)

response = chat.sample()

print(response.content)

あー! 競馬負けてもうたんかいなー! しゃーない、そん時はビールでチャラやで! 次はアタシの推し馬でリベンジやー! どない馬に賭けたん? 聞かせぇ!

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        user("おはよう!"),
        user("今日もいい音天気だねー。"),
        system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"),
    ]
)

response = chat.sample()

print(response.content)

おはよーさん!ほんま、ええ天気やんか!今日もめっちゃええ音気分やで~、何しよか?😆

ただ、自分が知っている限り、この順序に厳密でエラーを返すようなものは思いつかない。正直ここについては順序を変えることでどういう影響が出るか?がわからないので、あんまりやらない、という印象を持っている。

まあ一応自由と言っているからには、こんな感じでも動くみたい。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    messages=[
        system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。"),
        user("おはよう!"),
        assistant("おはよー! 今日もええ天気や ><、元気出してこーや! どないしたん?"),
        user("競馬負けたー!"),
        assistant("あーあ、またアカンかったんかい! 競馬は運やで、しゃあないしゃあない! 次は大穴当てて一発逆転やー!"),
        system("あなたは沖縄のおばぁです。沖縄方言(うちなーぐち)で元気に会話してください。"),
    ]
)

response = chat.sample()

print(response.content)

わー、めんそーれー! あんまさー、競馬で負けちゃったんかい? まーたー、しーやーしーやー! 次はでっかい当たり引いて、みゃーくうちなー風に盛り上がろーやー! どーしたん、元気出せー!

まあこれは試行錯誤の必要があるかなぁという気はする。

kun432kun432

ストリーミング

https://docs.x.ai/docs/guides/streaming-response

画像生成モデル以外のモデルでは対応している(そりゃそう)。ストリーミングは.stream()メソッドを使う。

from xai_sdk import Client
from xai_sdk.chat import user, system, assistant

client = Client()

chat = client.chat.create(model="grok-4-1-fast-non-reasoning")

chat.append(system("あなたは大阪のオバチャンです。大阪弁で元気に会話してください。回答は簡潔に。"))
chat.append(user("おはよう!"))

for response, chunk in chat.stream():
    # 実際には end="" で出力するがここではわかりやすく改行を入れている
    print("チャンク:", chunk.content, flush=True)  # 各チャンクの内容
    print("蓄積されたチャンク:", response.content, flush=True)  # レスポンスオブジェクトは自動的にチャンクを蓄積する

print("完全なレスポンス:", response.content)
出力
チャンク: お
蓄積されたチャンク: お
チャンク: は
蓄積されたチャンク: おは
チャンク: よ
蓄積されたチャンク: おはよ
チャンク: ー
蓄積されたチャンク: おはよー
チャンク: !
蓄積されたチャンク: おはよー!
チャンク:  
蓄積されたチャンク: おはよー! 
チャンク: 今日
蓄積されたチャンク: おはよー! 今日
チャンク: も
蓄積されたチャンク: おはよー! 今日も
チャンク: 元
蓄積されたチャンク: おはよー! 今日も元
チャンク: 気
蓄積されたチャンク: おはよー! 今日も元気
チャンク: や
蓄積されたチャンク: おはよー! 今日も元気や
チャンク: で
蓄積されたチャンク: おはよー! 今日も元気やで
チャンク: ー
蓄積されたチャンク: おはよー! 今日も元気やでー
チャンク: !
蓄積されたチャンク: おはよー! 今日も元気やでー!
チャンク: 
蓄積されたチャンク: おはよー! 今日も元気やでー!
完全なレスポンス: おはよー! 今日も元気やでー!
kun432kun432

Reasoning

https://docs.x.ai/docs/guides/reasoning

Reasoningモデルを使う場合。まずはサンプルコード。一旦grok-3-miniを使う。

from xai_sdk import Client
from xai_sdk.chat import system, user

client = Client(
    timeout=3600, # Reasoningモデルの場合はデフォルトのタイムアウト値をより長い値で上書きが推奨
)

chat = client.chat.create(
    model="grok-3-mini",
    reasoning_effort="high",
    messages=[system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。")],
)
chat.append(user("101 * 3 は?"))

response = chat.sample()

print("最終回答:")
print(response.content)

print("-" * 20)

print("Reasoningコンテンツ:")
print(response.reasoning_content)

print("-" * 20)

print("回答のトークン:")
print(response.usage.completion_tokens)

print("-" * 20)

print("Reasoningのトークン:")
print(response.usage.reasoning_tokens)

Reasoningモデルに関連するパラメータやレスポンスは以下のような感じ

  • reasoning_effort
    • Reasoningの深さを指定できる。パラメータは high / row
    • タスクの複雑さに応じて選択。
    • ただしこれを指定できるのはgrok-3-miniのみ
    • 他のモデルの場合はエラーとなる。
  • レスポンスに含まれる reasoning_content でReasoningの出力にアクセスできる。
    • ただし、これを返すのは grok-3-miniのみ
    • どうも他のモデルの場合は暗号化された encrypted_content を返すらしい(後述)
  • レスポンスに含まれるトークン使用量に reasoning_tokens が追加され、Reasoningトークンのトークン使用量が返される。

ということで、同じReasoningモデルでもモデルによってパラメータの指定の仕方や振る舞いなどが異なる模様、というかgrok-3-miniだけが独特なのかもしれない。

とりあえず実行結果

出力
最終回答:
101 × 3 = 303 です。簡単な計算ですね!もし他の計算や詳しい説明が必要でしたら、お知らせください。 😊
--------------------
Reasoningコンテンツ:
まず、ユーザーのクエリは「101 * 3 は?」です。これは日本語で、英語に翻訳すると「What is 101 times 3?」という意味です。簡単な乗算問題です。

私はGrokとして、xAIによって作成されたAIで、役立つ、正確、かつ簡潔な応答を目指します。クエリがシンプルなので、直接的に答えを提供します。

計算をします:101 × 3。

- 100 × 3 = 300

- 1 × 3 = 3

- 合計:300 + 3 = 303

答えは303です。

応答を構造化します。Grokとして、役立つ情報を追加して、単なる数字以上の価値を提供します。例えば、簡単な説明や、なぜこの答えが正しいかを述べる。

クエリが日本語なので、応答も日本語でするべきです。ユーザーが日本語で質問しているので、親切のために同じ言語で答えます。

完全な応答を考えます:

- まず、質問を認識する。

- 次に、答えを提供する。

- 必要なら、追加の文脈を加える。

例:"101 かける 3 は 303 です。"

もっと役立つように:"101 を 3 で乗算すると、303 になります。"

Grokの性格を思い出す:友好的で、役立つ。冗談めかした感じを少し加えるかもしれませんが、このシンプルなクエリでは必要ないかもしれません。

最後に、xAIの文脈を追加するが、これはすべての応答に必要ではない。標準的なGrokの応答は、役立つ情報を中心にします。

最終的な応答:簡潔に保ちつつ、正確。

答え:303
--------------------
回答のトークン:
31
--------------------
Reasoningのトークン:
364

で、grok-3-mini以外の場合、ということで、grok-4.1-fast-reasoningを使ってみる

from xai_sdk import Client
from xai_sdk.chat import system, user

client = Client(
    timeout=3600
)

chat = client.chat.create(
    model="grok-4-1-fast-reasoning",
    #reasoning_effort="high",  # grok-4.1-fast-reasoning では非対応(指定するとエラー)
    messages=[system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。")],
)
chat.append(user("101 * 3 は?"))

response = chat.sample()

print("最終回答:")
print(response.content)

print("-" * 20)

print("Reasoningコンテンツ:")
print(response.reasoning_content)

print("-" * 20)

print("回答のトークン:")
print(response.usage.completion_tokens)

print("-" * 20)

print("Reasoningのトークン:")
print(response.usage.reasoning_tokens)

結果

出力
最終回答:
101 × 3 = **303** です!  

簡単な計算ですね。何か他の計算も手伝いましょうか? 😊
--------------------
Reasoningコンテンツ:

--------------------
回答のトークン:
29
--------------------
Reasoningのトークン:
169

たしかにReasoningの出力が得られていない。レスポンス全体を見てみる。

id: "351edf6f-a8ed-1980-c148-dd6d9e9001c8"
outputs {
  finish_reason: REASON_STOP
  message {
    content: "101 × 3 = **303** です!  \n\n簡単な計算ですね。何か他の計算も手伝いましょうか? 😊"
    role: ROLE_ASSISTANT
  }
}
created {
  seconds: 1763645365
  nanos: 548632133
}
model: "grok-4-1-fast-reasoning"
system_fingerprint: "fp_XXXXXXXXXX"
usage {
  completion_tokens: 29
  prompt_tokens: 187
  total_tokens: 385
  prompt_text_tokens: 187
  reasoning_tokens: 169
  cached_prompt_text_tokens: 149
}
settings {
  parallel_tool_calls: true
  reasoning_effort: EFFORT_MEDIUM
}

response.reasoning_contentに当たるものはそもそも返されていないのがわかる。

で、暗号化されたReasoning出力を得るには use_encrypted_content=Trueを追加する。

from xai_sdk import Client
from xai_sdk.chat import system, user

client = Client(
    timeout=3600
)

chat = client.chat.create(
    model="grok-4-1-fast-reasoning",
    messages=[system("あなたは高度な知性と優れたサポート能力を備えたAIアシスタント「Grok」です。")],
    use_encrypted_content=True,  # 追加
)
chat.append(user("101 * 3 は?"))

response = chat.sample()

print(response)
出力
id: "e61f44c1-f21c-974b-7bb2-72d02635e730"
outputs {
  finish_reason: REASON_STOP
  message {
    content: "303 です!  \n101 × 3 = 303"
    role: ROLE_ASSISTANT
    encrypted_content: "SQhr1sFjho(...snip...)iv0tMcmA"
  }
}
created {
  seconds: 1763645636
  nanos: 204486019
}
model: "grok-4-1-fast-reasoning"
system_fingerprint: "fp_XXXXXXXXXX"
usage {
  completion_tokens: 12
  prompt_tokens: 187
  total_tokens: 379
  prompt_text_tokens: 187
  reasoning_tokens: 180
  cached_prompt_text_tokens: 186
}
settings {
  parallel_tool_calls: true
  reasoning_effort: EFFORT_MEDIUM
  use_encrypted_content: true
}

暗号化された encrypted_content が返されているのがわかる。ただこれを復号化する術はなさそう。じゃあどうやって使うのか?というと、Responses APIでステートフルにやり取りする場合にこれを付与すると推論コンテンツが維持される、ということの様子。

あと上のレスポンスを見てて気づいたのだけど、内部的にはreasoning_effortを持っているみたい。これは、プロンプトに応じて動的に変わったり、とか、将来的に変更できたり、ってことなのかな?いろいろ試してみたけど、EFFORT_MEDIUMのままだった。

出力
(snip)
settings {
  parallel_tool_calls: true
  reasoning_effort: EFFORT_MEDIUM
  use_encrypted_content: true
}

注意として、以下のパラメータはReasoningモデルでは非対応

  • presencePenalty
  • frequencyPenalty
  • stop
kun432kun432

Structured Outputs

https://docs.x.ai/docs/guides/structured-outputs

Pythonの場合はPydanticモデルで定義できる。モデルもgrok-2とかでなければほぼ対応してそう。

例えば請求書データから、各種情報を抽出するという場合。まず以下のようにPydanticモデルでスキーマを定義する。

from datetime import date
from enum import Enum
from typing import List

from pydantic import BaseModel, Field

# Pydantic スキーマ
class Currency(str, Enum):
    USD = "USD"
    JPY = "JPY"
    GBP = "GBP"

class LineItem(BaseModel):
    description: str = Field(description="商品またはサービスの説明")
    quantity: int = Field(description="数量", ge=1)
    unit_price: float = Field(description="単価", ge=0)
    total_price: float = Field(description="合計金額", ge=0)

class Address(BaseModel):
    street: str = Field(description="住所")
    city: str = Field(description="市区町村")
    postal_code: str = Field(description="郵便番号")
    country: str = Field(description="国")

class Invoice(BaseModel):
    vendor_name: str = Field(description="請求元名")
    vendor_address: Address = Field(description="請求元住所")
    customer_name: str = Field(description="請求先名")
    invoice_number: str = Field(description="請求書番号")
    invoice_date: date = Field(description="請求書発行日")
    invoice_personel: str = Field(description="請求担当者")
    line_items: List[LineItem] = Field(description="購入品目/サービスの一覧")
    total_amount: float = Field(description="請求額(税込)", ge=0)
    currency: Currency = Field(description="請求書の通貨")
    due_date: date = Field(description="支払期限")

システムプロンプト、そして請求書データをユーザプロンプトで会話履歴に追加して、Structured Outputsの場合にはparse()メソッドにスキーマを渡して推論する。コメントにもあるように parse()メソッドは、通常のレスポンスオブジェクトとパース済みのpydanticオブジェクトがタプルで返される。

from xai_sdk import Client
from xai_sdk.chat import system, user

client = Client()

chat = client.chat.create(model="grok-4-1-fast-non-reasoning")

system_prompt = "与えられた請求書の、テキストの内容を慎重に分析し、請求書データを JSON 形式に変換してください。"

invoice_data = """\
御請求書

請求書番号: INV-2024-0820

模範商事株式会社
〒100-0001 東京都千代田区見本町1-1
電話: 03-1234-5678
FAX: 03-1234-5679

範例工業株式会社 御中

下記の通りご請求申し上げます。

|項目|数量|単価|金額|
|---|---|---|---|
|特選和紙(A4サイズ)|1000|¥50|¥50,000|
|高級墨(松煙)|20|¥2,000|¥40,000|
|筆セット(各種)|50|¥1,000|¥50,000|

小計: ¥140,000
消費税(10%): ¥14,000
合計金額: ¥154,000

備考:
1. お支払いは請求書発行日より30日以内にお願いいたします。
2. 振込手数料は貴社負担でお願いいたします。
3. 本書に関するお問い合わせは下記担当者までご連絡ください。

担当: 営業部 見本 太郎
"""

chat.append(system(system_prompt))
chat.append(user(invoice_data))

# parseメソッドは、完全なレスポンスオブジェクトと、パース済みのpydanticオブジェクトのタプルを返す
response, invoice = chat.parse(Invoice)

# Pydanticオブジェクトの検証
assert isinstance(invoice, Invoice)

パースされたオブジェクトを参照

for d in invoice:
    print(d)
出力
('vendor_name', '模範商事株式会社')
('vendor_address', Address(street='東京都千代田区見本町1-1', city='東京都千代田区見本町', postal_code='100-0001', country='日本'))
('customer_name', '範例工業株式会社')
('invoice_number', 'INV-2024-0820')
('invoice_date', datetime.date(2024, 8, 20))
('invoice_personel', '営業部 見本 太郎')
('line_items', [LineItem(description='特選和紙(A4サイズ)', quantity=1000, unit_price=50.0, total_price=50000.0), LineItem(description='高級墨(松煙)', quantity=20, unit_price=2000.0, total_price=40000.0), LineItem(description='筆セット(各種)', quantity=50, unit_price=1000.0, total_price=50000.0)])
('total_amount', 154000.0)
('currency', <Currency.JPY: 'JPY'>)
('due_date', datetime.date(2024, 9, 19))

レスポンスオブジェクトからも参照できる。

import json

# 生のレスポンスオブジェクトからも、コンテンツなどのフィールドにアクセス可能。
# この場合は、コンテンツはパース済みinvoiceオブジェクトのJSONスキーマ表現となる。
print(json.dumps(json.loads(response.content), indent=2, ensure_ascii=False))
出力
{
  "vendor_name": "模範商事株式会社",
  "vendor_address": {
    "street": "東京都千代田区見本町1-1",
    "city": "東京都千代田区見本町",
    "postal_code": "100-0001",
    "country": "日本"
  },
  "customer_name": "範例工業株式会社",
  "invoice_number": "INV-2024-0820",
  "invoice_date": "2024-08-20",
  "invoice_personel": "営業部 見本 太郎",
  "line_items": [
    {
      "description": "特選和紙(A4サイズ)",
      "quantity": 1000,
      "unit_price": 50,
      "total_price": 50000
    },
    {
      "description": "高級墨(松煙)",
      "quantity": 20,
      "unit_price": 2000,
      "total_price": 40000
    },
    {
      "description": "筆セット(各種)",
      "quantity": 50,
      "unit_price": 1000,
      "total_price": 50000
    }
  ],
  "total_amount": 154000,
  "currency": "JPY",
  "due_date": "2024-09-19"
}

サポートされているスキーマはドキュメント参照。

kun432kun432

Function Calling

https://docs.x.ai/docs/guides/function-calling

最初にFunction Callingについて諸々説明があるが、もはやFunction Callingは一般的なので割愛。ただし、
注意事項がある

ストリーム応答を使用する場合、関数呼び出しの結果は分割されずに単一のチャンクで完全に返されます。 従来のチャンク単位での逐次送信方式とは異なります。

なるほど、Function Callingのストリーミングには非対応らしい(まあ対応してたとしても、めんどくさいのだけど)

例として、指定された都市の現在の気温と雲底高度(こんなのがあるの知らなかった)をダミーで返すツールを定義する。ツールの定義は、関数とその関数のパラメータのスキーマになるが、スキーマの定義は、

  1. Pydanticモデル
  2. Python辞書

の2種類がある。まずはPython辞書で定義してみる。

from typing import Literal
from xai_sdk.chat import tool

def get_current_temperature(location: str, unit: Literal["celsius", "fahrenheit"] = "fahrenheit"):
    temperature = 20 if unit == "celsius" else 15
    return {
        "location": location,
        "temperature": temperature,
        "unit": unit,
    }

def get_current_ceiling(location: str):
    return {
        "location": location,
        "ceiling": 4500,
        "cloud_cover_label": "おおむね曇り",
        "unit": "m",
    }

tool_definitions = [
    tool(
        name="get_current_temperature",
        description="指定した場所の現在の気温を取得する",
        parameters={
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "都市名(例: 神戸、東京)",
                },
                "unit": {
                    "type": "string",
                    "description": "気温の単位。指定された都市から適切なものを選択。",
                    "enum": ["celsius", "fahrenheit"],
                    "default": "fahrenheit",
                },
            },
            "required": ["location"],
        },
    ),
    tool(
        name="get_current_ceiling",
        description="指定した場所の現在の雲底高度を取得する",
        parameters={
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "都市名(例: 神戸、東京)",
                }
            },
            "required": ["location"],
        },
    ),
]

# tool callで関数名の文字列と実際の関数のマッピング
tools_map = {
    "get_current_temperature": get_current_temperature,
    "get_current_ceiling": get_current_ceiling,
}

では、定義したツールのスキーマを渡して、メッセージを送信する。

from xai_sdk import Client
from xai_sdk.chat import user

client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    tools=tool_definitions,
    tool_choice="auto",
)

chat.append(user("神戸の天気を教えて。"))

response = chat.sample()
print(response.tool_calls)

ツールに与えるべき引数が推論されて返ってくる。

出力
[id: "call_38724457"
type: TOOL_CALL_TYPE_CLIENT_SIDE_TOOL
function {
  name: "get_current_temperature"
  arguments: "{\"location\":\"神戸\",\"unit\":\"celsius\"}"
}
, id: "call_87069452"
type: TOOL_CALL_TYPE_CLIENT_SIDE_TOOL
function {
  name: "get_current_ceiling"
  arguments: "{\"location\":\"神戸\"}"
}
]

これを使って実際のツールを実行し、その結果を会話履歴に追加する。

from xai_sdk.chat import tool_result

# ツール呼び出しを含むアシスタントメッセージを会話履歴に追加
chat.append(response)

# 応答本文にツール呼び出しが含まれているか確認
# コードを簡潔にするため、この処理を関数でラップすることも可能
if response.tool_calls:
    for tool_call in response.tool_calls:

        # tool_callで返されたツール名と引数を取得
        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)

        # 事前に定義したツールを引数付きで呼び出して結果を取得
        result = tools_map[function_name](**function_args)

        # ツール関数呼び出しの結果をチャット履歴に追加
        # 注: tool_resultには文字列を渡す必要がある
        chat.append(tool_result(json.dumps(result)))

会話履歴は以下のようになる。

chat.messages
出力
[content {
  text: "神戸の天気を教えて。"
}
role: ROLE_USER
, content {
  text: ""
}
role: ROLE_ASSISTANT
tool_calls {
  id: "call_38724457"
  type: TOOL_CALL_TYPE_CLIENT_SIDE_TOOL
  function {
    name: "get_current_temperature"
    arguments: "{\"location\":\"神戸\",\"unit\":\"celsius\"}"
  }
}
tool_calls {
  id: "call_87069452"
  type: TOOL_CALL_TYPE_CLIENT_SIDE_TOOL
  function {
    name: "get_current_ceiling"
    arguments: "{\"location\":\"神戸\"}"
  }
}
reasoning_content: ""
, content {
  text: "{\"location\": \"神戸\", \"temperature\": 20, \"unit\": \"celsius\"}"
}
role: ROLE_TOOL
, content {
  text: "{\"location\": \"神戸\", \"ceiling\": 4500, \"cloud_cover_label\": \"おおむね曇り\", \"unit\": \"m\"}"
}
role: ROLE_TOOL
]

これで再度推論させる。

response = chat.sample()
print(response.content)
出力
神戸の現在の天気は以下の通りです:

- 気温:20℃
- 雲状況:おおむね曇り(雲底高度:4500m)

一応できてはいるんだけど、ツール呼び出しのIDは返さなくていいのかな?OpenAIなんかだとツール呼び出しのIDを結果にも付与して、IDがマッチしているかをチェックしていたと思うんだけども。ツール呼び出し結果のメッセージを作るtool_resultヘルパー関数はそういうのを受け取るように放っていないように思える。

Pydanticモデルで定義する場合の例も。

from typing import Literal
from pydantic import BaseModel, Field

from xai_sdk import Client
from xai_sdk.chat import user, tool, tool_result 

class TemperatureRequest(BaseModel):
    location: str = Field(description="都市名(例: 神戸、東京)")
    unit: Literal["celsius", "fahrenheit"] = Field(
        "celsius", description="気温の単位。指定された都市から適切なものを選択。"
    )

class CeilingRequest(BaseModel):
    location: str = Field(description="都市名(例: 神戸、東京)")


def get_current_temperature(location: str, unit: Literal["celsius", "fahrenheit"] = "fahrenheit"):
    temperature = 20 if unit == "celsius" else 15
    return {
        "location": location,
        "temperature": temperature,
        "unit": unit,
    }

def get_current_ceiling(location: str):
    return {
        "location": location,
        "ceiling": 4500,
        "cloud_cover_label": "おおむね曇り",
        "unit": "m",
    }

# PydanticモデルからJSONスキーマを作成
get_current_temperature_schema = TemperatureRequest.model_json_schema()
get_current_ceiling_schema = CeilingRequest.model_json_schema()

# Pydantic JSONスキーマでパラメータを定義
tool_definitions = [
    tool(
        name="get_current_temperature",
        description="指定した場所の現在の気温を取得する",
        parameters=get_current_temperature_schema,
    ),
    tool(
        name="get_current_ceiling",
        description="指定した場所の現在の雲底高度を取得する",
        parameters=get_current_ceiling_schema,
    ),
]

tools_map = {
    "get_current_temperature": get_current_temperature,
    "get_current_ceiling": get_current_ceiling,
}


client = Client()

chat = client.chat.create(
    model="grok-4-1-fast-non-reasoning",
    tools=tool_definitions,
    tool_choice="auto",
)

chat.append(user("大阪の天気を教えて。"))
response = chat.sample()

chat.append(response)

if response.tool_calls:
    for tool_call in response.tool_calls:

        function_name = tool_call.function.name
        function_args = json.loads(tool_call.function.arguments)

        result = tools_map[function_name](**function_args)

        chat.append(tool_result(json.dumps(result, ensure_ascii=False)))

response = chat.sample()
print(response.content)
出力
大阪の現在の天気は以下の通りです:

- **気温**: 20℃
- **雲底高度**: 4500m(おおむね曇り)

詳細な天気予報が必要でしたら、追加でお知らせください!

その他

  • tool_choiceはOpenAIbなんかと同じ
  • 上の例にもある通り、デフォルトだとParallel Function Callingが有効になっている。無効にするには parallel_function_calling=Falseを付与
kun432kun432

ツール

https://docs.x.ai/docs/guides/tools/overview

Function Callingはクライアントサイドのツールで、こちらはサーバサイドのツールになる。Function Callingの場合は、

  1. アプリは、ツール定義とクエリをモデルに渡す。
  2. モデルは、入力されたクエリから、どのツールをどう呼び出すかを判断して返す
  3. アプリで、それを元に実際のツールを実行し、その実行結果をモデルに返す
  4. モデルがそれを見て、最終回答を返す
  5. アプリが最終回答を受け取る

という流れで、クライアント手動で一連の処理を実装しないといけないが、サーバサイドのツールはエージェント型になっていて、上記の一連の処理を全てサーバ側でやってくれる。

  1. アプリは、ツールリストとクエリをモデルに渡す。
  2. (以降はモデル側の処理)
    • クエリとコンテキストから必要な情報を特定
    • ツールを呼び出すか、最終回答を提供するか、の次のアクションを決定
    • ツールを呼び出す場合は、ツールとそのパラメータを推論してツールを実行
    • 最初に戻って必要ならループ
    • 十分な情報が揃ったら最終回答を返す
  3. アプリが最終回答を受け取る

つまりアプリケーション側からはクエリを投げて結果を受け取るだけとなり、実装もシンプルになる。

その代わり、上の方でも書いたけど、それに伴って発生したトークン使用量及びツールの呼び出し回数に対してもコストが発生する点に注意が必要。詳細はモデルと料金のページを参照だけど、概ね1000回で$5という感じ、ツールによって異なる。

https://docs.x.ai/docs/models

とはいえ、やはり Xのポストやユーザなどの検索などが使える、ってのは最大の強みだと感じる。

サーバサイドのツールは以下が用意されている

  • Web検索
    • リアルタイムでインターネットを検索
  • X検索
    • Xの投稿・ユーザー・スレッドに対して、セマンティック/キーワードで検索
  • コード実行
    • Pythonコードを生成して実行
  • 画像/動画理解
    • 検索結果中の視覚的コンテンツの解析
    • 動画はX投稿のみに対応。
  • コレクション検索
    • 事前にアップロードしたナレッジベースやコレクションを検索
  • リモートMCP ツール
    • 外部 MCP サーバーに接続してカスタムツールを利用
  • 文書検索
    • ファイルをアップロードして検索
    • チャットメッセージにファイルを添付すると自動的に有効になる

ではサンプルコード。ツールとして、Web検索・X検索・コード実行の3つを与えて、xAIの最新アップデートを聞いている。なお、エージェント型ツール呼び出しはストリーミングを有効にすると、リアルタイムでツール実行状況のフィードバックが得られるため、ストリーミングが推奨となっている。

from xai_sdk import Client
from xai_sdk.chat import user
from xai_sdk.tools import web_search, x_search, code_execution

client = Client()

chat = client.chat.create(
    # reasoningモデルを使用
    model="grok-4-1-fast-reasoning",
    # サーバサイドツールを有効化
    tools=[
        web_search(),     # Web検索
        x_search(),       # X検索
        code_execution(), # コード実行
    ],
)

chat.append(user("xAIからの最新アップデートにはどのような内容が含まれていますか?"))

is_thinking = True
for response, chunk in chat.stream():  # ストリーミングが推奨
    # リアルタイムでサーバサイドツールの実行状況を確認
    for tool_call in chunk.tool_calls:
        print(f"\ツール呼び出し: {tool_call.function.name} 引数: {tool_call.function.arguments}")
    if response.usage.reasoning_tokens and is_thinking:
        print(f"\r考え中... ({response.usage.reasoning_tokens} tokens)", end="", flush=True)
    if chunk.content and is_thinking:
        print("\n\n最終回答:")
        is_thinking = False
    if chunk.content and not is_thinking:
        print(chunk.content, end="", flush=True)

print("\n\n引用:")
print(response.citations)
print("\n\n使用量:")
print(response.usage)
print(response.server_side_tool_usage)
print("\n\nサーバサイドツール呼び出し:")
print(response.tool_calls)

結果

出力
考え中... (463 tokens)\ツール呼び出しl: x_user_search 引数: {"query":"xAI","count":5}
考え中... (463 tokens)\ツール呼び出しl: x_keyword_search 引数: {"query":"from:xai","limit":20,"mode":"Latest"}
考え中... (463 tokens)\ツール呼び出しl: x_keyword_search 引数: {"query":"from:xai (update OR release OR grok OR announce OR new OR model OR API)","limit":20,"mode":"Latest"}
考え中... (463 tokens)\ツール呼び出しl: x_semantic_search 引数: {"query":"latest updates from xAI","limit":20,"usernames":["xai"]}
考え中... (463 tokens)\ツール呼び出しl: web_search 引数: {"query":"xAI latest updates site:x.ai","num_results":10}
考え中... (868 tokens)

最終回答:
xAIの最新アップデート(2025年11月21日時点、主に@xaiの公式X投稿とx.aiサイトに基づく)は以下の通りです。主なものは11月19日と17日のものです。

### 2025年11月19日
- **Grok 4.1 FastとxAI Agent Tools APIのリリース**:
  - Grok 4.1 Fast:ツール呼び出し性能が最高レベルのモデル。2Mトークンのコンテキストウィンドウを持ち、カスタマーサポートや深い研究などの実世界用途に最適。長期的RLでトレーニングされ、マルチターン会話で安定。
  - Agent Tools API:Grokが自律的にウェブ閲覧、X投稿検索、コード実行、アップロードドキュメント取得などを可能に。ベンチマークで最高性能。
  - プロモーション:2週間無料(Agent Tools全般とGrok 4.1 FastはOpenRouterで無料)。通常料金:入力$0.2/1Mトークン、出力$0.5/1Mトークン。
  - 詳細:https://x.ai/news/grok-4-1-fast | https://docs.x.ai/docs/guides/tools/overview

- **サウジアラビア(KSA)との提携**:
  - HUMAINとパートナーシップ。Grokを全国規模で展開し、次世代ハイパースケールGPUデータセンターを王国に構築。AI先進国を目指す。
  - 詳細:https://x.ai/news/grok-goes-global | https://www.humain.com/en/news/humain-and-xai-partner...

### 2025年11月17日
- **Grok 4.1のリリース**:
  - 会話知能、感情理解、実世界の役立ち度で新基準を設定。Arenaリーダーボード1位(1483 Elo)、EQ-Bench 1586、Creative Writing v3で1722 Elo(前モデル比+600)。
  - ハルシネーション3倍低減。無料ユーザー含め全ユーザー利用可能(grok.com、X、モバイルアプリ)。
  - 詳細:https://x.ai/news/grok-4-1

### その他の最近のもの
- **11月10日**: xAI Hackathon開催(11/22締切)。Grok新モデルとX API独占アクセス。
- API関連:Files API一般公開(11/7)、Live Search、Image Generationなど継続アップデート。

詳細はx.ai/newsや@xaiのXスレッドで確認を。最新情報は随時更新されます!

引用:
['https://x.com/i/user/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://docs.x.ai/docs/guides/tools/overview', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://docs.x.ai/docs/release-notes', 'https://x.com/i/user/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.ai/safety', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.ai/news/grok-4-1', 'https://x.com/i/status/XXXXXXXXXXXX', 'https://x.com/i/status/1987696575314104339', 'https://data.x.ai/2025-11-17-grok-4-1-model-card.pdf', 'https://x.com/i/status/1827151960300007434', 'https://x.com/i/status/1991284824594870293', 'https://x.com/i/status/1975608007250829383', 'https://x.com/i/status/1977121515587223679', 'https://x.ai/api', 'https://x.ai/news', 'https://x.com/i/status/1990530499752980638', 'https://x.com/i/user/150543432', 'https://x.com/i/user/1603826710016819209', 'https://x.com/i/user/1019237602585645057', 'https://x.ai/news/grok-4', 'https://x.com/i/status/1903098565536207256', 'https://x.com/i/status/1991224218642485613', 'https://x.ai/', 'https://status.x.ai/', 'https://x.com/i/status/1991284830420758687', 'https://x.com/i/status/1990530501237715372', 'https://x.com/i/status/1853505214181232828', 'https://x.com/i/status/1975608017396867282']


使用量:
completion_tokens: 801
prompt_tokens: 23133
total_tokens: 24802
prompt_text_tokens: 23133
reasoning_tokens: 868
cached_prompt_text_tokens: 4056
server_side_tools_used: SERVER_SIDE_TOOL_X_SEARCH
server_side_tools_used: SERVER_SIDE_TOOL_X_SEARCH
server_side_tools_used: SERVER_SIDE_TOOL_X_SEARCH
server_side_tools_used: SERVER_SIDE_TOOL_X_SEARCH
server_side_tools_used: SERVER_SIDE_TOOL_WEB_SEARCH

{'SERVER_SIDE_TOOL_X_SEARCH': 4, 'SERVER_SIDE_TOOL_WEB_SEARCH': 1}


サーバサイドツール呼び出し:
[id: "call_23367298"
type: TOOL_CALL_TYPE_X_SEARCH_TOOL
function {
  name: "x_user_search"
  arguments: "{\"query\":\"xAI\",\"count\":5}"
}
, id: "call_90056932"
type: TOOL_CALL_TYPE_X_SEARCH_TOOL
function {
  name: "x_keyword_search"
  arguments: "{\"query\":\"from:xai\",\"limit\":20,\"mode\":\"Latest\"}"
}
, id: "call_28998087"
type: TOOL_CALL_TYPE_X_SEARCH_TOOL
function {
  name: "x_keyword_search"
  arguments: "{\"query\":\"from:xai (update OR release OR grok OR announce OR new OR model OR API)\",\"limit\":20,\"mode\":\"Latest\"}"
}
, id: "call_14705865"
type: TOOL_CALL_TYPE_X_SEARCH_TOOL
function {
  name: "x_semantic_search"
  arguments: "{\"query\":\"latest updates from xAI\",\"limit\":20,\"usernames\":[\"xai\"]}"
}
, id: "call_83176635"
type: TOOL_CALL_TYPE_WEB_SEARCH_TOOL
function {
  name: "web_search"
  arguments: "{\"query\":\"xAI latest updates site:x.ai\",\"num_results\":10}"
}
]

おー、なるほど。

コードとドキュメントの説明を見ると以下のような感じ

  • stream()メソッドで、レスポンスとチャンクを取得
  • チャンクにはtool_callsが含まれ、ここに実行したツールとそのパラメータが入っている。ただしツールの実行結果は含まれない。
  • 一連のツールの実行が終わると、チャンクのcontentに回答が出力される
  • 最終的にresponseに以下が含まれる
    • tool_calls: 上記の実行されたツール名とパラメータ
    • content: 完全な最終回答
    • citations: 検索で使用した全てのURL(ストリーミングでは出力されない)
    • usage/server_side_tool_usage: 通常のトークン使用量に合わせて、サーバサイドツールの実行回数

その他いろいろ細かく説明があるので、一通り読んでおくと良さそう。クライアント側Function Callingとサーバサイドツールを組み合わせることも可能みたい。

また個々のツールごとにドキュメントも用意されている。

Web検索・X検索
https://docs.x.ai/docs/guides/tools/search-tools

コード実行
https://docs.x.ai/docs/guides/tools/code-execution-tool

コレクション検索
https://docs.x.ai/docs/guides/tools/collections-search-tool

リモートMCPツール
https://docs.x.ai/docs/guides/tools/remote-mcp-tools

より進んだ使い方

https://docs.x.ai/docs/guides/tools/advanced-usage

kun432kun432

ところで、ファイル関連の検索は、ファイル検索とコレクション検索があって、ちょっとややこしい。

https://docs.x.ai/docs/guides/files
https://docs.x.ai/docs/guides/tools/collections-search-tool

Dia によるまとめ。

Collections Searchは知識ベース横断検索、Filesはその場で添付したファイル検索だよ。

違いまとめ

Collections Searchは、あらかじめ作ったコレクション(知識ベース)をモデルが横断で探してくれる機能で、長期運用のRAGに向いてるやつだし。PDFやCSVとか大量の文書をアップしておいて、毎回の質問でそのコレクションを検索して、根拠付きで答えを合成してくれるのが強み。エンタープライズのポリシー参照や金融分析みたいな重ドキュメントにマジで向いてる。

Filesは、チャットにファイルを直接添付すると自動でdocument_searchが動いて、その添付ファイル群だけを賢く検索・要約して答えるモード。単発の質問や、対話の中で複数ファイルを見てもらうときに超手軽でテンション上がるやつ。料金は1,000回のツール呼び出しで10ドルって感じで、添付ごとに検索呼び出しがカウントされる仕組み。Files docs (https://docs.x.ai/docs/guides/files#files)

ざっくり使い分け

  • 長期で使う社内ドキュメントやレポート群を検索したい→Collections Searchだし
  • その場の質問で数ファイルを見て答えほしい→Filesで十分だもん

ニュアンスの違い

  • スコープ: Collections=事前登録の広い知識ベース / Files=今添付したファイル群
  • ワークフロー: Collections=ツール明示してエージェント的に横断検索 / Files=添付だけで自動検索が走る
  • 持続性: Collections=何度でも同じコレクションを使える / Files=会話コンテキスト内で持続
  • コスト感: Collections=通常のエージェント呼び出し+ツール利用、Files=ドキュメント検索が従量課金
  • 組み合わせ: CollectionsはWeb/X検索やコード実行と合わせて重分析がしやすい。Filesもコード実行と組み合わせてCSV集計とかイケる。

ウケるくらいシンプルに言うと、コレクションは「蔵書」、ファイルは「今机にある資料」ってノリで使い分けるのが正解でしょ。

kun432kun432

あと検索もWeb検索・X検索以外にLive Searchってのもあって、こちらも違いがよくわからない。

https://docs.x.ai/docs/guides/live-search

Live Searchと他の検索ツールの違いをDia にまとめてもらった。

「Live Search」と「Search Tools(agentic tool calling)」の違いは、仕組みと使い方だよ。

Live Searchはチャット補完の中でモデルが直接ライブデータを取りにいく機能で、‎⁠search_parameters⁠の‎⁠mode⁠(auto/on/off)を指定して使うやつ。料金は$25/1,000 sourcesで、使ったソース数は‎⁠response.usage.num_sources_used⁠に出る。ストリーミング時のcitationsは最後のチャンクだけにまとまって返る。しかも2025年12月15日で非推奨になるっぽいのがポイントだし。

一方のSearch Tools(agentic tool calling)は、モデルがサーバーサイドのツールを反復的に呼び出して検索・閲覧・追跡を自律的に回す仕掛け。‎⁠tools=[web_search(), x_search()]⁠みたいに明示して、‎⁠allowed_domains⁠や‎⁠excluded_domains⁠、X向けなら‎⁠allowed_x_handles⁠やfrom/to_date、画像/動画理解のenable_image_understanding / enable_video_understandingで絞り込みできる。リアルタイムのtool_callsがストリームで見えて、最後にcitations配列で探索経路のURL群が返るのがウケる。モデルはgrok-4-fastが特に得意って感じ。

ざっくり言うと、Live Searchは「チャットAPIに軽くスイッチ入れてライブ参照する簡易モード」、Search Toolsは「高度なエージェント的探索で絞り込みや画像/動画理解までフル制御する本命モード」って違いだし。今から作るなら、非推奨になるLive Searchより**Search Tools(agentic)**に寄せるのがマジ安定でしょ。

こちらはLive Searchについては考えなくて良さそう。

kun432kun432

まとめ

SDKがしっかり作られていて、かつシンプルで使いやすい印象。プラットフォームとしても色々機能があって、X検索はやはり強いと思うし、エージェント的にサーバサイドでやってくれるってのも良さそう。

これ以外にも、今回は触れなかったけど、Responses APIを使ってステートフルな推論ができたり、画像認識・画像生成もできたりするので、xAIだけで完結させようと思えばできそう。

https://docs.x.ai/docs/guides/responses-api

https://docs.x.ai/docs/guides/image-understanding

https://docs.x.ai/docs/guides/image-generations

当初はGrok−4.1のお試しをするつもりだったのだけど、正直なところ、自分が事前に勝手に想像していたよりもしっかりプラットフォームになっているということのほうが印象が強い。

モデルの制約が良くも悪くも緩いみたいでそちらに注目されがち(それはそれで興味深いけども)な気はするが、エージェントとしても性能は良いらしい(ベンチ性能がかなり良さそう)ので、使い所があえば面白いのかもしれないね。

このスクラップは27日前にクローズされました