Coverage for src\agents_sdk_models\clearify_pipeline.py: 90%

1 from __future__ import annotations

2

3"""ClearifyPipeline — Requirements clarification pipeline for OpenAI Agents SDK.

4

5 ClearifyPipelineは要件を明確化するのに必要なAgentPipelineのサブクラスです。

6次のステップへ要件が満たされているかを確認されるまで、繰り返し質問を行い、

7ユーザーに確認を行います。

8"""

9

10 from typing import Any, Dict, List, Optional, Type, TypeVar, Generic

11 import json

12 from dataclasses import dataclass

13

14 from agents_sdk_models.pipeline import AgentPipeline

15

16 try:

17 from pydantic import BaseModel # type: ignore

18 except ImportError:

19 BaseModel = object # type: ignore

20

21# English: Generic type variable for user requirement type

22# 日本語: ユーザー要求型用のジェネリック型変数

23 T = TypeVar('T')

24

25 class ClearifyBase(BaseModel):

26 """

27 Base class for requirement clarification output

28 要件明確化出力のベースクラス

29

30 Attributes:

31 clearity: True if requirements are confirmed / 要件が確定した場合True

32 """

33 clearity: bool # True if requirements are confirmed / 要件が確定した場合True

34

35 class ClearifyGeneric(ClearifyBase, Generic[T]):

36 """

37 Generic clarification output with typed user requirement

38 型付きユーザー要求を持つジェネリック明確化出力

39

40 Attributes:

41 clearity: True if requirements are confirmed / 要件が確定した場合True

42 user_requirement: Confirmed user requirement / 確定したユーザー要求

43 """

44 user_requirement: Optional[T] = None # Confirmed user requirement / 確定したユーザー要求

45

46 class Clearify(ClearifyBase):

47 """

48 Default clarification output with string user requirement

49 文字列ユーザー要求を持つデフォルト明確化出力

50

51 Attributes:

52 clearity: True if requirements are confirmed / 要件が確定した場合True

53 user_requirement: Confirmed user requirement as string / 文字列として確定したユーザー要求

54 """

55 user_requirement: Optional[str] = None # Confirmed user requirement as string / 文字列として確定したユーザー要求

56

57

58@dataclass

59 class ClarificationQuestion:

60 """

61 Represents a clarification question from the pipeline

62 パイプラインからの明確化質問を表現するクラス

63

64 Attributes:

65 question: The clarification question text / 明確化質問テキスト

66 turn: Current turn number / 現在のターン番号

67 remaining_turns: Remaining turns / 残りターン数

68 """

69 question: str # The clarification question text / 明確化質問テキスト

70 turn: int # Current turn number / 現在のターン番号

71 remaining_turns: int # Remaining turns / 残りターン数

72

73 def __str__(self) -> str:

74 """

75 String representation of the clarification question

76 明確化質問の文字列表現

77

78 Returns:

79 str: Formatted question with turn info / ターン情報付きフォーマット済み質問

80 """

81 return f"[ターン {self.turn}/{self.turn + self.remaining_turns}] {self.question}"

82

83

84 class ClearifyPipeline(AgentPipeline):

85 """

86 ClearifyPipeline class for requirements clarification using OpenAI Agents SDK

87 OpenAI Agents SDKを使用した要件明確化パイプラインクラス

88

89 This class extends AgentPipeline to handle:

90 このクラスはAgentPipelineを拡張して以下を処理します：

91 - Iterative requirement clarification / 反復的な要件明確化

92 - Type-safe output wrapping / 型安全な出力ラッピング

93 - Maximum turn control / 最大ターン数制御

94 - Structured requirement extraction / 構造化された要求抽出

95 """

96

97 def __init__(

98 self,

99 name: str,

100 generation_instructions: str,

101 output_data: Optional[Type[Any]] = None,

102 clerify_max_turns: int = 20,

103 evaluation_instructions: Optional[str] = None,

104 **kwargs

105 ) -> None:

