C# 50行でOpenAI Codex CLIを任意のLLMに繋ぐコンセプト画像

OpenAI Codex CLIをClaude・Gemini・Llamaの上で動かす — C# 50行で

OpenAIのCodex CLIは優れたエディタエージェントUXを提供します(shellツール、apply_patch、plan tracking がすべて揃っています)。問題は、2026年2月時点でOpenAI Responses APIだけをサポートしていることです。Chat Completionサポートは削除されており(codex-rs/model-provider-info/src/lib.rsのWireApi enumにはResponsesのみが残っています)、Chat Completionのみをサポートするエンドポイント(Ollama、LM Studio、お気に入りのLlama runner)はそのまま閉ざされてしまいます。本記事は.NET 10のfile-basedプログラムとMicrosoft.Extensions.AIのIChatClient抽象化を活用し、50行のC#一ファイルでResponses互換サーバーを立て、OpenRouterを介してCodex CLIを任意のモデル上で動作させた過程をまとめます。 はじめに Codex CLIはResponsesを話すどんなサーバーとも気持ちよく対話します。model_provider configブロックがまさにこのために存在します。つまり、好きなモデルでバックされたResponses互換HTTPエンドポイントを立てさえすれば、Codexは汎用フロントエンドになり、頭脳はユーザーが選べます。 最近私が気に入っているトリックは次のとおりです。Microsoft.Extensions.AIのベンダー中立なIChatClient抽象化の上で、OpenAI Chat CompletionサーバーとResponses APIサーバーを同時に動かす50行のC#スクリプトを起動します。バックエンドはOpenRouterに向けます(APIキー1つで Claude、Gemini、Llama、GPTなど数百のモデルを使えます)。そしてCodexに対して、OpenAIではなくこのローカルスクリプトと対話するよう設定します。 最終的な結果は、OpenAI Codex CLIがAnthropicのClaude 3.5 Sonnetの上で動作する状態です(あるいはその日に使いたい別のモデルの上でも)。 構成要素 私が自分で公開しているCadenza.AgentというMSBuild SDKを使います。単一の.csファイルを実行可能なエージェントサーバーに変換するSDKで、.NET 10のfile-basedプログラム向け単一ファイルスクリプティングSDKファミリーの一部です(dotnet run script.csと同じ発想ですが、より豊富なTier-1 API(Tool、UseOllama、UseOpenAi、Runなど)を提供します)。Agentバリアントは次を公開します。 POST /v1/chat/completions — Aider / Continue / Cursor / Copilot BYOK / sgpt 向け POST /v1/responses — Codex CLI 向け どちらもユーザーが構成した同じIChatClientでバックされます。バックエンドを変えても wireフォーマットはそのままです。 LLM側はOpenRouterを使います。OpenAIのChat Completion wireフォーマットを別のbase URLでそのまま提供するので、Microsoft.Extensions.AI.OpenAIのChatClientをそのまま挿して使えます。環境変数1つ、任意のモデル。 Codexの設定はCODEX_HOME環境変数のトリックを活用します。~/.codex/config.tomlを編集する代わりに、Codexが指すディレクトリをサンプル専用に別途作っておけば、そこから新しいconfig.tomlを読みます。これでユーザーのグローバル設定に一切触れない、自己完結型のサンプルが作れます。 スクリプト バックエンドのすべて、ファイル1つです。 #!/usr/bin/env dotnet run #:sdk Cadenza.Agent@1.0.14 using System.ClientModel; using OpenAI; var apiKey = Env.Get("OPENROUTER_API_KEY") ?? throw new InvalidOperationException("OPENROUTER_API_KEY env var missing"); var model = Env.Get("OPENROUTER_MODEL") ?? "anthropic/claude-3.5-sonnet"; ServedModelName = "cadenza-codex-openrouter"; // サンプル専用のCodex homeディレクトリを作成。 var codexHome = Path.Combine(Env.Cwd, ".cadenza-codex-openrouter"); MakeDir(codexHome); var catalogPath = Path.Combine(codexHome, "cadenza-catalog.json").Replace('\\', '/'); var configToml = $""" model = "cadenza-codex-openrouter" model_provider = "cadenza" model_catalog_json = "{catalogPath}" [model_providers.cadenza] name = "Cadenza.Agent (OpenRouter-backed)" base_url = "http://localhost:8080/v1" wire_api = "responses" env_key = "CADENZA_API_KEY" stream_idle_timeout_ms = 300000 """; WriteText(Path.Combine(codexHome, "config.toml"), configToml); // Catalog JSON: Codex に提供するモデル id を宣言して "Defaulting to // fallback metadata" 警告を防ぎます。フィールドは codex-rs/protocol/src/ // openai_models.rs の ModelInfo スキーマ基準 — すべてのキーが必須です。 var catalogJson = """ { "models": [{ "slug": "cadenza-codex-openrouter", "display_name": "Cadenza (OpenRouter)", "description": "OpenRouter-backed agent served by Cadenza.Agent", "supported_reasoning_levels": [], "shell_type": "default", "visibility": "list", "supported_in_api": true, "priority": 50, "availability_nux": null, "upgrade": null, "base_instructions": "", "supports_reasoning_summaries": false, "support_verbosity": false, "default_verbosity": null, "apply_patch_tool_type": "freeform", "truncation_policy": { "mode": "tokens", "limit": 8192 }, "supports_parallel_tool_calls": true, "context_window": 200000, "max_context_window": 200000, "auto_compact_token_limit": 180000, "effective_context_window_percent": 95, "experimental_supported_tools": [] }] } """; WriteText(Path.Combine(codexHome, "cadenza-catalog.json"), catalogJson); WriteLine($"Codex config generated at: {codexHome}"); WriteLine("In another terminal, run:"); WriteLine($" $env:CODEX_HOME = \"{codexHome}\""); WriteLine($" $env:CADENZA_API_KEY = \"any-non-empty-string\""); WriteLine($" codex"); // OpenRouter を LLM バックエンドとして接続。 var openAiOptions = new OpenAIClientOptions { Endpoint = new Uri("https://openrouter.ai/api/v1") }; var chatClient = new OpenAI.Chat.ChatClient(model, new ApiKeyCredential(apiKey), openAiOptions) .AsIChatClient(); UseChatClient(chatClient); await Run(); これがすべてです。プロジェクトファイルも、.csprojも、Program.csもありません。一番上の#:sdkディレクティブが.NET 10のfile-basedプログラムシステムに「このスクリプトはCadenza.Agent SDKを使う」と知らせ、SDKがHTTPサーバー、Responses wireフォーマット、すべてのパッケージ参照を引き込みつつ、Tool、UseOllama、UseChatClient、Runを名前空間なしですぐ呼べる名前として公開します。 ...

