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

117 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-06-04 17:38 +0900

1from __future__ import annotations 

2 

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

4 

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

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

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

8""" 

9 

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

11import json 

12from dataclasses import dataclass 

13 

14from agents_sdk_models.pipeline import AgentPipeline 

15 

16try: 

17 from pydantic import BaseModel # type: ignore 

18except ImportError: 

19 BaseModel = object # type: ignore 

20 

21# English: Generic type variable for user requirement type 

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

23T = TypeVar('T') 

24 

25class 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 

35class 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 

46class 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 

59class 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 

84class 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 

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

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

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

2104. 要件が不十分な場合は、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)