langchain.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. """
  2. lambdagent_guard.langchain — LangChain guard wrapper (I11).
  3. Usage:
  4. from lambdagent_guard import guard_langchain
  5. executor = AgentExecutor(agent=agent, tools=tools, max_iterations=20)
  6. guarded = guard_langchain(executor, cost_budget=5.0)
  7. result = guarded.invoke({"input": "Analyze this repo"})
  8. Catches: LangChain #10997 (type crash), #2495 (infinite loop), #26019 (tool re-invoke)
  9. """
  10. from __future__ import annotations
  11. from typing import Any, Callable, Optional
  12. from .core import GuardConfig, RuntimeMonitor, run_compile_checks
  13. import warnings
  14. def guard_langchain(
  15. executor,
  16. cost_budget: float = float("inf"),
  17. type_check: bool = True,
  18. loop_detection: bool = True,
  19. cost_alert: Optional[Callable[[float], None]] = None,
  20. parallel_safety: bool = False,
  21. ):
  22. """
  23. Wrap a LangChain AgentExecutor with lambdagent safety guards.
  24. Non-invasive: returns the same executor with hooks injected.
  25. User code changes: 2 lines (import + wrap).
  26. Args:
  27. executor: LangChain AgentExecutor
  28. cost_budget: Max cost in USD, auto-pause if exceeded
  29. type_check: Verify tool I/O type compatibility at wrap time
  30. loop_detection: Detect repeated identical states at runtime
  31. cost_alert: Callback(cost_usd) on each step
  32. """
  33. guard_cfg = GuardConfig(
  34. cost_budget=cost_budget,
  35. type_check=type_check,
  36. loop_detection=loop_detection,
  37. cost_alert=cost_alert,
  38. parallel_safety=parallel_safety,
  39. )
  40. # Phase 1: Compile-time checks
  41. try:
  42. from lambdagent.extractors import extract_config
  43. config = extract_config(executor, framework="langchain")
  44. issues = run_compile_checks(config, guard_cfg)
  45. for issue in issues:
  46. warnings.warn(f"[lambdagent-guard] {issue}")
  47. except Exception as e:
  48. warnings.warn(f"[lambdagent-guard] Compile check skipped: {e}")
  49. # Phase 2: Runtime hooks
  50. monitor = RuntimeMonitor(guard_cfg)
  51. # Hook into AgentExecutor's step method
  52. original_take_step = None
  53. if hasattr(executor, '_take_next_step'):
  54. original_take_step = executor._take_next_step
  55. elif hasattr(executor, 'agent') and hasattr(executor.agent, 'plan'):
  56. original_take_step = executor.agent.plan
  57. if original_take_step is not None:
  58. def guarded_step(*args, **kwargs):
  59. result = original_take_step(*args, **kwargs)
  60. # Extract info for monitoring
  61. output_str = str(result)[:500] if result else ""
  62. monitor.on_step({
  63. "output": output_str,
  64. "tokens": 0, # LangChain doesn't expose tokens per step easily
  65. })
  66. return result
  67. if hasattr(executor, '_take_next_step'):
  68. executor._take_next_step = guarded_step
  69. elif hasattr(executor, 'agent') and hasattr(executor.agent, 'plan'):
  70. executor.agent.plan = guarded_step
  71. # Attach monitor for result extraction
  72. executor._lambdagent_monitor = monitor
  73. return executor