2026年5月27日 · 3 分 ·  rkttu
C#とPythonが出会う機械学習インターロップの概念図

C#からHugging Faceモデルを呼ぶ:DotNetPy 0.6.0でWhisper・sentence-transformers・Stable Diffusionを動かす

週末に小さなC#ライブラリ DotNetPy の0.6.0をリリースしました。CPythonのC APIを直接呼び出して、.NETアプリの中でPythonを動かすインターロップライブラリです。本記事は0.6.0に含まれる3つの機械学習サンプル(sentence-transformersによる意味検索、Whisperによる音声認識、Stable Diffusion Turboによる画像生成)をどうまとめたか、そしてその過程でPEP 703 free-threaded CPythonをどのように検証したかを記録したものです。 出発点:手元にあるのはC#だけ、モデルはHugging Faceにある 数か月ごとに同じパターンが繰り返されます。字幕用にWhisperが必要、検索用にsentence-transformerが必要、ときにはStable Diffusionのようなモデルを使いたい。しかし手元にあるのはC#一つだけ。こんなとき、よく取られる回避策にはそれぞれ決定的な欠点があります。 ONNXに変換する。 画像系やエンコーダ系のモデルにはよく合いますが、新しいアーキテクチャやdiffusionパイプラインでは、変換そのものが別プロジェクトになります。 Pythonマイクロサービスとして立ち上げる。 プロセスが2つ、デプロイのシナリオが2つ、そしてホットパスにネットワークホップが1つ追加されます。 外部APIを呼び出す。 コストがかかり、インターネット接続が必要で、データが箱の外に出ます。 pythonnet や CSnakes を使う。 堅実な選択肢ですが、pythonnetはまだNative AOTをサポートしておらず、CSnakesはSource Generatorベースのワークフローを強制します。さらに、両ライブラリともfree-threaded CPythonビルドに対する公開された検証結果をまだ出していません。 私はもう少し薄い選択肢が欲しかったのです。C#コードの中にPythonスニペットを文字列としてインラインで書き、配列をそのまま渡し、JSON形式で結果を受け取り、全体がAOTコンパイルされて単一バイナリになる 形です。それがDotNetPyの目標であり、以下の3つのサンプルはすべてGPUのない普通のWindows 11ノートPCで最初から最後まで動作します。 サンプル1 — sentence-transformers による意味検索 最初のサンプルは小さなコーパスを埋め込み、クエリをエンコードして、最も類似する上位K件の文を返します。戻り値はDotNetPyValueで、内部的にはJSONドキュメントをラップしたもので、GetString()・GetInt32()・GetDouble()、そしてパスベースのプロパティアクセスを通じて.NETの世界に戻ってきます。 using DotNetPy; using DotNetPy.Uv; using var project = PythonProject.CreateBuilder() .WithProjectName("dotnetpy-ml-embeddings") .WithPythonVersion("==3.12.*") .AddDependencies( "sentence-transformers==2.7.0", "transformers==4.40.2", "torch>=2.2,<2.5") .Build(); await project.InitializeAsync(); var executor = project.GetExecutor(); executor.Execute(@" import numpy as np from sentence_transformers import SentenceTransformer model = SentenceTransformer('all-MiniLM-L6-v2') "); var corpus = new[] { "Python is a popular programming language for data science.", "C# and .NET are great for building enterprise applications.", "Rust offers memory safety without garbage collection.", "Pizza is delicious with various toppings.", // … }; var query = "Tell me about programming languages"; using var hits = executor.ExecuteAndCapture(@" corpus_emb = model.encode(corpus, normalize_embeddings=True) query_emb = model.encode([query], normalize_embeddings=True)[0] sims = corpus_emb @ query_emb top_idx = np.argsort(-sims)[:3] result = [ {'rank': int(rank + 1), 'score': float(sims[i]), 'text': corpus[int(i)]} for rank, i in enumerate(top_idx) ] ", new Dictionary<string, object?> { { "corpus", corpus }, { "query", query } }); foreach (var hit in hits!.RootElement.EnumerateArray()) { Console.WriteLine($" {hit.GetProperty("rank").GetInt32()}. " + $"[{hit.GetProperty("score").GetDouble():F3}] " + $"{hit.GetProperty("text").GetString()}"); } 実際の出力はこうなります。 ...

