Coverage for src/refinire/agents/pipeline/llm_pipeline.py: 76%

379 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-15 18:51 +0900

1"""LLM Pipeline - A replacement for deprecated AgentPipeline using OpenAI Python SDK directly. 

2 

3LLMパイプライン - 非推奨のAgentPipelineに代わって、OpenAI Python SDKを直接使用する新しい実装。 

4""" 

5 

6from __future__ import annotations 

7 

8import asyncio 

9import json 

10from typing import Any, Callable, Dict, List, Optional, Type, Union 

11from dataclasses import dataclass, field 

12from concurrent.futures import ThreadPoolExecutor 

13import warnings 

14 

15try: 

16 from openai import OpenAI, AsyncOpenAI 

17 from pydantic import BaseModel 

18except ImportError as e: 

19 raise ImportError(f"Required dependencies not found: {e}. Please install openai and pydantic.") 

20 

21try: 

22 from ...core.prompt_store import PromptReference 

23except ImportError: 

24 PromptReference = None 

25 

26 

27@dataclass 

28class LLMResult: 

29 """ 

30 Result from LLM generation 

31 LLM生成結果 

32  

33 Attributes: 

34 content: Generated content / 生成されたコンテンツ 

35 success: Whether generation was successful / 生成が成功したか 

36 metadata: Additional metadata / 追加メタデータ 

37 evaluation_score: Evaluation score if evaluated / 評価されている場合の評価スコア 

38 attempts: Number of attempts made / 実行された試行回数 

39 """ 

40 content: Any 

41 success: bool = True 

42 metadata: Dict[str, Any] = field(default_factory=dict) 

43 evaluation_score: Optional[float] = None 

44 attempts: int = 1 

45 

46 

47@dataclass 

48class EvaluationResult: 

49 """ 

50 Result from evaluation process 

51 評価プロセスの結果 

52  

53 Attributes: 

54 score: Evaluation score (0-100) / 評価スコア(0-100) 

55 passed: Whether evaluation passed threshold / 閾値を超えたか 

56 feedback: Evaluation feedback / 評価フィードバック 

57 metadata: Additional metadata / 追加メタデータ 

58 """ 

59 score: float 

60 passed: bool 

61 feedback: Optional[str] = None 

62 metadata: Dict[str, Any] = field(default_factory=dict) 

63 

64 

65class LLMPipeline: 

66 """ 

67 Modern LLM Pipeline using OpenAI Python SDK directly 

68 OpenAI Python SDKを直接使用するモダンなLLMパイプライン 

69  

70 This class replaces the deprecated AgentPipeline with a cleaner, more maintainable implementation. 

71 このクラスは非推奨のAgentPipelineを、よりクリーンで保守しやすい実装で置き換えます。 

72 """ 

73 

74 def __init__( 

75 self, 

76 name: str, 

77 generation_instructions: str, 

78 evaluation_instructions: Optional[str] = None, 

79 *, 

80 model: str = "gpt-4o-mini", 

81 evaluation_model: Optional[str] = None, 

82 output_model: Optional[Type[BaseModel]] = None, 

83 temperature: float = 0.7, 

84 max_tokens: Optional[int] = None, 

85 timeout: float = 30.0, 

86 threshold: float = 85.0, 

87 max_retries: int = 3, 

88 input_guardrails: Optional[List[Callable[[str], bool]]] = None, 

89 output_guardrails: Optional[List[Callable[[Any], bool]]] = None, 

90 session_history: Optional[List[str]] = None, 

91 history_size: int = 10, 

92 improvement_callback: Optional[Callable[[LLMResult, EvaluationResult], str]] = None, 

93 locale: str = "en", 

94 tools: Optional[List[Dict]] = None, 

95 mcp_servers: Optional[List[str]] = None 

96 ) -> None: 

97 """ 

98 Initialize LLM Pipeline 

99 LLMパイプラインを初期化する 

100  

101 Args: 

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

103 generation_instructions: Instructions for generation / 生成用指示 

104 evaluation_instructions: Instructions for evaluation / 評価用指示 

105 model: OpenAI model name / OpenAIモデル名 

106 evaluation_model: Model for evaluation / 評価用モデル 

107 output_model: Pydantic model for structured output / 構造化出力用Pydanticモデル 

108 temperature: Sampling temperature / サンプリング温度 

109 max_tokens: Maximum tokens / 最大トークン数 

110 timeout: Request timeout / リクエストタイムアウト 

111 threshold: Evaluation threshold / 評価閾値 

112 max_retries: Maximum retry attempts / 最大リトライ回数 

113 input_guardrails: Input validation functions / 入力検証関数 

114 output_guardrails: Output validation functions / 出力検証関数 

115 session_history: Session history / セッション履歴 

116 history_size: History size limit / 履歴サイズ制限 

117 improvement_callback: Callback for improvement suggestions / 改善提案コールバック 

118 locale: Locale for messages / メッセージ用ロケール 

119 tools: OpenAI function tools / OpenAI関数ツール 

120 mcp_servers: MCP server identifiers / MCPサーバー識別子 

121 """ 

122 # Basic configuration 

123 self.name = name 

124 

125 # Handle PromptReference for generation instructions 

126 self._generation_prompt_metadata = None 

127 if PromptReference and isinstance(generation_instructions, PromptReference): 

128 self._generation_prompt_metadata = generation_instructions.get_metadata() 

129 self.generation_instructions = str(generation_instructions) 

130 else: 

131 self.generation_instructions = generation_instructions 

132 

133 # Handle PromptReference for evaluation instructions 

134 self._evaluation_prompt_metadata = None 

135 if PromptReference and isinstance(evaluation_instructions, PromptReference): 

