Spaces:
Sleeping
Sleeping
| import os, math, time | |
| from typing import List, Dict, Any, Optional | |
| from openai import OpenAI | |
| def _norm(x): | |
| if x is None: return "" | |
| if isinstance(x, float) and math.isnan(x): return "" | |
| return str(x) | |
| def _backoff(attempt): | |
| # 0.5, 1, 2, 4 ... (上限 8s) | |
| return min(0.5 * (2 ** attempt), 8.0) | |
| class OpenAILLM: | |
| def __init__(self, model_chat: str = "gpt-4o-mini", model_translate: str = "gpt-4o-mini"): | |
| api_key = os.environ.get("OPENAI_API_KEY2") | |
| if not api_key: | |
| raise ValueError("環境変数 OPENAI_API_KEY2 が設定されていません。") | |
| self.client = OpenAI(api_key=api_key) | |
| self.model_chat = model_chat | |
| self.model_translate = model_translate | |
| self.last_usage = {"prompt_tokens":0, "completion_tokens":0, "total_tokens":0} | |
| def _update_usage(self, rsp): | |
| try: | |
| u = rsp.usage | |
| if u: | |
| self.last_usage = { | |
| "prompt_tokens": getattr(u, "prompt_tokens", 0), | |
| "completion_tokens": getattr(u, "completion_tokens", 0), | |
| "total_tokens": getattr(u, "total_tokens", 0), | |
| } | |
| except Exception: | |
| pass | |
| def _chat(self, model, messages, temperature=0.2, max_retries=3): | |
| for i in range(max_retries+1): | |
| try: | |
| rsp = self.client.chat.completions.create( | |
| model=model, messages=messages, temperature=temperature | |
| ) | |
| self._update_usage(rsp) | |
| return rsp.choices[0].message.content.strip() | |
| except Exception as e: | |
| if i == max_retries: | |
| raise | |
| time.sleep(_backoff(i)) | |
| def generate_ceo_message(self, meta, kpi: Dict[str, float], esg_rows: List[Dict[str, Any]]) -> str: | |
| prompt = ( | |
| "以下の企業情報・KPI・ESG指標をもとに、日本語で200字程度のCEOメッセージ草案を出力。" | |
| "事実ベース・簡潔・投資家向け。数値は丸め過ぎないこと。\n\n" | |
| f"企業情報: {meta.model_dump()}\nKPI: {kpi}\nESG: {esg_rows}\n" | |
| ) | |
| return self._chat(self.model_chat, [{"role":"user","content":prompt}], temperature=0.2) | |
| def generate_risk_opportunity(self, meta, kpi: Dict[str, float], esg_rows: List[Dict[str, Any]]) -> str: | |
| prompt = ( | |
| "以下に基づき主要なリスクと機会を150字程度で日本語要約。具体的観点を1-2点:\n\n" | |
| f"企業情報: {meta.model_dump()}\nKPI: {kpi}\nESG: {esg_rows}\n" | |
| ) | |
| return self._chat(self.model_chat, [{"role":"user","content":prompt}], temperature=0.2) | |
| def translate_texts(self, texts: List[Any], target_lang: str = "en", glossary: Optional[Dict[str,str]] = None) -> List[str]: | |
| norm = [_norm(t) for t in texts] | |
| SEP = "\n<<<SEP>>>\n" | |
| rules = "" | |
| if glossary: | |
| rules = "用語統一ルール(厳守):\n" + "\n".join([f"- {k} -> {v}" for k,v in glossary.items()]) | |
| system = ( | |
| "You are a precise financial/ESG translator. Preserve numbers and units. " | |
| "Follow the glossary strictly. Keep tone concise." | |
| ) | |
| prompt = f"Translate the following into {target_lang}. Each part is separated by <<<SEP>>>.\n{rules}\n\n" + SEP.join(norm) | |
| txt = self._chat(self.model_translate, [{"role":"system","content":system},{"role":"user","content":prompt}], temperature=0.1) | |
| parts = [p.strip() for p in txt.split("<<<SEP>>>")] | |
| if len(parts) != len(norm): | |
| parts = [txt] + norm[1:] | |
| parts = parts[:len(norm)] | |
| return parts | |