2026年5月11日 · 4 分 ·  rkttu
オープンソースプロジェクト間の協力とコードポーティングを象徴するイメージ

AI時代のオープンソース貢献:HwpLibSharpポーティングプロジェクトから学んだこと

AI時代のオープンソース貢献:HwpLibSharpポーティングプロジェクトから学んだこと Microsoft MVPとして活動して17年になります。その間、.NETコミュニティで最も多く受けた質問の一つが「C#でHWPファイルをどう扱えばいいですか?」でした。韓国のハンコム社の公式ライブラリはWindowsとCOMベースであり、クロスプラットフォーム.NET環境では事実上、解決策がありませんでした。 そんな中、@neolord0さんのhwplibを見つけました。Javaで書かれた、純粋にHWPファイルフォーマットをパースするオープンソースライブラリです。「これを.NETに移植すればコミュニティに貢献できる」とすぐに思いましたが、簡単なことではありませんでした。コードベースが膨大な上に、今も継続的にアップデートされていたからです。 2026年、AIコーディングアシスタントと共にこの作業を始めました。 一回限りのポーティングではなく「同期化」 一見、HWPは頻繁に変わるフォーマットではないため、一度ポーティングすれば手を加える必要はないと思いがちです。しかし、あらゆる技術は変化し続けます。元のプロジェクトが今も活発にメンテナンスされているのはもちろん、.NET技術自体も進化しているため、一回限りの作業では不十分です。 従来のフォーク(fork)は、時間が経つほど原本との距離が開きます。最終的に「自分たちのバージョン」と「オリジナル」がそれぞれ別の道を歩むことになります。当然の流れですが、それでも別のアプローチを選びました。単なるフォークではなく、** 原本と共に生き続ける「移植された実装(Ported Implementation)」** というアイデンティティを明確にしたのです。 元プロジェクト: hwplib (Java) ↓ 定期的な同期 ポーティングプロジェクト: HwpLibSharp (C#) ↓ .NET特化の改善 エコシステム拡張 そのため、READMEにもこう記載しました。 「本プロジェクトの意思決定および判断の優先権は、オリジナルプロジェクトの作者である @neolord0 氏の意思を優先します。」 これは礼儀のために書いた文章ではありません。2つのプロジェクトが長期的に共存するためには、誰が方向性を決めるのかを最初から明確にする必要があると判断したからです。 AIと共に行ったポーティング作業 正直に言えば、AIコーディングアシスタントなしでこのプロジェクトを進めていたら、初期ポーティングだけで6ヶ月以上かかっていたでしょう。アップストリーム同期は到底手が出せず、結局放置された「もう一つのレガシー」になっていたはずです。 しかし、AIと一緒に作業することで、まったく異なるアプローチが可能になりました。 AIがうまくできたこと 構文変換はほぼ完璧でした。Javaのgetter/setterをC#プロパティに変換し、命名規則をC#コンベンションに合わせ、nullチェックをNullable参照型に変換する作業は、ほとんど自動化できました。 ライブラリのマッピングでもAIの助けは大きかったです。Apache POIをOpenMcdfに置き換える際、「Javaでこのライブラリが果たす役割を.NETでは何で代替できるか」を素早く見つけてくれました。元プロジェクトがアップデートされるたびに変更点を追跡してC#バージョンに反映する反復作業でも、ヒューマンエラーを大幅に削減してくれました。 Upstream変更の自動追跡 この過程で特に効果的だったのは、AIエージェントを活用した** upstream変更の自動追跡** 方式でした。元のhwplibプロジェクトに新しいコミットが入るたびに、AIエージェントが変更されたJavaソースファイルと対応するC#ソースファイルを比較分析し、実装上の差異を検出します。 この同期を体系的に管理するために、ポーティングされたすべてのC#ソースファイルの先頭に、元のJavaファイルとの対応関係を明示するヒントヘッダーを残しました。 // ===================================================================== // Java Original: kr/dogfoot/hwplib/util/compressors/Compressor.java // Repository: https://github.com/neolord0/hwplib // ===================================================================== このヘッダーがあれば、AIエージェントは「このC#ファイルの元はJava側のどのファイルか」を即座に把握できます。upstreamでCompressor.javaが変更されると、AIが対応するC#ファイルを見つけてdiffを分析し、不足している変更点や実装上の差異をレポートしてくれます。人間が数百のファイルを一つずつ照合する必要なく、AIが「この部分が原本と異なっているので確認が必要です」と教えてくれる方式です。 実際にこの方式を導入した後、upstream同期にかかる時間が従来比80%以上短縮されました。以前は変更ログを読んで関連ファイルを一つずつ探しながら手動で反映していたのが、今ではAIが変更リストと影響範囲を自動的に整理してくれるようになりました。 AIができなかったこと 一方、HWPファイルのSection-Paragraph-Control構造、各コントロールの意味、韓国語ワープロ文書ならではの特性といったドメイン知識は、完全に自分の役割でした。AIがもっともらしく提案したコードが実際に正しいかどうかの検証は、やはり人間がやるべき仕事です。 戦略的な意思決定も同様でした。Native AOTをサポートするか、Blazor WebAssemblyでどう動作させるか、どの.NETバージョンまでサポートするか。こうした判断には.NETエコシステム全般への理解と、実際のユーザー環境に対する感覚が必要です。ライセンス文言、原作者との関係設定、韓国の開発者コミュニティの文脈を反映する作業も、AIが下書きを作り、自分が仕上げるという方法で進めました。 .NETエコシステムに合わせた再設計 単にコードを移すだけで終わりではありませんでした。.NET開発者が自然に使えるよう、原本プロジェクトの哲学と意図を損なわない範囲でAPIを整える作業も必要でした。 // Blazor WebAssemblyサポート(ファイルシステムなしでストリームから) var hwpFile = HWPReader.FromStream(memoryStream); // Native AOT互換、URLからの非同期ロード var hwpFile = await HWPReader.FromUrlAsync(url); これらの機能はオリジナルのJavaバージョンには存在しません。しかし.NET開発者なら当然期待するものです。おかげで、Azure FunctionsでHWPを処理したり、Blazorでブラウザ上にHWPをレンダリングすることも可能になりました。 ...