106 """

107 Initialize the ClearifyPipeline with configuration parameters

108 設定パラメータでClearifyPipelineを初期化する

109

110 Args:

111 name: Pipeline name / パイプライン名

112 generation_instructions: System prompt for generation / 生成用システムプロンプト

113 output_data: Output data model type / 出力データモデル型

114 clerify_max_turns: Maximum number of clarification turns / 最大明確化ターン数

115 evaluation_instructions: System prompt for evaluation / 評価用システムプロンプト

116 **kwargs: Additional arguments for AgentPipeline / AgentPipeline用追加引数

117 """

118

119 # English: Store original output data type before wrapping

120 # 日本語: ラッピング前の元の出力データ型を保存

121 self.original_output_data = output_data

122 self.clerify_max_turns = clerify_max_turns

123 self._turn_count = 0

124

125 # English: Create wrapped output model based on provided type

126 # 日本語: 提供された型に基づいてラップされた出力モデルを作成

127 if output_data is not None:

128 # English: For typed output, create generic wrapper

129 # 日本語: 型付き出力の場合、ジェネリックラッパーを作成

130 wrapped_output_model = self._create_wrapped_model(output_data)

131 else:

132 # English: For untyped output, use default string wrapper

133 # 日本語: 型なし出力の場合、デフォルトの文字列ラッパーを使用

134 wrapped_output_model = Clearify

135

136 # English: Enhanced generation instructions for clarification

137 # 日本語: 明確化用の拡張生成指示

138 enhanced_instructions = self._build_clarification_instructions(

139 generation_instructions,

140 output_data

141 )

142

143 # English: Initialize parent with wrapped output model

144 # 日本語: ラップされた出力モデルで親クラスを初期化

145 super().__init__(

146 name=name,

147 generation_instructions=enhanced_instructions,

148 evaluation_instructions=evaluation_instructions,

149 output_model=wrapped_output_model,

150 **kwargs

151 )

152

153 def _create_wrapped_model(self, output_data_type: Type[Any]) -> Type[BaseModel]:

154 """

155 Create a wrapped output model for the given type

156 指定された型用のラップされた出力モデルを作成する

157

158 Args:

159 output_data_type: Original output data type / 元の出力データ型

160

161 Returns:

162 Type[BaseModel]: Wrapped model type / ラップされたモデル型

163 """

164 # English: Create dynamic Pydantic model that wraps the original type

165 # 日本語: 元の型をラップする動的Pydanticモデルを作成

166

167 class WrappedClearify(BaseModel):

168 clearity: bool # True if requirements are confirmed / 要件が確定した場合True

169 user_requirement: Optional[output_data_type] = None # Confirmed user requirement / 確定したユーザー要求

170

171 return WrappedClearify

172

173 def _build_clarification_instructions(

174 self,

175 base_instructions: str,

176 output_data_type: Optional[Type[Any]]

177 ) -> str:

178 """

179 Build enhanced instructions for clarification process

180 明確化プロセス用の拡張指示を構築する

181

182 Args:

183 base_instructions: Base generation instructions / ベース生成指示

184 output_data_type: Output data type for schema reference / スキーマ参照用出力データ型

185

186 Returns:

187 str: Enhanced instructions / 拡張指示

188 """

189 schema_info = ""

190 if output_data_type is not None:

191 try:

192 # English: Try to get schema information if available

193 # 日本語: 利用可能な場合はスキーマ情報を取得を試行

194 if hasattr(output_data_type, 'model_json_schema'):

195 schema = output_data_type.model_json_schema()

196 schema_info = f"\n\n必要な出力形式のスキーマ:\n{json.dumps(schema, indent=2, ensure_ascii=False)}"

197 elif hasattr(output_data_type, '__annotations__'):

198 annotations = output_data_type.__annotations__