136 self._evaluation_prompt_metadata = evaluation_instructions.get_metadata() 

137 self.evaluation_instructions = str(evaluation_instructions) 

138 else: 

139 self.evaluation_instructions = evaluation_instructions 

140 

141 self.model = model 

142 self.evaluation_model = evaluation_model or model 

143 self.output_model = output_model 

144 self.temperature = temperature 

145 self.max_tokens = max_tokens 

146 self.timeout = timeout 

147 self.threshold = threshold 

148 self.max_retries = max_retries 

149 self.locale = locale 

150 

151 # Guardrails 

152 self.input_guardrails = input_guardrails or [] 

153 self.output_guardrails = output_guardrails or [] 

154 

155 # History management 

156 self.session_history = session_history or [] 

157 self.history_size = history_size 

158 self._pipeline_history: List[Dict[str, Any]] = [] 

159 

160 # Callbacks 

161 self.improvement_callback = improvement_callback 

162 

163 # Tools and MCP support 

164 self.tools = tools or [] 

165 self.mcp_servers = mcp_servers or [] 

166 

167 # Initialize OpenAI clients 

168 self.sync_client = OpenAI() 

169 self.async_client = AsyncOpenAI() 

170 

171 def run(self, user_input: str) -> LLMResult: 

172 """ 

173 Run the pipeline synchronously 

174 パイプラインを同期的に実行する 

175  

176 Args: 

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

178  

179 Returns: 

180 LLMResult: Generation result / 生成結果 

181 """ 

182 # Input validation 

183 if not self._validate_input(user_input): 

184 return LLMResult( 

185 content=None, 

186 success=False, 

187 metadata={"error": "Input validation failed", "input": user_input} 

188 ) 

189 

190 # Build prompt with history 

191 full_prompt = self._build_prompt(user_input) 

192 

193 # Generation with retries 

194 for attempt in range(1, self.max_retries + 1): 

195 try: 

196 # Generate content 

197 generation_result = self._generate_content(full_prompt) 

198 

199 # Output validation 

200 if not self._validate_output(generation_result): 

201 if attempt < self.max_retries: 

202 continue 

203 return LLMResult( 

204 content=None, 

205 success=False, 

206 metadata={"error": "Output validation failed", "attempts": attempt} 

207 ) 

208 

209 # Parse structured output if model specified 

210 parsed_content = self._parse_structured_output(generation_result) 

211 

212 # Evaluate if evaluation instructions provided 

213 evaluation_result = None 

214 if self.evaluation_instructions: 

215 evaluation_result = self._evaluate_content(user_input, parsed_content) 

216 

217 # Check if evaluation passed 

218 if not evaluation_result.passed and attempt < self.max_retries: 

219 # Generate improvement if callback provided 

220 if self.improvement_callback: 

221 improvement = self.improvement_callback( 

222 LLMResult(content=parsed_content, success=True), 

223 evaluation_result 

224 ) 

225 full_prompt = f"{full_prompt}\n\nImprovement needed: {improvement}" 

226 continue 

227 

228 # Success - store in history and return 

229 metadata = { 

230 "model": self.model, 

231 "temperature": self.temperature, 

232 "attempts": attempt 

233 } 

234 

235 # Add prompt metadata if available 

236 if self._generation_prompt_metadata: 

237 metadata.update(self._generation_prompt_metadata) 

238 

239 if self._evaluation_prompt_metadata: 

240 metadata["evaluation_prompt"] = self._evaluation_prompt_metadata 

241 

242 result = LLMResult( 

243 content=parsed_content, 

244 success=True, 

245 metadata=metadata, 

246 evaluation_score=evaluation_result.score if evaluation_result else None, 

247 attempts=attempt 

248 ) 

249 

250 self._store_in_history(user_input, result) 

251 return result 

252 

253 except Exception as e: 

254 if attempt == self.max_retries: 

255 return LLMResult( 

256 content=None, 

257 success=False, 

258 metadata={"error": str(e), "attempts": attempt} 

259 ) 

260 continue 

261 

262 # Should not reach here 

263 return LLMResult( 

264 content=None, 

265 success=False, 

266 metadata={"error": "Maximum retries exceeded"} 

267 ) 

268 

269 async def run_async(self, user_input: str) -> LLMResult: 

270 """ 

271 Run the pipeline asynchronously 

272 パイプラインを非同期的に実行する 

273  

274 Args: 

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

276  

277 Returns: 

278 LLMResult: Generation result / 生成結果 

279 """ 

280 # For simplicity, run sync version in executor 

281 # In production, this would be fully async 

282 loop = asyncio.get_event_loop() 

283 with ThreadPoolExecutor() as executor: 

284 return await loop.run_in_executor(executor, self.run, user_input) 

285 

286 def _validate_input(self, user_input: str) -> bool: 

287 """Validate input using guardrails / ガードレールを使用して入力を検証""" 

288 for guardrail in self.input_guardrails: 

289 if not guardrail(user_input): 

290 return False 

291 return True 

292 

293 def _validate_output(self, output: str) -> bool: 

294 """Validate output using guardrails / ガードレールを使用して出力を検証""" 

295 for guardrail in self.output_guardrails: 

296 if not guardrail(output): 

297 return False 

298 return True 

299 

300 def _build_prompt(self, user_input: str) -> str: 

301 """Build complete prompt with instructions and history / 指示と履歴を含む完全なプロンプトを構築""" 

302 prompt_parts = [self.generation_instructions] 

303 

304 # Add history if available 

305 if self.session_history: 

306 history_text = "\n".join(self.session_history[-self.history_size:]) 

307 prompt_parts.append(f"Previous context:\n{history_text}") 

308 

