File size: 3,860 Bytes
e22b238
 
9daf928
f67c66f
9daf928
e22b238
 
 
 
 
 
 
 
 
 
 
 
9daf928
 
f67c66f
 
 
 
 
9daf928
 
 
f67c66f
 
 
9daf928
 
f67c66f
9daf928
 
 
 
 
 
f67c66f
e22b238
9daf928
 
 
 
 
 
 
 
 
 
 
 
e22b238
 
 
 
 
 
9daf928
 
 
 
 
 
f67c66f
 
 
 
e22b238
9daf928
 
 
 
 
 
 
 
 
e22b238
9daf928
 
 
 
e22b238
 
 
9daf928
 
 
 
 
e22b238
9daf928
 
 
 
 
 
f67c66f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import os
from typing import Literal
from openai import OpenAI
from app.config import settings

# 遅延初期化:インポート時にHTTPクライアントを作らない(環境差異の影響を減らす)
_client = None

def _get_client() -> OpenAI:
    global _client
    if _client is None:
        api_key = settings.OPENAI_API_KEY or os.getenv("OPENAI_API_KEY", "")
        if not api_key:
            # 起動時ではなく呼び出し時に明示エラー
            raise RuntimeError("OPENAI_API_KEY is not set. Please add it to HF Spaces Secrets or .env.")
        _client = OpenAI(api_key=api_key)
    return _client

TONE_MAP = {
    "neutral": "ニュートラルで事実中心",
    "formal": "フォーマルで簡潔",
    "friendly": "親しみやすく平易",
    "investor": "投資家向け、KPI/財務メトリクスを強調",
    "pr_bold": "PR向け、ヘッドラインを強調し勢いある表現",
}

TYPE_GUIDE = {
    "press_release": "ニュースリリース: タイトル/リード/本文/会社概要/問い合わせ先",
    "ir_letter": "IRレター: タイトル/ご挨拶/ハイライト/今後の見通し/お問い合わせ",
    "investor_summary": "投資家向けサマリー: 3-7箇条書き、KPI・成長要因・リスク要因",
}

SYSTEM_TMPL = """
あなたは日本語のPR/IRライターです。事実の正確性、簡潔さ、再利用しやすいMarkdown構成を重視します。
- 出力は必ずMarkdown。
- センセーショナルすぎる表現や根拠のない主張は避ける。
- プレス/IRのルールに配慮し、誤解を招かない文に整える。
"""

PROMPT_TMPL = """
【目的】{ctype}のドラフトを{tone_ja}で作成。
【入力テキスト要約】以下の素材から、要点を抽出し、{ctype_guide}に沿ってMarkdownドラフトを生成。
---
{source_text}
---
出力要件:
- 1行目に短いタイトル(H1)
- 適切な小見出し(H2/H3)
- 箇条書きは短文で
- ファクトと数値はそのまま
- 最後に「メタデータ」セクションを追加(推奨タグ・要約140字・推奨URLスラッグ)
"""

def generate_draft(
    source_text: str,
    content_type: Literal["press_release", "ir_letter", "investor_summary"],
    tone: str,
):
    client = _get_client()
    tone_ja = TONE_MAP.get(tone, TONE_MAP["neutral"])
    guide = TYPE_GUIDE[content_type]

    messages = [
        {"role": "system", "content": SYSTEM_TMPL},
        {"role": "user", "content": PROMPT_TMPL.format(
            ctype=content_type,
            tone_ja=tone_ja,
            ctype_guide=guide,
            source_text=source_text[:12000],
        )},
    ]

    resp = client.chat.completions.create(
        model=settings.OPENAI_MODEL,
        temperature=settings.OPENAI_TEMPERATURE,
        messages=messages,
    )
    text = resp.choices[0].message.content

    # 件名(Subject)候補A/B
    subj_resp = client.chat.completions.create(
        model=settings.OPENAI_MODEL,
        temperature=0.3,
        messages=[
            {"role": "system", "content": "日本語のPRメール件名ライター。50文字以内、事実に忠実。"},
            {"role": "user", "content": f"次のMarkdownドラフトから、差分のある件名案を2つ出して。\n---\n{text[:6000]}"},
        ],
    )
    subs = subj_resp.choices[0].message.content.splitlines()
    subj_a = next((s for s in subs if s.strip()), "お知らせ")
    subj_b = next((s for s in subs[1:] if s.strip()), subj_a + "【続報】")

    # タイトル抽出(先頭行 # 見出し)
    title = None
    for line in text.splitlines():
        if line.strip().startswith("# "):
            title = line.strip().lstrip("# ").strip()
            break

    return title or "ドラフト", text, subj_a.strip("- ・* "), subj_b.strip("- ・* ")