あなたはソフトウェアエンジニアです。長年の丁寧な実践を通じて磨かれた技術を持っています。ところが突然、チャットボットやエージェントが登場します。一夜にして、同僚たちはLinkedInで新しい肩書き「AIエンジニア」を名乗るようになりました。中にはすでに「シニアAIエンジニア」になっている人もいます。あなたはこの新しい世界に興味を持ち、自分も追いついてその一部になりたいと考えているかもしれません。

もしあなたがそうなら、AIエンジニアリングという分野を構成する概念とパターンのツアーにぜひ参加してください。私たちは、AIアプリケーション開発が主に「ただの」ソフトウェアエンジニアリングであり、そこに本質的に奇妙な新しい非決定的なコンポーネントであるLLMが加わっていることを理解するでしょう。

ツアーでは、実際のアプリケーションをエンドツーエンドで構築します。各記事で新しいレイヤーを追加し、新しいパターンや用語を、すでに知っているソフトウェアエンジニアリングの概念に結びつけていきます。

出発前に、シリーズを通じて使用する語彙のルールを1つ確認しておきます。「モデル」とはLLM自体(GPTやClaudeのような大規模言語モデル)を指し、AIエンジニアがその周りに構築するものを「アプリケーション」「エージェント」「ハーネス」と呼びます。

私たちが構築するもの

私自身が決済会社で働いているため、ドメインに沿ったものにしました。私たちが構築するPayIQというアプリケーションは、加盟店が支払い業務を行うためのアシスタントです。返金の実行、チャージバックの防御、手数料の計算などが可能です。請求金額と支払い方法を入力すると、実際の返金コストを計算します(ヒント:返金額より多くなります)。チャージバックと戦う価値があるかどうかを尋ねると、あなたの知識ベースを使って期待値の計算を行います。責任を持って回答できないことを尋ねられた場合、足りない情報を尋ねます。推測やハルシネーションは行いません。

最終的にPayIQは、他のシステムで利用可能な構造化出力、金融計算ツール群、知識ベースを対象とした検索、永続メモリを持つエージェントループ、モデルがスキップできないステップを持つオーケストレーショングラフ、FastAPIサービス背後のトークンストリーミング、回帰評価スイート、そして多層インジェクション防御を備えることになります。これらの用語の意味が分からなくても心配いりません。すぐに理解できるようになります。

各記事は10〜15分の読書で構成され、前の記事を基に意図的に構築されています(パート2の構造化出力はパート3のツール使用で使用されます)。そのため、順番に読むことをおすすめします。この最初の記事では、基礎を固め、モデルとのシンプルな対話を行います。

