#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
get_from_ai.py - AIサービスからの応答取得スクリプト
このスクリプトは、指定されたINIファイルからプロンプトを読み込み、
Google GeminiまたはOpenAI APIを利用してAIからの応答を取得し、
その応答を指定されたファイルパスに書き出します。
コマンドライン引数でINIファイル、出力ファイル、使用するAPI、モデルを指定できます。
:doc:`get_from_ai_usage`
"""
import os
import sys
import argparse
import re
from pathlib import Path
from openai import OpenAI
import google.generativeai as genai
[ドキュメント]
def read_ini(path):
"""
寛容な key=value 形式のINIファイルを読み込みます。
この関数は、`key=value` の形式で設定を読み込みます。
値が `\"\"\"` または `\'\'\'` で始まる場合、次の同じ記号が現れるまでをマルチラインの値として扱います。
また、ファイルに `key=` のない最初の有効な行が `\"\"\"` または `\'\'\'` で始まる場合、
または単にテキストである場合、その内容を `PROMPT` キーのマルチライン値として扱います。
:param path: 読み込むINIファイルのパス。
:type path: str
:returns: INIファイルから読み込まれた設定を格納した辞書。
:rtype: dict
:raises FileNotFoundError: 指定されたINIファイルが見つからない場合に発生します。
"""
if not os.path.isfile(path):
raise FileNotFoundError(f"INIファイルが見つかりません: {path}")
result = {}
buf = []
current_key = None
multi = None
first_valid_line = True
with open(path, encoding="utf-8") as f:
for line in f:
line = line.rstrip()
if not line or line.startswith(("#", ";")):
continue
# multi-line mode
if multi:
if line.strip() == multi:
result[current_key] = "\n".join(buf)
buf = []
multi = None
else:
buf.append(line)
continue
# key=value line
if "=" in line:
k, v = map(str.strip, line.split("=", 1))
if v in ('"""', "'''"):
multi = v
current_key = k
buf = []
else:
result[k] = v
first_valid_line = False
else:
# 最初の有効行で = がない → multiライン開始と仮定
if first_valid_line:
if line in ('"""', "'''"):
multi = line
current_key = "PROMPT"
buf = []
else:
# 開始記号がない場合でも、multiラインとして扱う
multi = '"""' # 仮の閉じ記号(どちらでもよい)
current_key = "PROMPT"
buf = [line]
first_valid_line = False
else:
# それ以降の =なし行は無視またはログ出力
pass
# ファイル末尾まで multi-line が閉じられなかった場合
if multi and buf:
result[current_key] = "\n".join(buf)
return result
[ドキュメント]
def call_ai(prompt, api="gemini", model=None):
"""
指定されたプロンプトを使用してAIサービス(Google GeminiまたはOpenAI)を呼び出します。
この関数は、`api` 引数の値に基づいてGoogle GeminiまたはOpenAIのAPIを利用します。
APIキーは環境変数 `GOOGLE_API_KEY` または `OPENAI_API_KEY` から読み込まれます。
モデルが指定されない場合、環境変数 `GOOGLE_MODEL` または `OPENAI_MODEL`、
あるいはデフォルトのモデル (`gemini-2.5-flash`, `gpt-4o`) が使用されます。
:param prompt: AIに渡すプロンプト文字列。
:type prompt: str
:param api: 使用するAIサービスを指定します ('gemini', 'openai', 'openai5')。
:type api: str
:param model: 使用するAIモデルの名前(オプション)。指定しない場合は環境変数またはデフォルトが使用されます。
:type model: str or None
:returns: AIからの応答テキスト。
:rtype: str
:raises ValueError: 環境変数が未設定の場合、または未対応のAPIが指定された場合に発生します。
"""
if api == "gemini":
key = os.getenv("GOOGLE_API_KEY")
if not key:
raise ValueError("環境変数 GOOGLE_API_KEY が未設定です。")
genai.configure(api_key=key)
model = model or os.getenv("GOOGLE_MODEL", "gemini-2.5-flash")
print(f"🚀 Gemini [{model}] 実行中...")
m = genai.GenerativeModel(model)
resp = m.generate_content(prompt)
return resp.text
elif api in ("openai", "openai5"):
key = os.getenv("OPENAI_API_KEY")
if not key:
raise ValueError("環境変数 OPENAI_API_KEY が未設定です。")
model = model or os.getenv("OPENAI_MODEL", "gpt-4o")
print(f"🚀 OpenAI [{model}] 実行中...")
client = OpenAI(api_key=key)
if api == "openai5":
r = client.responses.create(model=model, input=prompt)
return r.output_text
else:
r = client.chat.completions.create(model=model,
messages=[{"role": "user", "content": prompt}])
return r.choices[0].message.content
else:
raise ValueError(f"未対応API: {api}")
[ドキュメント]
def main():
"""
スクリプトのメインエントリーポイント。
コマンドライン引数を解析し、INIファイルからプロンプトを読み込み、
指定されたAIサービスを呼び出して応答を取得し、その結果を指定された出力ファイルに書き込みます。
プロンプトがINIファイルに見つからない場合はエラーで終了します。
"""
p = argparse.ArgumentParser(description="get_from_ai.iniからプロンプトを読み込み、AI応答をファイルに出力")
p.add_argument("--inifile", "-i", default="get_from_ai.ini", help="プロンプトを含むINIファイル (default: get_from_ai.ini)")
p.add_argument("--output_path", "-o", required=True, help="AI応答を書き込む出力ファイルパス")
p.add_argument("--api", "-a", choices=["gemini", "openai", "openai5"], default="gemini", help="使用するAPI")
p.add_argument("--model", "-m", default=None, help="モデル名を明示指定(任意)")
args = p.parse_args()
print()
data = read_ini(args.inifile)
print(f"keys in [{args.inifile}]:")
for key in data.keys():
print(f" {key}")
prompt = data.get("PROMPT") or data.get("PROMPT_TEMPLATE_JA") or data.get("PROMPT_TEMPLATE_EN")
if not prompt:
print("❌ INIにPROMPTが定義されていません", file=sys.stderr)
sys.exit(1)
print("Prompt:", prompt)
print(f"Requesting to {args.api}/{args.model}...")
response = call_ai(prompt, api=args.api, model=args.model)
Path(args.output_path).write_text(response.strip(), encoding="utf-8")
print(f"✅ 出力ファイルを生成しました: {args.output_path}")
if __name__ == "__main__":
main()