309 prompt_parts.append(f"User input: {user_input}") 

310 

311 return "\n\n".join(prompt_parts) 

312 

313 def _generate_content(self, prompt: str) -> str: 

314 """Generate content using OpenAI API with full tool support / OpenAI APIを使用した完全なtoolサポート付きコンテンツ生成""" 

315 messages = [{"role": "user", "content": prompt}] 

316 

317 # Tools/function calling loop 

318 # Tools/function calling ループ 

319 max_tool_iterations = 10 # Prevent infinite loops / 無限ループを防ぐ 

320 iteration = 0 

321 

322 while iteration < max_tool_iterations: 

323 # Prepare API call parameters 

324 # API呼び出しパラメータを準備 

325 params = { 

326 "model": self.model, 

327 "messages": messages, 

328 "temperature": self.temperature, 

329 "timeout": self.timeout 

330 } 

331 

332 if self.max_tokens: 

333 params["max_tokens"] = self.max_tokens 

334 

335 # Add tools if available 

336 # toolsが利用可能な場合は追加 

337 if self.tools: 

338 params["tools"] = self.tools 

339 params["tool_choice"] = "auto" 

340 

341 # Add structured output if model specified 

342 # 構造化出力が指定されている場合は追加 

343 if self.output_model: 

344 params["response_format"] = {"type": "json_object"} 

345 

346 response = self.sync_client.chat.completions.create(**params) 

347 message = response.choices[0].message 

348 

349 # Add assistant message to conversation 

350 # アシスタントメッセージを対話に追加 

351 messages.append({ 

352 "role": "assistant", 

353 "content": message.content, 

354 "tool_calls": [ 

355 { 

356 "id": tc.id, 

357 "type": tc.type, 

358 "function": { 

359 "name": tc.function.name, 

360 "arguments": tc.function.arguments 

361 } 

362 } 

363 for tc in (message.tool_calls or []) 

364 ] 

365 }) 

366 

367 # If no tool calls, we're done 

368 # tool呼び出しがない場合は完了 

369 if not message.tool_calls: 

370 return message.content or "" 

371 

372 # Execute tool calls 

373 # tool呼び出しを実行 

374 for tool_call in message.tool_calls: 

375 try: 

376 # Execute the tool function 

377 # tool関数を実行 

378 tool_result = self._execute_tool(tool_call) 

379 

380 # Add tool result to conversation 

381 # tool結果を対話に追加 

382 messages.append({ 

383 "role": "tool", 

384 "tool_call_id": tool_call.id, 

385 "content": str(tool_result) 

386 }) 

387 

388 except Exception as e: 

389 # Handle tool execution errors 

390 # tool実行エラーを処理 

391 messages.append({ 

392 "role": "tool", 

393 "tool_call_id": tool_call.id, 

394 "content": f"Error executing tool: {str(e)}" 

395 }) 

396 

397 iteration += 1 

398 

399 # If we reach here, we hit the iteration limit 

400 # ここに到達した場合は反復制限に達した 

401 return "Maximum tool iteration limit reached. Please try a simpler request." 

402 

403 def _execute_tool(self, tool_call) -> Any: 

404 """Execute a tool function call / tool関数呼び出しを実行""" 

405 function_name = tool_call.function.name 

406 arguments = json.loads(tool_call.function.arguments) 

407 

408 # Find the tool by name 

409 # 名前でtoolを検索 

410 for tool in self.tools: 

411 if tool.get("function", {}).get("name") == function_name: 

412 # Check if tool has a callable function 

413 # toolに呼び出し可能な関数があるかチェック 

414 if "callable" in tool: 

415 return tool["callable"](**arguments) 

416 elif "handler" in tool: 

417 return tool["handler"](**arguments) 

418 else: 

419 # Try to find a Python function with the same name 

420 # 同じ名前のPython関数を検索 

421 import inspect 

422 frame = inspect.currentframe() 

423 while frame: 

424 if function_name in frame.f_globals: 

425 func = frame.f_globals[function_name] 

426 if callable(func): 

427 return func(**arguments) 

428 frame = frame.f_back 

429 

430 raise ValueError(f"No callable implementation found for tool: {function_name}") 

431 

432 raise ValueError(f"Tool not found: {function_name}") 

433 

434 def add_tool(self, tool_definition: Dict, handler: Optional[callable] = None) -> None: 

435 """Add a tool to the pipeline / パイプラインにtoolを追加""" 

436 if handler: 

437 tool_definition["callable"] = handler 

438 self.tools.append(tool_definition) 

439 

440 def add_function_tool( 

441 self, 

442 func: callable, 

443 name: Optional[str] = None, 

444 description: Optional[str] = None 

445 ) -> None: 

446 """Add a Python function as a tool / Python関数をtoolとして追加""" 

447 import inspect 

448 

449 function_name = name or func.__name__ 

450 function_description = description or func.__doc__ or f"Function: {function_name}" 

451 

452 # Get function signature for parameters 

453 # パラメータ用の関数シグネチャを取得 

454 sig = inspect.signature(func) 

455 parameters = {} 

456 required = [] 

457 

458 for param_name, param in sig.parameters.items(): 

459 param_info = {"type": "string"} # Default type 

460 

461 # Try to infer type from annotation 

462 # アノテーションから型を推論 

463 if param.annotation != inspect.Parameter.empty: 

464 if param.annotation == int: 

465 param_info["type"] = "integer" 

466 elif param.annotation == float: 

467 param_info["type"] = "number" 

468 elif param.annotation == bool: 

469 param_info["type"] = "boolean" 

470 elif param.annotation == list: 

471 param_info["type"] = "array" 

472 elif param.annotation == dict: 

