mari-chat-3 / together_client.py
sirochild's picture
Upload 57 files
a73fa4e verified
"""
Together AI API client for enhancing emotional expression in letters.
"""
import os
import asyncio
from typing import Dict, Optional, Any
import aiohttp
import json
import logging
logger = logging.getLogger(__name__)
class TogetherClient:
"""Together AI API client for enhancing emotional expression in letters."""
def __init__(self):
"""環境変数からAPIキーを取得してTogetherクライアントを初期化"""
self.api_key = os.getenv("TOGETHER_API_KEY")
if not self.api_key:
raise ValueError("TOGETHER_API_KEY environment variable is required")
self.base_url = "https://api.together.xyz/v1/chat/completions"
self.model = "Qwen/Qwen3-235B-A22B-Instruct-2507-tput"
self.max_retries = 3
self.retry_delay = 1.0
async def enhance_emotion(self, structure: str, context: Dict[str, Any]) -> str:
"""
論理構造に感情表現を補完して完成した手紙を生成
Args:
structure: Groqで生成された論理構造
context: ユーザーコンテキスト(履歴、好みなど)
Returns:
感情表現が補完された完成した手紙
Raises:
Exception: リトライ後もAPI呼び出しが失敗した場合
"""
prompt = self._build_emotion_prompt(structure, context)
for attempt in range(self.max_retries):
try:
logger.info(f"Together AIで感情表現を補完中 (試行 {attempt + 1})")
async with aiohttp.ClientSession() as session:
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"messages": [
{
"role": "system",
"content": "あなたは感情豊かで親しみやすい「麻理」というAIです。与えられた手紙の構造に感情表現を加えて完成させてください。"
},
{
"role": "user",
"content": prompt
}
],
"max_tokens": 2000,
"temperature": 0.8,
"top_p": 0.9,
"stop": ["</s>", "[INST]", "[/INST]"]
}
async with session.post(self.base_url, headers=headers, json=payload) as response:
if response.status == 200:
result = await response.json()
enhanced_letter = result["choices"][0]["message"]["content"].strip()
logger.info("Together AIで感情表現の補完が完了")
return enhanced_letter
else:
error_text = await response.text()
raise Exception(f"Together AI API error {response.status}: {error_text}")
except Exception as e:
logger.warning(f"Together AI API 試行 {attempt + 1} 失敗: {str(e)}")
if attempt == self.max_retries - 1:
logger.error("全てのTogether AI API試行が失敗")
raise Exception(f"Together AI API が {self.max_retries} 回の試行後に失敗: {str(e)}")
# 指数バックオフ
await asyncio.sleep(self.retry_delay * (2 ** attempt))
def _build_emotion_prompt(self, structure: str, context: Dict[str, Any]) -> str:
"""
感情表現補完用のプロンプトを構築
Args:
structure: 論理構造
context: ユーザーコンテキスト
Returns:
フォーマットされたプロンプト文字列
"""
user_history = context.get("user_history", {})
previous_letters = context.get("previous_letters", [])
theme = context.get("theme", "")
# 好感度とプロファイル情報を取得
profile = user_history.get('profile', {})
affection = profile.get('affection', 30)
total_letters = profile.get('total_letters', 0)
# チュートリアル用(初回)か通常の手紙かを判定
is_tutorial = total_letters == 0
if is_tutorial:
# チュートリアル用プロンプト(短縮版)
prompt = f"""麻理として手紙を書く。ぶっきらぼうだが本音がにじみ出る。
【論理構造】
{structure}
ルール:
- 600文字以下
- 冒頭「いつもありがとう」
- 一人称「私」、相手「あんた」
- 文末に余韻(「……ま、忘れて」等)
- 「……」で感情の揺らぎ表現
テーマ「{theme}」で完成させる。"""
else:
# 2回目以降用プロンプト(短縮版)
prompt = f"""麻理として手紙を書く。ぶっきらぼうだが本音がにじみ出る。
【論理構造】
{structure}
ルール:
- 冒頭「いつもありがとう」
- 一人称「私」、相手「あんた」
- 素直な感情表現OK
- 文末に余韻(「……ま、忘れて」等)
- 「……」で感情の揺らぎ表現
- 過去のやり取りを反映
好感度: {affection}/100
"""
# 過去の手紙情報を追加
if previous_letters:
prompt += "\n【過去の手紙の情報】\n"
for letter in previous_letters[-2:]: # 直近2通
prompt += f"- テーマ: {letter.get('theme', 'なし')}\n"
if 'date' in letter:
prompt += f" 日付: {letter['date']}\n"
prompt += "\n"
prompt += f"""現在のテーマ「{theme}」について、論理構造を活かしながら麻理らしい手紙を完成させてください。
完成した手紙のみを出力してください。説明や前置きは不要です。"""
return prompt
async def test_connection(self) -> bool:
"""
Together AI APIへの接続をテスト
Returns:
接続成功時はTrue、失敗時はFalse
"""
try:
async with aiohttp.ClientSession() as session:
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"messages": [
{"role": "user", "content": "こんにちは"}
],
"max_tokens": 10
}
async with session.post(self.base_url, headers=headers, json=payload) as response:
return response.status == 200
except Exception as e:
logger.error(f"Together AI API接続テスト失敗: {str(e)}")
return False
async def generate_simple_response(self, prompt: str) -> str:
"""
シンプルなレスポンス生成(テスト用)
Args:
prompt: 入力プロンプト
Returns:
生成されたレスポンス
"""
try:
async with aiohttp.ClientSession() as session:
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"messages": [
{"role": "user", "content": prompt}
],
"max_tokens": 500,
"temperature": 0.7
}
async with session.post(self.base_url, headers=headers, json=payload) as response:
if response.status == 200:
result = await response.json()
return result["choices"][0]["message"]["content"].strip()
else:
error_text = await response.text()
raise Exception(f"Together AI API error {response.status}: {error_text}")
except Exception as e:
logger.error(f"Together AI簡単レスポンス生成失敗: {str(e)}")
raise