199 schema_info = f"\n\n必要なフィールド: {list(annotations.keys())}"

200 except Exception:

201 pass

202

203 enhanced_instructions = f"""{base_instructions}

204

205あなたは要件明確化の専門家です。以下の手順に従ってください：

206

207 1. ユーザーの要求を理解し、不明確な点や不足している情報を特定する

208 2. より良い結果のために必要な追加情報を質問する

209 3. 要件が十分に明確になった場合は、clearityをtrueに設定する

210 4. 要件が不十分な場合は、clearityをfalseに設定し、追加の質問を行う

211

212出力形式：

213- clearity: 要件が明確で完全な場合はtrue、追加情報が必要な場合はfalse

214- user_requirement: clearityがtrueの場合のみ、確定した要件を設定

215

216{schema_info}

217

218最大{self.clerify_max_turns}回のやり取りで要件を明確化してください。

219"""

220

221 return enhanced_instructions

222

223 def run(self, user_input: str) -> Any:

224 """

225 Run the clarification pipeline with user input

226 ユーザー入力で明確化パイプラインを実行する

227

228 Args:

229 user_input: User input text / ユーザー入力テキスト

230

231 Returns:

232 Any: Final clarified requirement, clarification question, or None if failed / 最終的な明確化された要求、明確化質問、または失敗時はNone

233 """

234 # English: Reset turn count for new session

235 # 日本語: 新しいセッション用にターンカウントをリセット

236 self._turn_count = 0

237 return self._process_input(user_input)

238

239 def continue_clarification(self, user_response: str) -> Any:

240 """

241 Continue clarification with user response to previous question

242 前の質問に対するユーザー回答で明確化を継続する

243

244 Args:

245 user_response: User response to clarification question / 明確化質問に対するユーザー回答

246

247 Returns:

248 Any: Final clarified requirement, next clarification question, or None if failed / 最終的な明確化された要求、次の明確化質問、または失敗時はNone

249 """

250 if self._turn_count >= self.clerify_max_turns:

251 # English: Maximum turns reached, cannot continue

252 # 日本語: 最大ターン数に達した、継続できない

253 return None

254

255 return self._process_input(user_response)

256

257 def _process_input(self, user_input: str) -> Any:

258 """

259 Process user input and return result or next question

260 ユーザー入力を処理し、結果または次の質問を返す

261

262 Args:

263 user_input: User input text / ユーザー入力テキスト

264

265 Returns:

266 Any: Final clarified requirement, clarification question, or None if failed / 最終的な明確化された要求、明確化質問、または失敗時はNone

267 """

268 # English: Build context with conversation history

269 # 日本語: 会話履歴を含むコンテキストを構築

270 if self._turn_count > 0:

271 # English: For subsequent turns, include conversation history

272 # 日本語: 2回目以降のターンでは会話履歴を含める

273 conversation_context = self._build_conversation_context()

274 full_input = f"{conversation_context}\n\nユーザー: {user_input}"

275 else:

276 full_input = user_input

277

278 # English: Run parent pipeline

279 # 日本語: 親パイプラインを実行

280 result = super().run(full_input)

281

282 if result is None:

283 # English: Pipeline failed

284 # 日本語: パイプライン失敗

285 return None

286

287 self._turn_count += 1

288

289 # English: Store this interaction in history

290 # 日本語: この対話を履歴に保存

291 self._store_interaction(user_input, result)

292

293 # English: Check if clarification is complete

294 # 日本語: 明確化が完了しているかチェック

295 if hasattr(result, 'clearity') and result.clearity:

296 # English: Requirements are clear, extract and return final result

297 # 日本語: 要件が明確、最終結果を抽出して返す

298 if hasattr(result, 'user_requirement') and result.user_requirement:

299 return result.user_requirement

300 else:

301 return result

302

303 # English: Requirements not clear yet, return the clarification question

304 # 日本語: 要件がまだ明確でない、明確化質問を返す