473 param_info["type"] = "object" 

474 

475 parameters[param_name] = param_info 

476 

477 # Add to required if no default value 

478 # デフォルト値がない場合は必須に追加 

479 if param.default == inspect.Parameter.empty: 

480 required.append(param_name) 

481 

482 tool_definition = { 

483 "type": "function", 

484 "function": { 

485 "name": function_name, 

486 "description": function_description, 

487 "parameters": { 

488 "type": "object", 

489 "properties": parameters, 

490 "required": required 

491 } 

492 }, 

493 "callable": func 

494 } 

495 

496 self.tools.append(tool_definition) 

497 

498 def add_mcp_server(self, server_config: Dict) -> None: 

499 """Add MCP server configuration / MCPサーバー設定を追加""" 

500 # MCP server integration would be implemented here 

501 # MCPサーバー統合をここで実装 

502 self.mcp_servers.append(server_config) 

503 

504 # For now, log that MCP is configured but not fully implemented 

505 # 現在のところ、MCPが設定されているがまだ完全に実装されていないことをログ 

506 print(f"MCP server configured: {server_config.get('name', 'unnamed')}") 

507 print("Note: Full MCP integration is planned for future release") 

508 

509 def remove_tool(self, tool_name: str) -> bool: 

510 """Remove a tool by name / 名前でtoolを削除""" 

511 for i, tool in enumerate(self.tools): 

512 if tool.get("function", {}).get("name") == tool_name: 

513 del self.tools[i] 

514 return True 

515 return False 

516 

517 def list_tools(self) -> List[str]: 

518 """List all available tool names / 利用可能なtool名をリスト""" 

519 return [ 

520 tool.get("function", {}).get("name", "unnamed") 

521 for tool in self.tools 

522 ] 

523 

524 def _parse_structured_output(self, content: str) -> Any: 

525 """Parse structured output if model specified / モデルが指定されている場合は構造化出力を解析""" 

526 if not self.output_model: 

527 return content 

528 

529 try: 

530 # Parse JSON and validate with Pydantic model 

531 data = json.loads(content) 

532 return self.output_model.model_validate(data) 

533 except Exception: 

534 # Fallback to raw content if parsing fails 

535 return content 

536 

537 def _evaluate_content(self, user_input: str, generated_content: Any) -> EvaluationResult: 

538 """Evaluate generated content / 生成されたコンテンツを評価""" 

539 evaluation_prompt = f""" 

540{self.evaluation_instructions} 

541 

542User Input: {user_input} 

543Generated Content: {generated_content} 

544 

545Please provide a score from 0 to 100 and brief feedback. 

546Return your response as JSON with 'score' and 'feedback' fields. 

547""" 

548 

549 messages = [{"role": "user", "content": evaluation_prompt}] 

550 

551 try: 

552 response = self.sync_client.chat.completions.create( 

553 model=self.evaluation_model, 

554 messages=messages, 

555 temperature=0.3, # Lower temperature for evaluation 

556 response_format={"type": "json_object"}, 

557 timeout=self.timeout 

558 ) 

559 

560 eval_data = json.loads(response.choices[0].message.content) 

561 score = float(eval_data.get("score", 0)) 

562 feedback = eval_data.get("feedback", "") 

563 

564 return EvaluationResult( 

565 score=score, 

566 passed=score >= self.threshold, 

567 feedback=feedback, 

568 metadata={"model": self.evaluation_model} 

569 ) 

570 

571 except Exception as e: 

572 # Fallback evaluation 

573 return EvaluationResult( 

574 score=0.0, 

575 passed=False, 

576 feedback=f"Evaluation failed: {str(e)}", 

577 metadata={"error": str(e)} 

578 ) 

579 

580 def _store_in_history(self, user_input: str, result: LLMResult) -> None: 

581 """Store interaction in history / 対話を履歴に保存""" 

582 interaction = { 

583 "user_input": user_input, 

584 "result": result.content, 

585 "success": result.success, 

586 "metadata": result.metadata, 

587 "timestamp": json.dumps({"pipeline": self.name}, ensure_ascii=False) 

588 } 

589 

590 self._pipeline_history.append(interaction) 

591 

592 # Add to session history for context 

593 session_entry = f"User: {user_input}\nAssistant: {result.content}" 

594 self.session_history.append(session_entry) 

595 

596 # Trim history if needed 

597 if len(self.session_history) > self.history_size: 

598 self.session_history = self.session_history[-self.history_size:] 

599 

600 def clear_history(self) -> None: 

601 """Clear all history / 全履歴をクリア""" 

602 self._pipeline_history.clear() 

603 self.session_history.clear() 

604 

605 def get_history(self) -> List[Dict[str, Any]]: 

606 """Get pipeline history / パイプライン履歴を取得""" 

607 return self._pipeline_history.copy() 

608 

609 def update_instructions( 

610 self, 

611 generation_instructions: Optional[str] = None, 

612 evaluation_instructions: Optional[str] = None 

613 ) -> None: 

614 """Update instructions / 指示を更新""" 

615 if generation_instructions: 

616 self.generation_instructions = generation_instructions 

617 if evaluation_instructions: 

618 self.evaluation_instructions = evaluation_instructions 

619 

620 def set_threshold(self, threshold: float) -> None: 

621 """Set evaluation threshold / 評価閾値を設定""" 

622 if 0 <= threshold <= 100: 

623 self.threshold = threshold 

624 else: 

625 raise ValueError("Threshold must be between 0 and 100") 

626 

627 def __str__(self) -> str: 

628 return f"LLMPipeline(name={self.name}, model={self.model})" 

629 

630 def __repr__(self) -> str: 

631 return self.__str__() 

632 

