はじめに
最近Nano Banana Proでの画像生成が話題になったので自分も試し始め、小説の登場人物のキャラデザさせて遊んで楽しんでいる日々です。
このとき、Geminiへの指示にYAMLを使うという話を見かけて、設定画の生成のためにキャラクター造形をYAMLで定義し、それをGeminiに与えて画像を生成するようになりました。
下手に文章だけで指示するよりも情報が整理されて伝わりやすく、自分自身も明確な要求を伝えやすく、なるほど効率的。
……コード生成でもYAMLで指示すれば良いのでは?
まずは単純に指示してみる
さてコード生成はどんなものか。まずは単純に以下のようなプロンプトを与えてみます。今回は高速モードで。
三分探索するメソッドをC#で生成してください。
出力されたコードが以下になります。
using System;
public class TernarySearch
{
/// <summary>
/// 指定された範囲内で関数の最大値を求める三分探索
/// </summary>
/// <param name="f">対象となる関数</param>
/// <param name="left">探索範囲の左端</param>
/// <param name="right">探索範囲の右端</param>
/// <param name="epsilon">許容誤差(収束条件)</param>
/// <returns>最大値をとるxの値</returns>
public static double FindMaximum(Func<double, double> f, double left, double right, double epsilon = 1e-9)
{
while (right - left > epsilon)
{
// 範囲を3等分する2つの地点を計算
double m1 = left + (right - left) / 3;
double m2 = right - (right - left) / 3;
if (f(m1) < f(m2))
{
// m2の方が値が大きいため、最大値は[m1, right]の範囲にある
left = m1;
}
else
{
// m1の方が値が大きいか等しいため、最大値は[left, m2]の範囲にある
right = m2;
}
}
return (left + right) / 2;
}
public static void Main()
{
// テスト用の関数: f(x) = -(x - 2)^2 + 10 (頂点は x = 2)
Func<double, double> myFunc = x => -Math.Pow(x - 2, 2) + 10;
double result = FindMaximum(myFunc, 0, 5);
Console.WriteLine($"最大値をとるxの値: {result:F6}"); // ほぼ 2.000000
}
}
ぱっと見た感じ、ちゃんとしたコードですね。
今回、これと違った出力をさせたいので、以下を満たすコードをゴールとしましょう。
- 変数宣言時は
varを用いること - 英字一文字およびそれに数字を加えただけの変数名は使用しないこと
- インデントはタブ
- 関数名はスネークケースにすること
- 開き括弧
{は改行せずに使用すること -
else句も改行せずに使用すること
C#としてはマイナーな書き方かなと思います(自分としてはvarはよく使いますが)。
YAMLの書式を決める
面倒くさいのでこれもGeminにやらせます。
あなたにコード生成を指示する際にコーディング規約等に基づいて欲しいです。そのためYAMLを使って指示をしたいので、そのYAMLの書式を出力してください。
出てきたのが以下。
context:
project_name: "プロジェクト名"
task_description: "何を作るか(例:ユーザー認証機能の実装)"
coding_standards:
language: "言語(例: TypeScript, Python)"
framework: "フレームワーク(例: Next.js, FastAPI)"
version: "バージョン指定(例: Python 3.11+, React 18)"
style_guide: "規約(例: Google Style Guide, Airbnb Style Guide)"
naming_convention:
variables: "camelCase / snake_case"
classes: "PascalCase"
constants: "UPPER_SNAKE_CASE"
architecture:
pattern: "設計パターン(例: Clean Architecture, MVC, Repository Pattern)"
directory_structure:
- "src/domain"
- "src/infrastructure"
constraints:
- "型定義を厳格に行うこと"
- "非同期処理は async/await を使用すること"
- "エラーハンドリングは独自例外クラスを定義すること"
- "コメントは JSDoc / Google Style で記述すること"
testing:
required: true
framework: "Jest / Pytest"
coverage_goal: "80%"
output_format:
include_explanation: true # コードの解説を含めるか
file_per_code: true # 1ファイルごとにコードブロックを分けるか
あんま意識してなかったテストとかまで出てきてちょっとビックリ。
一旦これでいいのでは?
YAMLで指示してみる
作成したYAMLは以下。
context:
project_name: "TernarySearch"
task_description: "三分探索するメソッドの生成"
coding_standards:
language: "C#"
framework: ".Net"
version: ".Net9"
style_guide: ""
naming_convention:
variables: "camelCase"
methods: "snake_case"
classes: "PascalCase"
constants: "UPPER_SNAKE_CASE"
length: "greater than 1"
others: "2文字以上の変数名であっても`a1`のように1文字の英字+数字での命名は禁止とする"
indent: "Tab"
brace_style: "K&R Style"
constraints:
- "変数宣言は`var`を用いること"
- "コメントは日本語で出力すること"
- "コメントは処理のまとまりごとに出力し、処理そのものの説明ではなく、何を目的にしているかや、何故その処理をしているかなどの観点で記述すること"
- "パブリックなクラスやメソッドには必ずXMLコメントを付与すること"
output_format:
include_explanation: true # コードの解説を含めるか
file_per_code: true # 1ファイルごとにコードブロックを分けるか
結果が以下。
using System;
namespace TernarySearch;
/// <summary>
/// 三分探索アルゴリズムを提供するクラスです。
/// </summary>
public class TernarySearcher
{
/// <summary>
/// 指定された範囲内で凸関数の最大値を探索します。
/// </summary>
/// <param name="function">探索対象となる関数</param>
/// <param name="left">探索範囲の左端</param>
/// <param name="right">探索範囲の右端</param>
/// <param name="precision">探索を終了する精度の閾値</param>
/// <returns>関数の最大値をとる引数(x)の値</returns>
public double find_maximum(Func<double, double> function, double left, double right, double precision = 1e-9) {
var currentLeft = left;
var currentRight = right;
// 指定された精度に収束するまで、探索範囲を3分割して追い込んでいきます。
// 二分探索とは異なり、関数の増減の変化点(極値)を特定するために2つの比較点を利用します。
while (currentRight - currentLeft > precision) {
// 探索範囲を1:1:1に分割するための2つの境界点を計算します。
// 1/3地点と2/3地点の値を比較することで、極値が存在しない方の端を切り捨てます。
var m1 = currentLeft + (currentRight - currentLeft) / 3.0;
var m2 = currentRight - (currentRight - currentLeft) / 3.0;
// 計算した2点における関数の値を比較します。
// 凸関数の場合、値が小さい方の外側には最大値が存在し得ないため、範囲を狭めることができます。
if (function(m1) < function(m2)) {
currentLeft = m1;
}
else {
currentRight = m2;
}
}
// 最終的に絞り込まれた範囲の中間点を、最大値を与える近似値として返却します。
return (currentLeft + currentRight) / 2.0;
}
}
- 変数宣言時は
varを用いること- 変数はすべて
varで宣言されています
- 変数はすべて
- 英字一文字およびそれに数字を加えただけの変数名は使用しないこと
- 元々1文字だった関数
fがfunctionになっています - 一方で
m1やm2といった変数名が使われているので、ここの指示は上手くいかず
- 元々1文字だった関数
- インデントはタブ
- 選択しないと解りませんがタブになってます
- 関数名はスネークケースにすること
- スネークケースになってます
- 開き括弧
{は改行せずに使用すること- 改行せずに
{が置かれています
- 改行せずに
-
else句も改行せずに使用すること- これは上手くいかず。これは「K&R」に対する理解不足で、「OTBS」とすると上手くいったかも
でもこれって……
上手くいきましたね。これからは文章ではなくYAMLで指示を──
基本的にパラメータは不変だから意味なくない?
元々のイラスト生成ではキャラクターの特徴なんかをパラメータとしてYAMLで指定していました。出力したいキャラクターや、そのときの衣装やなんかが毎回変わってくるので、これらのパラメータは可変です。
一方、今回のようなコーディング規則は少なくとも同じ言語、同じプロジェクトに対しては不変です。
うんうん、なるほどね。
三分探索するメソッドを生成してください。
デデン!
using System;
/// <summary>
/// 関数内の極値を探索するための機能を提供するクラスです。
/// </summary>
public class TernarySearcher
{
/// <summary>
/// 指定された範囲内で凸関数の最大値をとる引数を探索します。
/// </summary>
/// <param name="targetFunc">探索対象となる1変数関数</param>
/// <param name="leftEdge">探索範囲の下限</param>
/// <param name="rightEdge">探索範囲の上限</param>
/// <param name="precision">探索を終了する閾値(精度)</param>
/// <returns>最大値を与える引数 $x$</returns>
public double find_maximum(Func<double, double> targetFunc, double leftEdge, double rightEdge, double precision = 1e-9)
{
var left_side = leftEdge;
var right_side = rightEdge;
// 探索範囲が十分に収束するまで、範囲を3等分して追い込みをかける
// 二分探索とは異なり、2つの比較点を用いることで関数の傾きを判断している
while (Math.Abs(right_side - left_side) > precision)
{
var left_third = left_side + (right_side - left_side) / 3;
var right_third = right_side - (right_side - left_side) / 3;
// 三分した地点の値を比較し、極値が存在し得ない方の端を切り捨てる
// 最大値を求めたいため、値が小さい方の外側を探索範囲から除外している
if (targetFunc(left_third) < targetFunc(right_third))
{
left_side = left_third;
}
else
{
right_side = right_third;
}
}
// 最終的に絞り込まれた範囲の中央値を解として採用する
return (left_side + right_side) / 2;
}
}
デデデン!!!!!!!
これならYAML使ってGem作れば毎回毎回YAMLで指示書かなくて良くない? どうやらこれが最適解──
誰がそんな余計なこと言えって言った。
出来てないくない????
おわりに
というわけで、YAMLで細かくパラメータ指定するようなプロンプトは、コード生成を目的とする場合にはちょっと使いづらいかなあという結論。
使えなくはないのですが、毎回YAML用意するのも面倒で、もっとカジュアルに指示がしたい。
一方でGeminiのGemなんかには有効にも思えます。ただその場合は出力するコードのコーディング規則だけでなく、回答の文章に対してもある程度ガイドしてあげる必要がありそうです。


