ローカルLLMにワークフローを作らせる
This commit is contained in:
99
autonomous_windmill/llm_interface.py
Normal file
99
autonomous_windmill/llm_interface.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""Ollama へのプロンプト送信と JSON 抽出。"""
|
||||
import json
|
||||
import re
|
||||
import httpx
|
||||
from config import OLLAMA_URL, OLLAMA_MODEL
|
||||
|
||||
_SYSTEM_PROMPT = """\
|
||||
あなたはWindmillフロー生成AIです。
|
||||
以下のルールを必ず守ってください:
|
||||
- JSONのみ出力すること
|
||||
- Markdownのコードブロック(```)は使わない
|
||||
- 説明文・コメントは一切出力しない
|
||||
- フィールド順は必ず summary → value の順にすること
|
||||
- 出力するJSONは必ず以下のスキーマに従うこと:
|
||||
|
||||
{
|
||||
"summary": "<タスクを一言で表す英語の説明>",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "<タスクを実行するPython3コード>",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
【必須ルール】
|
||||
- content のコードは必ず def main(): で始めること(Windmillのエントリーポイント)
|
||||
- main() がない場合は AttributeError になるため絶対に省略しないこと
|
||||
- content の内容はユーザーのタスク説明に従って書くこと(テンプレートをそのままコピーしないこと)
|
||||
- content 内の改行は \\n でエスケープすること(リテラル改行を入れると JSON パースエラーになる)
|
||||
- modules.id は a, b, c... の連番。追加フィールド禁止。
|
||||
|
||||
【出力例1】タスク: 「おはよう」と表示する
|
||||
{"summary":"Print greeting","value":{"modules":[{"id":"a","value":{"type":"rawscript","language":"python3","content":"def main():\\n print('おはよう')","input_transforms":{}}}]}}
|
||||
|
||||
【出力例2】タスク: 1から5までの数字を表示する
|
||||
{"summary":"Print numbers 1 to 5","value":{"modules":[{"id":"a","value":{"type":"rawscript","language":"python3","content":"def main():\\n for i in range(1, 6):\\n print(i)","input_transforms":{}}}]}}
|
||||
|
||||
【出力例3】タスク: 現在の日時を表示する
|
||||
{"summary":"Display current datetime","value":{"modules":[{"id":"a","value":{"type":"rawscript","language":"python3","content":"def main():\\n from datetime import datetime\\n print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))","input_transforms":{}}}]}}\
|
||||
"""
|
||||
|
||||
|
||||
def _chat(messages: list[dict]) -> str:
|
||||
resp = httpx.post(
|
||||
f"{OLLAMA_URL}/api/chat",
|
||||
json={
|
||||
"model": OLLAMA_MODEL,
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
"options": {"temperature": 0.1, "top_p": 0.9},
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
raw = resp.json()["message"]["content"].strip()
|
||||
return _extract_json(raw)
|
||||
|
||||
|
||||
def _extract_json(raw: str) -> str:
|
||||
"""LLM がコードブロックで囲んでしまった場合でも JSON 部分を取り出す。"""
|
||||
# ```json ... ``` または ``` ... ``` を除去
|
||||
match = re.search(r"```(?:json)?\s*([\s\S]+?)\s*```", raw)
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
return raw
|
||||
|
||||
|
||||
def generate_flow(task_description: str) -> str:
|
||||
"""初回生成:タスク説明からフロー JSON を生成する。"""
|
||||
messages = [
|
||||
{"role": "system", "content": _SYSTEM_PROMPT},
|
||||
{"role": "user", "content": f"以下のフローをJSON形式で生成してください。\n要件: {task_description}"},
|
||||
]
|
||||
return _chat(messages)
|
||||
|
||||
|
||||
def fix_flow(previous_flow_json: str, error_log: str) -> str:
|
||||
"""リトライ生成:前回の JSON + エラーログから修正版を生成する。"""
|
||||
messages = [
|
||||
{"role": "system", "content": _SYSTEM_PROMPT},
|
||||
{"role": "user", "content": (
|
||||
"前回のフロー実行でエラーが発生しました。修正したフローをJSON形式で出力してください。\n\n"
|
||||
f"--- 前回のフローJSON ---\n{previous_flow_json}\n\n"
|
||||
f"--- エラーログ ---\n{error_log}\n\n"
|
||||
"--- 修正指示 ---\n"
|
||||
"- 前回と同一のJSONは絶対に出力しないこと\n"
|
||||
"- エラーの原因箇所のみ修正すること\n"
|
||||
"- スキーマは変えないこと"
|
||||
)},
|
||||
]
|
||||
return _chat(messages)
|
||||
Reference in New Issue
Block a user