633 

634# Utility functions for common configurations 

635# 共通設定用のユーティリティ関数 

636 

637def create_simple_llm_pipeline( 

638 name: str, 

639 instructions: str, 

640 model: str = "gpt-4o-mini", 

641 **kwargs 

642) -> LLMPipeline: 

643 """ 

644 Create a simple LLM pipeline 

645 シンプルなLLMパイプラインを作成 

646 """ 

647 return LLMPipeline( 

648 name=name, 

649 generation_instructions=instructions, 

650 model=model, 

651 **kwargs 

652 ) 

653 

654 

655def create_evaluated_llm_pipeline( 

656 name: str, 

657 generation_instructions: str, 

658 evaluation_instructions: str, 

659 model: str = "gpt-4o-mini", 

660 evaluation_model: Optional[str] = None, 

661 threshold: float = 85.0, 

662 **kwargs 

663) -> LLMPipeline: 

664 """ 

665 Create an LLM pipeline with evaluation 

666 評価機能付きLLMパイプラインを作成 

667 """ 

668 return LLMPipeline( 

669 name=name, 

670 generation_instructions=generation_instructions, 

671 evaluation_instructions=evaluation_instructions, 

672 model=model, 

673 evaluation_model=evaluation_model, 

674 threshold=threshold, 

675 **kwargs 

676 ) 

677 

678 

679def create_tool_enabled_llm_pipeline( 

680 name: str, 

681 instructions: str, 

682 tools: Optional[List[callable]] = None, 

683 model: str = "gpt-4o-mini", 

684 **kwargs 

685) -> LLMPipeline: 

686 """ 

687 Create an LLM pipeline with automatic tool registration 

688 自動tool登録機能付きLLMパイプラインを作成 

689  

690 Args: 

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

692 instructions: System instructions / システム指示 

693 tools: List of Python functions to register as tools / tool登録するPython関数のリスト 

694 model: LLM model name / LLMモデル名 

695 **kwargs: Additional arguments for LLMPipeline / LLMPipeline用追加引数 

696  

697 Returns: 

698 LLMPipeline: Configured pipeline with tools / tool設定済みパイプライン 

699  

700 Example: 

701 >>> def get_weather(city: str) -> str: 

702 ... '''Get weather for a city''' 

703 ... return f"Weather in {city}: Sunny" 

704 ... 

705 >>> def calculate(expression: str) -> float: 

706 ... '''Calculate mathematical expression''' 

707 ... return eval(expression) 

708 ... 

709 >>> pipeline = create_tool_enabled_llm_pipeline( 

710 ... name="assistant", 

711 ... instructions="You are a helpful assistant with access to tools.", 

712 ... tools=[get_weather, calculate] 

713 ... ) 

714 >>> result = pipeline.run("What's the weather in Tokyo and what's 2+2?") 

715 """ 

716 pipeline = LLMPipeline( 

717 name=name, 

718 generation_instructions=instructions, 

719 model=model, 

720 tools=[], # Start with empty tools list 

721 **kwargs 

722 ) 

723 

724 # Register all provided tools 

725 # 提供されたすべてのtoolを登録 

726 if tools: 

727 for tool_func in tools: 

728 pipeline.add_function_tool(tool_func) 

729 

730 return pipeline 

731 

732 

733def create_web_search_pipeline( 

734 name: str, 

735 instructions: str = "You are a helpful assistant with access to web search. Use web search when you need current information.", 

736 model: str = "gpt-4o-mini", 

737 **kwargs 

738) -> LLMPipeline: 

739 """ 

740 Create an LLM pipeline with web search capability 

741 Web検索機能付きLLMパイプラインを作成 

742  

743 Note: This is a template - actual web search implementation would require 

744 integration with search APIs like Google Search API, Bing API, etc. 

745 注意:これはテンプレートです。実際のWeb検索実装には 

746 Google Search API、Bing APIなどとの統合が必要です。 

747 """ 

748 def web_search(query: str) -> str: 

749 """Search the web for information (placeholder implementation)""" 

750 # This is a placeholder implementation 

751 # Real implementation would use actual search APIs 

752 return f"Web search results for '{query}': [This is a placeholder. Integrate with actual search API.]" 

753 

754 return create_tool_enabled_llm_pipeline( 

755 name=name, 

756 instructions=instructions, 

757 tools=[web_search], 

758 model=model, 

759 **kwargs 

760 ) 

761 

762 

763def create_calculator_pipeline( 

764 name: str, 

765 instructions: str = "You are a helpful assistant with calculation capabilities. Use the calculator for mathematical computations.", 

766 model: str = "gpt-4o-mini", 

767 **kwargs 

768) -> LLMPipeline: 

769 """ 

770 Create an LLM pipeline with calculation capability 

771 計算機能付きLLMパイプラインを作成 

772 """ 

773 def calculate(expression: str) -> float: 

774 """Calculate mathematical expression safely""" 

775 try: 

776 # For production, use a safer expression evaluator 

777 # 本番環境では、より安全な式評価器を使用 

778 import ast 

779 import operator 

780 

781 # Allowed operations 

782 operators = { 

783 ast.Add: operator.add, 

784 ast.Sub: operator.sub, 

785 ast.Mult: operator.mul, 

786 ast.Div: operator.truediv, 

787 ast.Pow: operator.pow, 

788 ast.Mod: operator.mod, 

789 ast.USub: operator.neg, 

790 } 

791 

792 def eval_expr(expr): 

793 if isinstance(expr, ast.Num): 

794 return expr.n 

795 elif isinstance(expr, ast.Constant): 

796 return expr.value 

797 elif isinstance(expr, ast.BinOp): 