305 if hasattr(result, 'user_requirement') and result.user_requirement:

306 # English: Return the clarification question/request for more info

307 # 日本語: 明確化質問/追加情報の要求を返す

308 return ClarificationQuestion(

309 question=str(result.user_requirement),

310 turn=self._turn_count,

311 remaining_turns=self.remaining_turns

312 )

313

314 # English: No clear question, return the raw result

315 # 日本語: 明確な質問がない、生の結果を返す

316 return result

317

318 def _build_conversation_context(self) -> str:

319 """

320 Build conversation context from history

321 履歴から会話コンテキストを構築する

322

323 Returns:

324 str: Formatted conversation context / フォーマット済み会話コンテキスト

325 """

326 if not hasattr(self, '_conversation_history'):

327 return ""

328

329 context_lines = []

330 for interaction in self._conversation_history:

331 context_lines.append(f"ユーザー: {interaction['user_input']}")

332 context_lines.append(f"AI: {interaction['ai_response']}")

333

334 return "\n".join(context_lines)

335

336 def _store_interaction(self, user_input: str, ai_result: Any) -> None:

337 """

338 Store interaction in conversation history

339 会話履歴に対話を保存する

340

341 Args:

342 user_input: User input / ユーザー入力

343 ai_result: AI result / AI結果

344 """

345 if not hasattr(self, '_conversation_history'):

346 self._conversation_history = []

347

348 # English: Extract AI response text

349 # 日本語: AI応答テキストを抽出

350 if hasattr(ai_result, 'user_requirement') and ai_result.user_requirement:

351 ai_response = str(ai_result.user_requirement)

352 else:

353 ai_response = str(ai_result)

354

355 self._conversation_history.append({

356 'user_input': user_input,

357 'ai_response': ai_response,

358 'turn': self._turn_count

359 })

360

361 # English: Keep only recent history to avoid context overflow

362 # 日本語: コンテキストオーバーフローを避けるため最近の履歴のみ保持

363 max_history = 10

364 if len(self._conversation_history) > max_history:

365 self._conversation_history = self._conversation_history[-max_history:]

366

367 def reset_turns(self) -> None:

368 """

369 Reset the turn counter for a new clarification session

370 新しい明確化セッション用にターンカウンターをリセットする

371 """

372 self._turn_count = 0

373 if hasattr(self, '_conversation_history'):

374 self._conversation_history = []

375

376 def reset_session(self) -> None:

377 """

378 Reset the entire session including conversation history

379 会話履歴を含むセッション全体をリセットする

380 """

381 self.reset_turns()

382 if hasattr(self, '_conversation_history'):

383 self._conversation_history = []

384

385 @property

386 def is_complete(self) -> bool:

387 """

388 Check if clarification process is complete (max turns reached)

389 明確化プロセスが完了しているかチェック（最大ターン数に達した）

390

391 Returns:

392 bool: True if max turns reached / 最大ターン数に達した場合True

393 """

394 return self._turn_count >= self.clerify_max_turns

395

396 @property

397 def conversation_history(self) -> List[Dict[str, Any]]:

398 """

399 Get the conversation history

400 会話履歴を取得する

401

402 Returns:

403 List[Dict[str, Any]]: Conversation history / 会話履歴

404 """

405 if not hasattr(self, '_conversation_history'):

406 return []

407 return self._conversation_history.copy()

408

409 @property

410 def current_turn(self) -> int:

411 """

412 Get the current turn number

413 現在のターン番号を取得する

414

415 Returns:

416 int: Current turn number / 現在のターン番号

417 """

418 return self._turn_count

419

420 @property

421 def remaining_turns(self) -> int:

422 """

423 Get the remaining number of turns

424 残りのターン数を取得する

425

426 Returns:

427 int: Remaining turns / 残りターン数

428 """

429 return max(0, self.clerify_max_turns - self._turn_count)