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
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-04 17:38 +0900
1from __future__ import annotations
3"""ClearifyPipeline — Requirements clarification pipeline for OpenAI Agents SDK.
5ClearifyPipelineは要件を明確化するのに必要なAgentPipelineのサブクラスです。
6次のステップへ要件が満たされているかを確認されるまで、繰り返し質問を行い、
7ユーザーに確認を行います。
8"""
10from typing import Any, Dict, List, Optional, Type, TypeVar, Generic
11import json
12from dataclasses import dataclass
14from agents_sdk_models.pipeline import AgentPipeline
16try:
17 from pydantic import BaseModel # type: ignore
18except ImportError:
19 BaseModel = object # type: ignore
21# English: Generic type variable for user requirement type
22# 日本語: ユーザー要求型用のジェネリック型変数
23T = TypeVar('T')
25class ClearifyBase(BaseModel):
26 """
27 Base class for requirement clarification output
28 要件明確化出力のベースクラス
30 Attributes:
31 clearity: True if requirements are confirmed / 要件が確定した場合True
32 """
33 clearity: bool # True if requirements are confirmed / 要件が確定した場合True
35class ClearifyGeneric(ClearifyBase, Generic[T]):
36 """
37 Generic clarification output with typed user requirement
38 型付きユーザー要求を持つジェネリック明確化出力
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 / 確定したユーザー要求
46class Clearify(ClearifyBase):
47 """
48 Default clarification output with string user requirement
49 文字列ユーザー要求を持つデフォルト明確化出力
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 / 文字列として確定したユーザー要求
58@dataclass
59class ClarificationQuestion:
60 """
61 Represents a clarification question from the pipeline
62 パイプラインからの明確化質問を表現するクラス
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 / 残りターン数
73 def __str__(self) -> str:
74 """
75 String representation of the clarification question
76 明確化質問の文字列表現
78 Returns:
79 str: Formatted question with turn info / ターン情報付きフォーマット済み質問
80 """
81 return f"[ターン {self.turn}/{self.turn + self.remaining_turns}] {self.question}"
84class ClearifyPipeline(AgentPipeline):
85 """
86 ClearifyPipeline class for requirements clarification using OpenAI Agents SDK
87 OpenAI Agents SDKを使用した要件明確化パイプラインクラス
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 """
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を初期化する
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 """
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
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
136 # English: Enhanced generation instructions for clarification
137 # 日本語: 明確化用の拡張生成指示
138 enhanced_instructions = self._build_clarification_instructions(
139 generation_instructions,
140 output_data
141 )
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 )
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 指定された型用のラップされた出力モデルを作成する
158 Args:
159 output_data_type: Original output data type / 元の出力データ型
161 Returns:
162 Type[BaseModel]: Wrapped model type / ラップされたモデル型
163 """
164 # English: Create dynamic Pydantic model that wraps the original type
165 # 日本語: 元の型をラップする動的Pydanticモデルを作成
167 class WrappedClearify(BaseModel):
168 clearity: bool # True if requirements are confirmed / 要件が確定した場合True
169 user_requirement: Optional[output_data_type] = None # Confirmed user requirement / 確定したユーザー要求
171 return WrappedClearify
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 明確化プロセス用の拡張指示を構築する
182 Args:
183 base_instructions: Base generation instructions / ベース生成指示
184 output_data_type: Output data type for schema reference / スキーマ参照用出力データ型
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
203 enhanced_instructions = f"""{base_instructions}
205あなたは要件明確化の専門家です。以下の手順に従ってください:
2071. ユーザーの要求を理解し、不明確な点や不足している情報を特定する
2082. より良い結果のために必要な追加情報を質問する
2093. 要件が十分に明確になった場合は、clearityをtrueに設定する
2104. 要件が不十分な場合は、clearityをfalseに設定し、追加の質問を行う
212出力形式:
213- clearity: 要件が明確で完全な場合はtrue、追加情報が必要な場合はfalse
214- user_requirement: clearityがtrueの場合のみ、確定した要件を設定
216{schema_info}
218最大{self.clerify_max_turns}回のやり取りで要件を明確化してください。
219"""
221 return enhanced_instructions
223 def run(self, user_input: str) -> Any:
224 """
225 Run the clarification pipeline with user input
226 ユーザー入力で明確化パイプラインを実行する
228 Args:
229 user_input: User input text / ユーザー入力テキスト
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)
239 def continue_clarification(self, user_response: str) -> Any:
240 """
241 Continue clarification with user response to previous question
242 前の質問に対するユーザー回答で明確化を継続する
244 Args:
245 user_response: User response to clarification question / 明確化質問に対するユーザー回答
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
255 return self._process_input(user_response)
257 def _process_input(self, user_input: str) -> Any:
258 """
259 Process user input and return result or next question
260 ユーザー入力を処理し、結果または次の質問を返す
262 Args:
263 user_input: User input text / ユーザー入力テキスト
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
278 # English: Run parent pipeline
279 # 日本語: 親パイプラインを実行
280 result = super().run(full_input)
282 if result is None:
283 # English: Pipeline failed
284 # 日本語: パイプライン失敗
285 return None
287 self._turn_count += 1
289 # English: Store this interaction in history
290 # 日本語: この対話を履歴に保存
291 self._store_interaction(user_input, result)
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
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 )
314 # English: No clear question, return the raw result
315 # 日本語: 明確な質問がない、生の結果を返す
316 return result
318 def _build_conversation_context(self) -> str:
319 """
320 Build conversation context from history
321 履歴から会話コンテキストを構築する
323 Returns:
324 str: Formatted conversation context / フォーマット済み会話コンテキスト
325 """
326 if not hasattr(self, '_conversation_history'):
327 return ""
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']}")
334 return "\n".join(context_lines)
336 def _store_interaction(self, user_input: str, ai_result: Any) -> None:
337 """
338 Store interaction in conversation history
339 会話履歴に対話を保存する
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 = []
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)
355 self._conversation_history.append({
356 'user_input': user_input,
357 'ai_response': ai_response,
358 'turn': self._turn_count
359 })
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:]
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 = []
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 = []
385 @property
386 def is_complete(self) -> bool:
387 """
388 Check if clarification process is complete (max turns reached)
389 明確化プロセスが完了しているかチェック(最大ターン数に達した)
391 Returns:
392 bool: True if max turns reached / 最大ターン数に達した場合True
393 """
394 return self._turn_count >= self.clerify_max_turns
396 @property
397 def conversation_history(self) -> List[Dict[str, Any]]:
398 """
399 Get the conversation history
400 会話履歴を取得する
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()
409 @property
410 def current_turn(self) -> int:
411 """
412 Get the current turn number
413 現在のターン番号を取得する
415 Returns:
416 int: Current turn number / 現在のターン番号
417 """
418 return self._turn_count
420 @property
421 def remaining_turns(self) -> int:
422 """
423 Get the remaining number of turns
424 残りのターン数を取得する
426 Returns:
427 int: Remaining turns / 残りターン数
428 """
429 return max(0, self.clerify_max_turns - self._turn_count)