798 return operators[type(expr.op)](eval_expr(expr.left), eval_expr(expr.right)) 

799 elif isinstance(expr, ast.UnaryOp): 

800 return operators[type(expr.op)](eval_expr(expr.operand)) 

801 else: 

802 raise TypeError(f"Unsupported operation: {type(expr)}") 

803 

804 tree = ast.parse(expression, mode='eval') 

805 return eval_expr(tree.body) 

806 

807 except Exception as e: 

808 return f"Error calculating '{expression}': {str(e)}" 

809 

810 return create_tool_enabled_llm_pipeline( 

811 name=name, 

812 instructions=instructions, 

813 tools=[calculate], 

814 model=model, 

815 **kwargs 

816 ) 

817 

818 

819@dataclass 

820class InteractionQuestion: 

821 """ 

822 Represents a question from the interactive pipeline 

823 対話的パイプラインからの質問を表現するクラス 

824  

825 Attributes: 

826 question: The question text / 質問テキスト 

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

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

829 metadata: Additional metadata / 追加メタデータ 

830 """ 

831 question: str # The question text / 質問テキスト 

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

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

834 metadata: Dict[str, Any] = field(default_factory=dict) # Additional metadata / 追加メタデータ 

835 

836 def __str__(self) -> str: 

837 """ 

838 String representation of the interaction question 

839 対話質問の文字列表現 

840  

841 Returns: 

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

843 """ 

844 return f"[Turn {self.turn}/{self.turn + self.remaining_turns}] {self.question}" 

845 

846 

847@dataclass 

848class InteractionResult: 

849 """ 

850 Result from interactive pipeline execution 

851 対話的パイプライン実行の結果 

852  

853 Attributes: 

854 is_complete: True if interaction is complete / 対話が完了した場合True 

855 content: Result content or next question / 結果コンテンツまたは次の質問 

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

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

858 success: Whether execution was successful / 実行が成功したか 

859 metadata: Additional metadata / 追加メタデータ 

860 """ 

861 is_complete: bool # True if interaction is complete / 対話が完了した場合True 

862 content: Any # Result content or next question / 結果コンテンツまたは次の質問 

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

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

865 success: bool = True # Whether execution was successful / 実行が成功したか 

866 metadata: Dict[str, Any] = field(default_factory=dict) # Additional metadata / 追加メタデータ 

867 

868 

869class InteractivePipeline(LLMPipeline): 

870 """ 

871 Interactive Pipeline for multi-turn conversations using LLMPipeline 

872 LLMPipelineを使用した複数ターン会話のための対話的パイプライン 

873  

874 This class extends LLMPipeline to handle: 

875 このクラスはLLMPipelineを拡張して以下を処理します: 

876 - Multi-turn interactive conversations / 複数ターンの対話的会話 

877 - Completion condition checking / 完了条件のチェック 

878 - Turn management / ターン管理 

879 - Conversation history tracking / 会話履歴の追跡 

880  

881 The pipeline uses a completion check function to determine when the interaction is finished. 

882 パイプラインは完了チェック関数を使用して対話の終了時期を判定します。 

883 """ 

884 

885 def __init__( 

886 self, 

887 name: str, 

888 generation_instructions: str, 

889 completion_check: Callable[[Any], bool], 

890 max_turns: int = 20, 

891 evaluation_instructions: Optional[str] = None, 

892 question_format: Optional[Callable[[str, int, int], str]] = None, 

893 **kwargs 

894 ) -> None: 

895 """ 

896 Initialize the InteractivePipeline 

897 InteractivePipelineを初期化する 

898  

899 Args: 

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

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

902 completion_check: Function to check if interaction is complete / 対話完了チェック関数 

903 max_turns: Maximum number of interaction turns / 最大対話ターン数 

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

905 question_format: Optional function to format questions / 質問フォーマット関数(任意) 

906 **kwargs: Additional arguments for LLMPipeline / LLMPipeline用追加引数 

907 """ 

908 # Initialize base LLMPipeline 

909 # ベースのLLMPipelineを初期化 

910 super().__init__( 

911 name=name, 

912 generation_instructions=generation_instructions, 

913 evaluation_instructions=evaluation_instructions, 

914 **kwargs 

915 ) 

916 

917 # Interactive-specific configuration 

918 # 対話固有の設定 

919 self.completion_check = completion_check 

920 self.max_turns = max_turns 

921 self.question_format = question_format or self._default_question_format 

922 

923 # Interaction state 

924 # 対話状態 

925 self._turn_count = 0 

926 self._conversation_history: List[Dict[str, Any]] = [] 

927 self._is_complete = False 

928 self._final_result: Any = None 

929 

930 def run_interactive(self, initial_input: str) -> InteractionResult: 

931 """ 

932 Start an interactive conversation 

933 対話的会話を開始する 

934  

935 Args: 

936 initial_input: Initial user input / 初期ユーザー入力 

937  

938 Returns: 

939 InteractionResult: Initial interaction result / 初期対話結果 

940 """ 

941 self.reset_interaction() 

942 return self.continue_interaction(initial_input) 

943 

944 def continue_interaction(self, user_input: str) -> InteractionResult: 

945 """ 

946 Continue the interactive conversation with user input 

947 ユーザー入力で対話的会話を継続する 

948  

949 Args: 

950 user_input: User input for this turn / このターンのユーザー入力 

951  

952 Returns: 

953 InteractionResult: Interaction result / 対話結果 

954 """ 

955 # Check if max turns reached 

956 # 最大ターン数に達したかを確認 

957 if self._turn_count >= self.max_turns: 