コンパニオンリポジトリ(https://github.com/BjornvdLaan/ai-engineering-articles-code-samples)にはすべてのコードサンプルが含まれており、実際に試すことができます。例ではAnthropicのモデルを使用しています。別のプロバイダー(またはローカルでホストされたモデル)を希望する場合でも、お気に入りのチャットボットが設定の調整を手助けしてくれるでしょう。LangChainのライブラリは(ほとんど)モデル非依存です。

モデルについて考えるためのメンタルモデル

コードに入る前に、モデルが実際に何であるかについての正確なメンタルモデルを構築しましょう。ニューラルネットワークやトランスフォーマーが内部でどのように動作するかは、優秀な科学者たちに任せます。AIエンジニアを目指すあなたに必要なのは、運用的なメンタルモデルです。つまり、このものが何を消費し、何にコストがかかり、何を制御できるかということです。最も単純な形では、モデルは次のようなインターフェースです。

f(list of input messages) -> output message

Enter fullscreen mode Exit fullscreen mode

これだけです。重要な点は、入力がメッセージのリストであり、最後に与えたプロンプトだけではないことです。モデルへの各呼び出しには通常、システムメッセージ(「アプリケーションの設定」)、これまでの会話全体(「現在の状態」)、最新のプロンプトが含まれます。これらの先行するすべてのメッセージに基づいて、モデルは会話の次のメッセージを生成します。

CodexやClaude Codeなどの「ハーネス」を使ったことがある場合、これは奇妙に聞こえるかもしれません。インターネットを検索したり、過去のセッションから事実を記憶したり、ToDoリストを保持したり、与えられたドキュメントを読んだり、コードベースを書いたり編集したりする様子が見えます。これらの魔法はすべて、AIエンジニアがモデルの周りに構築したアプリケーションのロジックですが、モデルが見るのは単なる入力メッセージのリストだけです。

トークン:すべての基本単位

メッセージは文字や単語ではなく、トークンとしてモデルに送信されます。トークナイザーと呼ばれるコンポーネントがまずテキストをトークンに分割し、それぞれに数値のトークンIDを割り当てます。一般的な単語は1つのトークンになることが多く、一般的でない単語は複数のトークンに分割されることがあります。モデルは元のテキストを見ることはありません。これらのトークンIDのみを処理します。

トークンは計算の単位として機能するだけでなく、課金の単位でもあるため重要です。入力トークンと出力トークンごとに料金が発生します。トークンはクラウド料金のcompute-secondsと同じように扱ってください。AI機能は、単一のリクエストのコストが気にするほど大きい最初の種類のバックエンドエンドポイントです。ユーザーリクエストごとに5回のモデル呼び出しを行うエージェントは、本番環境のトラフィック量では実際のお金がかかります。これはまた、新たなセキュリティリスクでもあります。従来、DDoS攻撃は主にインフラリソース(CPU、メモリ、帯域幅、オートスケールインスタンス)を消費していました。今では、すべてのリクエストが請求対象のトークンも消費し、2つ目の、潜在的にずっと大きなコスト要素を追加します。この攻撃には独自の名前もあります。「denial of wallet」です。

コンテキストウィンドウ:モデルの入力制限

各モデルには処理可能な入力トークンの最大数があります。これはコンテキストウィンドウと呼ばれ、数十万トークンを保持できることがよくあります。上記で述べたように、モデルがリクエストに答えるために必要なすべてのものは、その中に収める必要があります。システムプロンプト、これまでの会話、取得したドキュメント、ツールの結果、その他提供するコンテキストです。モデルのワーキングメモリのようなものです。コンテキストウィンドウが大きいほど多くの情報を提供できますが、コンテキストが多ければ良いわけではありません。無関係な情報が蓄積すると、モデルはより多くの注意散漫を起こし、重要な詳細を見逃しやすくなります。これをcontext rotと呼びます。入力トークンが増えるとコストも上がります!だからこそ「すべてを貼り付けるだけ」ではスケールしないのです。コンテキストエンジニアリングは量より質です。

セットアップ

背景情報が得られたので、実際にモデルを動かしてみましょう。このセットアップはシリーズ全体のすべてのコードサンプルで一度だけ必要です。

前提条件:Python 3.11以上、お気に入りのプロバイダーのAPIキー、そしてシリーズ全体で5ユーロ未満のクレジット。

新しいプロジェクトディレクトリを作成(またはコンパニオンリポジトリをクローン)し、Python仮想環境を有効にします。

python3 -m venv .venv
source .venv/bin/activate

Enter fullscreen mode Exit fullscreen mode

requirements.txtを作成します。

langchain>=1.3,<2.0
langchain-core>=1.4,<2.0
langchain-text-splitters>=1.1,<2.0
langgraph>=1.2,<2.0
langgraph-swarm>=0.1
langchain-anthropic>=0.4
langchain-huggingface>=0.2
sentence-transformers>=3.0
langchain-mcp-adapters>=0.1
mcp>=1.9
fastapi>=0.115
uvicorn>=0.32
pydantic>=2.9
python-dotenv>=1.0

Enter fullscreen mode Exit fullscreen mode

これらのパッケージのほとんどは後のパート(検索、MCP、Webレイヤー)用ですが、今インストールしておけばこのファイルを二度と触る必要がありません。

pip install -r requirements.txtを実行し、プロジェクトルートに.envを作成します。ここにAPIキー(およびオプションでその他の設定)を保存します。

ANTHROPIC_API_KEY=sk-ant-...

Enter fullscreen mode Exit fullscreen mode

AnthropicのAPIキーはplatform.claude.comから取得できます。

最初の呼び出し

01_first_call.pyを作成します。

from dotenv import load_dotenv
from langchain.chat_models import init_chat_model

load_dotenv()

model = init_chat_model("anthropic:claude-sonnet-5")

response = model.invoke(
    "A customer is disputing a €480 online card payment, claiming they "
    "never made it. In two sentences, what are the two most important "
    "pieces of evidence I should gather before responding?"
)

print(response.content)
print("\n--- metadata ---")
print(response.usage_metadata)

Enter fullscreen mode Exit fullscreen mode

実行します:python3 01_first_call.py

コードサンプルで注目すべき点が2つあります。まず、init_chat_modelはLangChainのプロバイダー非依存のコンストラクタです。文字列"anthropic:claude-sonnet-5""openai:gpt-5.5""ollama:llama3.3"にもでき、残りのアプリケーションコードは同じままです。これはソフトウェアエンジニアが具体的な実装ではなく抽象化に対してプログラミングするのと同じ理由です。コスト、機能、コンプライアンスの理由でプロバイダーを切り替える必要がある場合、設定を変更するだけでシステムを再設計する必要がありません。

次に、レスポンスのusage_metadataを見てください。この呼び出しの入力トークンと出力トークンが表示されます。これらの数値がコストを決定します。本番環境のAIシステムは、従来のアプリケーションがデータベースクエリ、メモリ使用量、その他のリソース消費を監視するのと同じように、これらの数値を監視します。Grafanaダッシュボードでこれらの数値を監視したいものです。

以下は出力の例です。2回実行します。

$ python3 01_first_call.py

The two most critical pieces of evidence are:

1. **Transaction authentication records** - Check whether the payment was verified through 3D Secure/Strong Customer Authentication (SCA), as successful two-factor authentication significantly shifts liability away from you and toward the cardholder or their bank.

2. **Delivery/fulfillment proof** - Gather evidence that the goods or services were delivered to the address or account linked to the cardholder, such as signed delivery confirmation, IP address logs, or account activity showing the customer used what was purchased.

--- metadata ---
{'input_tokens': 45, 'output_tokens': 119, 'total_tokens': 164, 'input_token_details': {'cache_read': 0, 'cache_creation': 0, 'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}}

$ python3 01_first_call.py

Pull the transaction's authentication data (AVS/CVV match results, and whether 3-D Secure/EMV 3DS was used with a successful cardholder authentication, e.g., SCA challenge completion) and the device/IP/geolocation and behavioral data captured at checkout (billing/shipping address match, device fingerprint, past purchase history from that account) to establish whether the legitimate cardholder likely completed the transaction. Also gather delivery confirmation or service usage records (proof of delivery, IP login after purchase, downloads, or usage logs tied to the account) to show the goods or services were actually received or accessed by the customer.

--- metadata ---
{'input_tokens': 57, 'output_tokens': 209, 'total_tokens': 266, 'input_token_details': {'cache_read': 0, 'cache_creation': 0, 'ephemeral_5m_input_tokens': 0, 'ephemeral_1h_input_tokens': 0}}

Enter fullscreen mode Exit fullscreen mode

出力が毎回異なる理由

ご覧のように、01_first_call.pyを2回目に実行すると異なる回答が得られます。実際に知らないことを尋ねると、完全に自信を持って回答をでっち上げるかもしれません。どちらの振る舞いも同じ原因から来ています。それはモデルがテキストを生成する方法です。

モデルは出力トークンを1つずつ生成します。各ステップで、モデルは可能な次のトークンごとに確率を予測し、その中から1つをサンプリングし、入力を追加し、次の出力トークンを得るために繰り返します。この単一のメカニズムから2つの結果が生じ、どちらもこの後に構築するすべてのものに関係します。

1つ目は非決定性です。モデルは毎回最も可能性の高いトークンを選ぶわけではありません。むしろ分布からサンプリングするため、同じ入力でも次の呼び出しで異なる出力が生成される可能性があります。サンプリングの自由度や、それを制御できるかどうかはモデルによって異なります。一部のモデルはtemperature設定を提供しています。この非決定性と向き合うことは、「通常の」ソフトウェアエンジニアリングとは異なる重要な点です。

2つ目はハルシネーションで、モデルが物事をでっち上げるという話を聞いたことがあるでしょう。これはモデルの構築方法に遡ります。モデルは事実を区別するためではなく、次のトークンを予測するように設計されています。モデルは事実を取得するのではなく、もっともらしいテキストを生成しています。質問に正しい答えがあるのにモデルに必要な情報がない場合、説得力はあるが誤った回答を自信を持って生成する可能性があります。モデルの作成者は「ポストトレーニング」を適用して、モデルに指示に従うこと、特定のフォーマットを使用すること、一部のリクエストを拒否すること、不確実性をより頻繁に認めることを教えます。しかし、それでもモデルに内部的な事実チェッカーを与えることはできません。

これらは同じコインの裏表であることに注意してください。モデルは正しい事実を取得するのではなく、もっともらしいトークンをサンプリングします。修正は通常、より良いプロンプトではありません。正確さが重要な場合にモデルの出力を制約し、適切な情報を与えるシステムをモデルの周りに構築することです。そのシステムこそが、このシリーズの残りの部分で構築するものであり、AIエンジニアが行うことです。

完了しました!

お疲れ様でした!AIエンジニアリングへの最初のステップを踏み出しました。いくつかの理論を学び、最初のモデル呼び出しを行いました。もちろん、これはまだあまり役に立つものではありません。バックエンドがこのモデルを呼び出す場合、どのような出力が得られるか分かりません。出力メッセージは非構造化であり、JSONレスポンスで行うようにオブジェクトに簡単にパースできません。次の記事では、まさにそれを行います。モデルの出力がコードが確実に依存できる構造に従うようにします。