Corin1998 commited on
Commit
3a9c939
·
verified ·
1 Parent(s): 74b51dc

Upload 22 files

Browse files
README.md CHANGED
@@ -1,8 +1,9 @@
1
- # 営業メール/提案書ジェネレーター(HF + ChatGPT)
2
-
3
- ## 事前準備
4
- - `backend/.env.example` を `backend/.env` にコピーし、`OPENAI_API_KEY` と `HF_API_KEY` を設定してください。
5
-
6
- ## ローカル起動(Docker)
7
- ```bash
8
- docker compose up --build
 
 
1
+ ---
2
+ title: Sales Writer API
3
+ emoji: 📧
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ app_port: 8000
8
+ pinned: false
9
+ ---
backend:.env.exampl ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
2
+ # "gpt-4o" など。お好みで差し替え可
3
+ OPENAI_MODEL=gpt-4o
4
+
5
+ # Hugging Face: どちらか
6
+ # 1) Inference API を使う場合(簡単・有料)
7
+ HF_API_KEY=hf_xxxxxxxxxxxxxxxxxxxxxxxx
8
+ HF_SENTIMENT_MODEL=cardiffnlp/twitter-roberta-base-sentiment-latest
9
+ HF_TOXIC_MODEL=unitary/toxic-bert
10
+ HF_SUMMARY_MODEL=facebook/bart-large-cnn
11
+
12
+ # 2) ローカル推論を使う場合は HF_API_KEY 不要(このMVPはInference API前提)
backend:app:config.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ import os
3
+
4
+ class Settings(BaseModel):
5
+ openai_api_key: str = os.getenv("OPENAI_API_KEY", "")
6
+ openai_model: str = os.getenv("OPENAI_MODEL", "gpt-4o")
7
+
8
+ hf_api_key: str = os.getenv("HF_API_KEY", "")
9
+ hf_sentiment_model: str = os.getenv("HF_SENTIMENT_MODEL", "cardiffnlp/twitter-roberta-base-sentiment-latest")
10
+ hf_toxic_model: str = os.getenv("HF_TOXIC_MODEL", "unitary/toxic-bert")
11
+ hf_summary_model: str = os.getenv("HF_SUMMARY_MODEL", "facebook/bart-large-cnn")
12
+
13
+ settings = Settings()
backend:app:openai_utils.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+ from openai import OpenAI
3
+ from .cofig import settings
4
+
5
+ client = OpenAI(api_key = settings.openai_api_key)
6
+
7
+ def chat_complete(system_prompt:str, user_prompt:str) -> str:
8
+ resp = client.chat.completions.create(
9
+ model=settings.openai_model,
10
+ messages=[
11
+ {"role": "system", "content": user_prompt},
12
+ {"role": "user", "content": user_prompt}
13
+ ],
14
+ temperature=0.6,
15
+ )
16
+ return resp.choices[0].message.content or ""
17
+
18
+ def split_subject_body(text:str) -> Tuple[str, str]:
19
+ subject, body = "",text
20
+ lower = text.replace(":",":")
21
+ if "件名:"in lower and "本文:"in lower:
22
+ s = lower.split("件名:",1)[1]
23
+ parts = s.split("本文:",1)
24
+ subject = parts[0].strip()
25
+ body = parts[1].strip()if len(parts)>1 else""
26
+ return subject.strip(), body.strip()
backend:app:prompt_templates.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ EMIL_SYSTEM="""あなたは一流のB2B営業コピーライターです。
2
+ -誤解を避けるため、簡潔で読みやすく、相手中心で書きます。
3
+ -日本のビジネス慣習を配慮し、丁寧で過度な断定や誇張を避けます。
4
+ -スパム判定を避ける表現に留意します。
5
+ -必ず件名と本文(挨拶→課題の共感→価値提案→次のアクション→署名)の順に出力します。
6
+ """
7
+
8
+ EMAIL_USER="""出力言語:{language}
9
+ 業界:{industry}
10
+ 相手企業:{target_company}
11
+ 担当者:{target_persona}
12
+ 課題:{pain_points}
13
+ 提供価値:{value_prop}
14
+ 製品名:{product_name}
15
+ CTA:{cta}
16
+ 企業トーン:{tone}
17
+ 文章ボリューム目安:{length_hint}
18
+ 差し込み変数:{variables}
19
+ 追加指示:{extra_instr}
20
+
21
+ #出力フォーマット
22
+ 件名:
23
+ 本文
24
+ """
25
+
26
+ PROPOSAL_SYSTEM="""あなたはB2B提案書のストラクチャ設計に長けたコンサルタントです。
27
+ -相手企業の課題に即した章立てとエグゼクティブサマリーを日本語で作成します。
28
+ """
29
+
30
+ PROPOSAL_USER="""相手業界:{industry}
31
+ 相手企業:{target_company}
32
+ 課題:{pain_points}
33
+ 提供価値:{value_prop}
34
+ 製品名:{product_name}
35
+ CTA:{cta}
36
+ 希望トーン:{tone}
37
+
38
+ #出力
39
+ -章立て(5~8章)
40
+ -エグゼクティブサマリー(200~300字)
41
+ """
backend:app:schemas.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional, Dict, List
3
+
4
+ class GenerateRequest(BaseModel):
5
+ language: str = Field("ja", description="出力言語 'ja' or 'en'")
6
+ industry: str = Field(..., description="相手の業界")
7
+ target_company: str = Field(..., description="相手企業名")
8
+ target_persona: str = Field(..., description="担当者ペルソナ(役職など)")
9
+ pain_points: List[str] = Field(..., description="相手の課題(箇条書きOK)")
10
+ value_prop: str = Field(..., description="自社の提供価値")
11
+ product_name: str = Field(..., description="製品/サービス名")
12
+ cta: str = Field("15分のオンライン面談をご提案", description="コールトゥアクション")
13
+ tone: str = Field("フォーマルで間血", description="希望トーン")
14
+ variables: Dict[str, str] = Field(default_factory=dict, description="差し込み変数")
15
+ length_hint: str = Field("中", description="短/中/長")
16
+ extra_instr: Optional[str] = Field(None, description="追加指示(禁止表現など)")
17
+
18
+ class EmailRequest(BaseModel):
19
+ subject: str
20
+ body: str
21
+ quality: Dict[str, float] # e.g., {"relevance": 0.92, "clarity": 0.01}
22
+ warnings: List[str] = []
23
+
24
+ class ProposalResponse(BaseModel):
25
+ outline: List[str]
26
+ executive_summary: str
27
+
28
+ class LintResponse(BaseModel):
29
+ text: str
30
+
31
+ class LintResponse(BaseModel):
32
+ issues: List[str]
33
+ toxicity: float
backend:requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.112.0
2
+ uvicorn[standard]==0.30.6
3
+ pydantic==2.8.2
4
+ python-dotenv==1.0.1
5
+ httpx==0.27.0
6
+ openai==1.50.0
frontend:index.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>営業メール/提案書ジェネレーター</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"> </div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>