958 return InteractionResult( 

959 is_complete=True, 

960 content=self._final_result, 

961 turn=self._turn_count, 

962 remaining_turns=0, 

963 success=False, 

964 metadata={"error": "Maximum turns reached"} 

965 ) 

966 

967 return self._process_turn(user_input) 

968 

969 def _process_turn(self, user_input: str) -> InteractionResult: 

970 """ 

971 Process a single turn of interaction 

972 単一ターンの対話を処理する 

973  

974 Args: 

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

976  

977 Returns: 

978 InteractionResult: Turn result / ターン結果 

979 """ 

980 try: 

981 # Increment turn count 

982 # ターン数を増加 

983 self._turn_count += 1 

984 

985 # Build context with conversation history 

986 # 会話履歴でコンテキストを構築 

987 context_prompt = self._build_interaction_context() 

988 full_input = f"{context_prompt}\n\nCurrent user input: {user_input}" 

989 

990 # Run the LLMPipeline 

991 # LLMPipelineを実行 

992 llm_result = super().run(full_input) 

993 

994 # Store interaction in history 

995 # 対話を履歴に保存 

996 self._store_turn(user_input, llm_result) 

997 

998 if not llm_result.success: 

999 # Handle LLM execution failure 

1000 # LLM実行失敗を処理 

1001 return InteractionResult( 

1002 is_complete=False, 

1003 content=InteractionQuestion( 

1004 question="Sorry, I encountered an error. Please try again.", 

1005 turn=self._turn_count, 

1006 remaining_turns=max(0, self.max_turns - self._turn_count), 

1007 metadata=llm_result.metadata 

1008 ), 

1009 turn=self._turn_count, 

1010 remaining_turns=max(0, self.max_turns - self._turn_count), 

1011 success=False, 

1012 metadata=llm_result.metadata 

1013 ) 

1014 

1015 # Check if interaction is complete using completion check function 

1016 # 完了チェック関数を使用して対話完了を確認 

1017 if self.completion_check(llm_result.content): 

1018 # Interaction complete 

1019 # 対話完了 

1020 self._is_complete = True 

1021 self._final_result = llm_result.content 

1022 

1023 return InteractionResult( 

1024 is_complete=True, 

1025 content=llm_result.content, 

1026 turn=self._turn_count, 

1027 remaining_turns=0, 

1028 success=True, 

1029 metadata=llm_result.metadata 

1030 ) 

1031 else: 

1032 # Check if max turns reached after this turn 

1033 # このターン後に最大ターン数に達したかを確認 

1034 if self._turn_count >= self.max_turns: 

1035 # Force completion due to max turns 

1036 # 最大ターン数により強制完了 

1037 self._is_complete = True 

1038 self._final_result = llm_result.content 

1039 

1040 return InteractionResult( 

1041 is_complete=True, 

1042 content=llm_result.content, 

1043 turn=self._turn_count, 

1044 remaining_turns=0, 

1045 success=True, 

1046 metadata=llm_result.metadata 

1047 ) 

1048 

1049 # Continue interaction - format as question 

1050 # 対話継続 - 質問としてフォーマット 

1051 question_text = self.question_format( 

1052 str(llm_result.content), 

1053 self._turn_count, 

1054 max(0, self.max_turns - self._turn_count) 

1055 ) 

1056 

1057 question = InteractionQuestion( 

1058 question=question_text, 

1059 turn=self._turn_count, 

1060 remaining_turns=max(0, self.max_turns - self._turn_count), 

1061 metadata=llm_result.metadata 

1062 ) 

1063 

1064 return InteractionResult( 

1065 is_complete=False, 

1066 content=question, 

1067 turn=self._turn_count, 

1068 remaining_turns=max(0, self.max_turns - self._turn_count), 

1069 success=True, 

1070 metadata=llm_result.metadata 

1071 ) 

1072 

1073 except Exception as e: 

1074 # Handle errors gracefully 

1075 # エラーを適切に処理 

1076 return InteractionResult( 

1077 is_complete=False, 

1078 content=InteractionQuestion( 

1079 question=f"An error occurred: {str(e)}. Please try again.", 

1080 turn=self._turn_count, 

1081 remaining_turns=max(0, self.max_turns - self._turn_count), 

1082 metadata={"error": str(e)} 

1083 ), 

1084 turn=self._turn_count, 

1085 remaining_turns=max(0, self.max_turns - self._turn_count), 

1086 success=False, 

1087 metadata={"error": str(e)} 

1088 ) 

1089 

1090 def _build_interaction_context(self) -> str: 

1091 """ 

1092 Build interaction context from conversation history 

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

1094  

1095 Returns: 

1096 str: Conversation context / 会話コンテキスト 

1097 """ 

1098 if not self._conversation_history: 

1099 return "This is the beginning of the conversation." 

1100 

1101 context_parts = ["Previous conversation:"] 

1102 for i, interaction in enumerate(self._conversation_history, 1): 

1103 user_input = interaction.get('user_input', '') 

1104 ai_response = str(interaction.get('ai_result', {}).get('content', '')) 

1105 context_parts.append(f"{i}. User: {user_input}") 

1106 context_parts.append(f" Assistant: {ai_response}") 

1107 

1108 return "\n".join(context_parts) 

1109 

1110 def _store_turn(self, user_input: str, llm_result: LLMResult) -> None: 

1111 """ 

1112 Store interaction turn in conversation history 

1113 対話ターンを会話履歴に保存する 

1114  

1115 Args: 

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

1117 llm_result: LLM result / LLM結果 

1118 """ 

1119 # Get timestamp safely 

1120 try: 

1121 timestamp = asyncio.get_event_loop().time() 

1122 except RuntimeError: 

1123 # Fallback to regular time if no event loop is running 

1124 import time 

1125 timestamp = time.time() 