2026年2月7日 · 1 分 ·  rkttu
AIコーディングツールと開発者のバランスの取れた関係

AIコーディングツール、「遅れをとる」という不安に振り回されない方法

最近のAIコーディングツール業界のニュースを見ていると、新しいツールが登場するたびに「これが未来だ」「使わなければ遅れをとる」というメッセージが過度に強調されているのがわかります。バックグラウンドエージェント、並列AIセッション、自律コーディング—毎週新しい概念が登場し、それを導入しなければ時代に遅れているかのように感じさせられます。 しかし、このようなメッセージをそのまま受け入れることが本当に健全なアプローチでしょうか?私はそうではないと思います。 Hypeはどのように作られるのか AIコーディングツール業界のhypeには構造的な理由があります。AI技術で収益を上げなければならない企業の経営者は、投資家や株主を満足させなければならない立場にあります。そのため、彼らのメッセージには二重の聴衆問題が存在します。開発者には「生産性向上」を約束しながら、同時に投資家には「市場支配力」と「不可欠なツール」という物語を提供しなければなりません。 この過程で「半分だけ正しい言葉」が溢れ出します。「AIがコーディングを革新する」は間違っていませんが、「今すぐこのツールを使わなければ淘汰される」という飛躍が一緒にパッケージングされます。完全な嘘は簡単に反論されますが、誇張された真実は検証が困難だからです。 このようなメッセージはAI企業から始まり、先を行こうとする一部の企業の経営者や技術リーダー、そしてインフルエンサーを経て、実務開発者に届きます。問題は、中間の伝達者たちが自分の利害関係のためにメッセージをフィルタリングするよりも、より強く増幅させる傾向があるということです。「最新技術を導入している」というイメージ自体が彼らの市場でのポジショニングであり営業ツールになるからです。 バックグラウンドエージェントの約束と現実 最近強調されているトレンドの一つが「バックグラウンドエージェント」です。VS CodeのAgent HQ、Google AntigravityのManager Viewなどの機能が代表的です。これらのツールの核心的な約束は、タスクを委任すればAIが別の環境で自律的に実行し、開発者は他の作業ができるというものです。複数のエージェントを並列で実行すれば生産性が何倍にも向上するという主張も付いてきます。 しかし、この約束には根本的な問題があります。バックグラウンドエージェントは本質的にHuman-In-The-Loop(HITL)を意図的に排除する方向で設計されています。「非同期作業」「自律実行」「委任後の確認」という概念自体が、中間介入ポイントをなくすことを前提としています。 実際に事故はすでに発生しています。GoogleのAntigravityエージェントがハードドライブのパーティション全体を削除した事件が報告されています。従来のチャットウィンドウで作業する場合、各ステップで結果を確認して途中で介入できますが、バックグラウンドエージェントは間違った方向に進んでも完了するまで認識しにくいのです。 免責条項とマーケティングの矛盾 興味深い点は、すべてのAIサービス事業者が「AIは間違える可能性があるので必ず確認してください」という免責条項を明示していることです。しかし同時に、マーケティングでは「自律的に働くエージェント」を強調しています。この二つのメッセージを同時に真剣に受け止めると、並列エージェントの効用はかなり限定的にならざるを得ません。 「複数のエージェントを並列で実行してこそ本当の生産性」という主張は、「AI出力は必ず確認せよ」という警告と正面から衝突します。一つのセッションの出力でも検証が必要なのに、それを五つ同時に実行すれば検証の負担が五倍になるのであって、生産性が五倍になるのではありません。ボトルネックが生成から検証に移動するだけで、全体のスループットが線形に増加するわけではありません。 逆説的に、五人を並列で投入しても、管理と確認なしには品質は保証されません。調整コストが増加し、一貫性が崩れ、成果物の品質のばらつきが大きくなります。これはソフトウェアエンジニアリングで数十年間繰り返し検証されてきた事実です。AIエージェントがこの原則から自由である理由はありません。 技術リーダーが持つべき姿勢 CTOや技術意思決定者の立場から見ると、この状況で最も重要な徳目は「冷静な距離感と判断」です。問題はこれを実践するのが構造的に難しいということです。冷静な判断を下すと「革新に消極的だ」「トレンドを知らない」という評価を受けるリスクがあり、その判断が正しかったことは数年後にようやく検証されます。 マクロ的に見ると、技術に対する深い理解(アンカー)がない状態で意思決定をしなければならない経営者は、外部のシグナルに頼らざるを得ません。しかし、そのシグナルの大部分が利害関係の絡んだソースから来るため、簡単に不安になり、簡単に焦ってしまう脆弱な状態に置かれることになります。 一つの実用的な代替案はLab戦略です。実験の範囲とコストを制御しながらも、組織が新しい技術を学習し評価できる空間を確保することです。失敗コストが低い領域(内部ツール、プロトタイプ)では積極的なツール選択を許可し、コアビジネス領域には検証済みのツールを維持します。こうすれば「私たちもAIツールを積極的に活用している」という物語を維持しながら、実際のビジネスリスクは管理できます。 ツール(Harness)の重要性:馬と馬具の比喩 最近、AnthropicのリサーチャーBoris Chernyが興味深い比喩を提示しました。「Claudeは馬(horse)で、Claude Codeはharness(馬具)だ。馬に乗るには鞍が必要で、その鞍が馬に乗る時に大きな違いを生む」というものです。彼の説明によると、AIコーディングが長い間うまく機能しなかった理由は、モデルが十分に良くなかったこともありますが、モデルの上のscaffolding(ツール)が十分に良くなかったことも大きな理由でした。 この比喩で重要な点は、人間のプログラマーがまだループの中にいるということです。馬具を使って馬を望む方向に導くように、開発者はツールを通じてAIを制御します。そして、そのツールの品質が成果物の品質を大きく左右します。 伝統的なIDEインフラの再発見:Visual Studio 2026の事例 この文脈で、Visual Studio 2026のデバッガエージェントは注目に値します。この機能は、単体テストが失敗した時に次のプロセスを自律的に実行します。ワークスペースからコンテキスト(テストコード、関連ソース、最近の修正)を収集し、失敗原因についての仮説を形成し、分析に基づいてコードを修正し、デバッガでテストを実行して検証し、問題が続けばデバッガのインサイトを活用して仮説を精緻化し、テストが通過するまで繰り返します。 ここで重要なのは、これらすべてが数十年間蓄積されたIDEインフラの上で動作するという点です。ブレークポイント操作、変数状態のリアルタイム追跡、コールスタック分析、シンボル解決、プロファイリング—これらはターミナルベースの軽量エージェントでは簡単に実装できない領域です。AIが「仮説を立てて検証する」ことは、このようなインフラが支えてこそ意味のある自動化になります。 .NET開発者の観点から見ると、トレンドに従ってターミナルベースのツールに移行することが必ずしも生産性向上を意味するわけではありません。むしろ、完成度の高い伝統的なIDEがAIと組み合わさった時に、より強力なシナジーが発生する可能性があります。新しいものが常により良いわけではありません。 同期セッションの高度化という現実的な代替案 バックグラウンドエージェントやHITLなしで実行されるモードに対する確信や信頼がなければ、無理して使用する理由はありません。むしろ、現在のようにHITLが維持される同期セッションベースのワークフローをより深く見つめながら高度化することが、安全性と効率性を同時に確保する戦略です。 プロンプトパターンの体系化、コンテキスト提供方式の最適化、IDE統合機能の深い活用、作業単位の適切な分割などの領域で熟練度が高まれば、新しいツールが登場しても、その価値を冷静に評価できる基準が生まれます。 結論:ツールの新規性よりツール使用の深さ 「確認してください」という警告が形式的な免責ではなく実質的な運用指針であるならば、現在のAIツールの適切な使用方法は、HITLが維持される単一または少数のセッションの深い活用です。並列自律エージェントは、AIの信頼性が今よりはるかに高くなった後にこそ意味のある選択肢になるでしょう。 ツールの新規性よりツール使用の深さが実務の生産性に大きな影響を与えます。そしてこれがhypeに振り回されない実質的な競争力になります。新しいツールが登場するたびに「これが解決する実際の問題は何か」を最初に問う習慣を身につけてください。 ツールは手段であって、アイデンティティではありません。

2026年1月27日 · 1 分 ·  rkttu
JavaからNETへのコード移植を表現した抽象的なイメージ

Java hwplibを.NETに移植する:AIと共に歩んだオープンソース移植の旅

始まりは単純な好奇心から 「HWPファイルを.NETで直接扱えたらいいのに…」 こんな考えをした.NET開発者は私だけではないでしょう。HWPファイルは韓国で公共機関を中心に今でも広く使われている文書形式ですが、.NETエコシステムではこれを適切に扱えるオープンソースライブラリがありませんでした。 これまで.NETでHWPファイルを扱うには、Windows OS限定でアレアハングルをインストールすると付属するHWP ActiveXコントロールのCOMタイプライブラリを呼び出して制御する程度しかありませんでしたが、残念ながらこれさえもサポートが終了し、今は道が閉ざされた状態です。 そんな中、Javaで書かれたhwplibを発見しました。neolord0さんが継続的にメンテナンスしてきたこのライブラリは、HWPファイルの読み書きを完成度高くサポートしていました。 以前なら、このようなライブラリを移植することは、強い使命感と目的意識がなければなかなか手を出せる作業ではありませんでした。しかし、高性能なAIモデルが多く登場し、今なら新しい挑戦ができるのではないかという好奇心がありました。 こうして始まった3週間の旅を共有します。 数字で見るプロジェクト 本格的な話の前に、Javaバージョンのプロジェクトの規模を数字でまとめてみましょう。 項目 内容 元プロジェクト neolord0/hwplib (Java) 移植プロジェクト rkttu/libhwpsharp (C#) ターゲットフレームワーク .NET Standard 2.0, .NET Framework 4.7.2, .NET 8 総コミット数 54個 開発期間 2025-12-16 ~ 2026-01-08(約3週間) 初期移植コード 641ファイル、50,010行 641ファイル、5万行。正直、最初にこの数字を見たときは「これは可能なのか?」という疑問が湧きました。 初日:AIと共に行った大規模コード変換 5万行を1日で? 2025年12月16日、プロジェクトを開始しました。通常の状況であれば、5万行のJavaコードをC#に変換するには数ヶ月かかったでしょう。しかし、AIコーディングアシスタントがゲームチェンジャーでした。 AIにJavaファイルを渡し、C#に変換してもらいました。もちろん、機械的な変換だけでは不十分でした。JavaとC#は似ているように見えますが、微妙な違いが多いからです。 // Java public byte getValue() { return value; } public void setValue(byte value) { this.value = value; } // C# public byte Value { get; set; } このようなパターン変換はAIがうまく処理してくれました。しかし、本当の問題は別にありました。 最大の難関:Apache POIからOpenMcdfへ OLE2複合ドキュメントの沼 HWPファイルはMicrosoftのOLE2複合ドキュメント形式(Compound File Binary Format)に基づいています。簡単に言えば、1つのファイルの中に複数の「ストリーム」がフォルダ構造のように格納されている形式です。 JavaではApache POIライブラリがこの形式を扱いますが、.NETには直接対応するものがありません。代わりにOpenMcdfというライブラリを使う必要がありましたが、残念ながらApache POIとはAPIが完全に異なります。 ...

2026年1月8日 · 2 分 ·  rkttu
AIと開発を象徴するイメージ

AI「学習」という用語に騙されないでください

サービス開発をしている同僚たちと話をしていると、AI導入について漠然とした負担感を感じているケースをよく見かけます。その負担感の根源を掘り下げると、大抵は**「学習(Training)」** という用語が生む誤解に起因しています。 「モデルをサービスに組み込むと、ユーザーデータを取り込んでリアルタイムで学習して賢くなるんですか?」 「じゃあ、その学習プロセスを私たちがコントロールできますか?変なことを学んだらどうしますか?」 もしこのような悩みをお持ちでしたら、少し心配を置いても大丈夫です。今日はその誤解を開発者の言葉で解説します。 「学習」はビルドタイム、「推論」はランタイム 最初に正すべきことは、** 私たちがサービスにデプロイするAIモデルは、ほとんどの場合「凍結された(Frozen)」状態である** という点です。 開発用語で例えると次のようになります。 概念 AI用語 開発用語の比喩 説明 モデルを作る 学習(Training) ビルドタイム(Build Time) 膨大なリソースと時間が必要 モデルを使う 推論(Inference) ランタイム(Runtime) リクエストを受けて結果を返す 私たちがサービスを運用するとき、実行中のバイナリコードが自らコードを修正して進化することはありません。AIも同様です。 モデルはデプロイされた瞬間、もはや「学ぶ学生」ではなく、「指示通りに働く労働者」 になります。私たちが心配すべき領域は学習ではなく、この労働者がうまく仕事ができるよう環境を整えることです。 図書館の比喩でAI構造を理解する では、最新情報はどのように反映され、AIはどのような役割を果たすのでしょうか?私はこの構造を**「図書館」** に例えることがよくあります。 構成要素 図書館の比喩 説明 AIモデル(LLM) 司書 言語能力と推論能力を持つ存在 RAG / DB 書庫 サービスデータと最新情報が保存される空間 プロンプト 業務指示書 司書に仕事を任せるときに渡すガイドライン サービス開発者である私たちが毎日やっていることは、「図書館に新刊を補充すること(DBアップデート)」 です。司書(AI)は私たちが補充した本を必要なときに取り出し(Retrieve)、内容を読んで組み合わせてユーザーに提供する(Inference)だけです。 多くの方が恐れている**「学習(Fine-tuning)」** は、「司書を大学院に送って再教育すること」 に相当します。司書が基本的な読解力が不足している場合や、一般的な常識では理解できない特殊なドメインを扱う必要がある場合にのみ必要なエッジケースです。 ほとんどのビジネス問題は、司書を再教育(学習)させることではなく、以下の3つで解決できます: 優秀な司書を採用する - 良いベースモデルを選択 良い業務マニュアル - よく設計されたプロンプト 整理された書庫 - 構造化されたRAG/DB AIは「意味処理アクセラレータ」である このように構造を把握すると、AIはもはや恐ろしい未知の存在ではありません。単に私たちのシステムアーキテクチャに組み込むべき一つの**「部品」** に過ぎません。 コンピュータの歴史は「処理の高速化」でした。 ハードウェア 高速化対象 CPU 計算とロジック(if-else)処理 GPU グラフィックスとピクセル処理 AI(LLM) 意味(Semantics)と文脈(Context)処理 私たちはこれまでif (text.contains("りんご"))のようなコードで人間の言語を機械的に処理するのに苦労してきました。今では、その「意味処理」を専門に担当する高性能アクセラレータ(AI)が登場したのです。 ...

2025年12月5日 · 1 分 ·  rkttu