1126 

1127 interaction = { 

1128 'user_input': user_input, 

1129 'ai_result': { 

1130 'content': llm_result.content, 

1131 'success': llm_result.success, 

1132 'metadata': llm_result.metadata 

1133 }, 

1134 'turn': self._turn_count, 

1135 'timestamp': timestamp 

1136 } 

1137 self._conversation_history.append(interaction) 

1138 

1139 def _default_question_format(self, response: str, turn: int, remaining: int) -> str: 

1140 """ 

1141 Default question formatting function 

1142 デフォルト質問フォーマット関数 

1143  

1144 Args: 

1145 response: AI response / AI応答 

1146 turn: Current turn / 現在のターン 

1147 remaining: Remaining turns / 残りターン 

1148  

1149 Returns: 

1150 str: Formatted question / フォーマット済み質問 

1151 """ 

1152 return f"[Turn {turn}] {response}" 

1153 

1154 def reset_interaction(self) -> None: 

1155 """ 

1156 Reset the interaction session 

1157 対話セッションをリセットする 

1158 """ 

1159 self._turn_count = 0 

1160 self._conversation_history = [] 

1161 self._is_complete = False 

1162 self._final_result = None 

1163 

1164 @property 

1165 def is_complete(self) -> bool: 

1166 """ 

1167 Check if interaction is complete 

1168 対話が完了しているかを確認する 

1169  

1170 Returns: 

1171 bool: True if complete / 完了している場合True 

1172 """ 

1173 return self._is_complete 

1174 

1175 @property 

1176 def current_turn(self) -> int: 

1177 """ 

1178 Get current turn number 

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

1180  

1181 Returns: 

1182 int: Current turn / 現在のターン 

1183 """ 

1184 return self._turn_count 

1185 

1186 @property 

1187 def remaining_turns(self) -> int: 

1188 """ 

1189 Get remaining turns 

1190 残りターン数を取得する 

1191  

1192 Returns: 

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

1194 """ 

1195 return max(0, self.max_turns - self._turn_count) 

1196 

1197 @property 

1198 def interaction_history(self) -> List[Dict[str, Any]]: 

1199 """ 

1200 Get interaction history 

1201 対話履歴を取得する 

1202  

1203 Returns: 

1204 List[Dict[str, Any]]: Interaction history / 対話履歴 

1205 """ 

1206 return self._conversation_history.copy() 

1207 

1208 @property 

1209 def final_result(self) -> Any: 

1210 """ 

1211 Get final result if interaction is complete 

1212 対話完了の場合は最終結果を取得する 

1213  

1214 Returns: 

1215 Any: Final result or None / 最終結果またはNone 

1216 """ 

1217 return self._final_result if self._is_complete else None 

1218 

1219 

1220def create_simple_interactive_pipeline( 

1221 name: str, 

1222 instructions: str, 

1223 completion_check: Callable[[Any], bool], 

1224 max_turns: int = 20, 

1225 model: str = "gpt-4o-mini", 

1226 **kwargs 

1227) -> InteractivePipeline: 

1228 """ 

1229 Create a simple InteractivePipeline with basic configuration 

1230 基本設定でシンプルなInteractivePipelineを作成する 

1231  

1232 Args: 

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

1234 instructions: Generation instructions / 生成指示 

1235 completion_check: Function to check completion / 完了チェック関数 

1236 max_turns: Maximum interaction turns / 最大対話ターン数 

1237 model: LLM model name / LLMモデル名 

1238 **kwargs: Additional arguments / 追加引数 

1239  

1240 Returns: 

1241 InteractivePipeline: Configured pipeline / 設定済みパイプライン 

1242 """ 

1243 return InteractivePipeline( 

1244 name=name, 

1245 generation_instructions=instructions, 

1246 completion_check=completion_check, 

1247 max_turns=max_turns, 

1248 model=model, 

1249 **kwargs 

1250 ) 

1251 

1252 

1253def create_evaluated_interactive_pipeline( 

1254 name: str, 

1255 generation_instructions: str, 

1256 evaluation_instructions: str, 

1257 completion_check: Callable[[Any], bool], 

1258 max_turns: int = 20, 

1259 model: str = "gpt-4o-mini", 

1260 evaluation_model: Optional[str] = None, 

1261 threshold: float = 85.0, 

1262 **kwargs 

1263) -> InteractivePipeline: 

1264 """ 

1265 Create an InteractivePipeline with evaluation capabilities 

1266 評価機能付きInteractivePipelineを作成する 

1267  

1268 Args: 

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

1270 generation_instructions: Generation instructions / 生成指示 

1271 evaluation_instructions: Evaluation instructions / 評価指示 

1272 completion_check: Function to check completion / 完了チェック関数 

1273 max_turns: Maximum interaction turns / 最大対話ターン数 

1274 model: LLM model name / LLMモデル名 

1275 evaluation_model: Evaluation model name / 評価モデル名 

1276 threshold: Evaluation threshold / 評価閾値 

1277 **kwargs: Additional arguments / 追加引数 

1278  

1279 Returns: 

1280 InteractivePipeline: Configured pipeline / 設定済みパイプライン 

1281 """ 

1282 return InteractivePipeline( 

1283 name=name, 

1284 generation_instructions=generation_instructions, 

1285 evaluation_instructions=evaluation_instructions, 

1286 completion_check=completion_check, 

1287 max_turns=max_turns, 

1288 model=model, 

1289 evaluation_model=evaluation_model, 

1290 threshold=threshold, 

1291 **kwargs 

1292 ) 

1293 

1294 

1295# Utility functions for LLMPipeline (existing) 

1296# LLMPipeline用ユーティリティ関数(既存)