aigp-langchain 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Node
|
|
11
|
+
node_modules/
|
|
12
|
+
dist/
|
|
13
|
+
|
|
14
|
+
# Go
|
|
15
|
+
bin/
|
|
16
|
+
|
|
17
|
+
# Rust
|
|
18
|
+
target/
|
|
19
|
+
|
|
20
|
+
# .NET
|
|
21
|
+
bin/
|
|
22
|
+
obj/
|
|
23
|
+
|
|
24
|
+
# Java
|
|
25
|
+
*.class
|
|
26
|
+
out/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
|
|
33
|
+
# OS
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
36
|
+
|
|
37
|
+
# Secrets (never commit)
|
|
38
|
+
.env
|
|
39
|
+
*.pem
|
|
40
|
+
*.key
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""LangChain Callback Handler — maps LangChain lifecycle to AIGP governance."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from aigp_agent_core import AgentGovernance
|
|
10
|
+
from aigp_agent_core.governance import GovernanceBlockedError
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AigpCallbackHandler:
|
|
16
|
+
"""LangChain BaseCallbackHandler implementing AIGP governance.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
handler = AigpCallbackHandler(gov_url=..., app_id=..., hmac_secret=...)
|
|
20
|
+
agent = AgentExecutor(agent=..., tools=[...], callbacks=[handler])
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, gov_url: str, app_id: str, hmac_secret: str, **kwargs):
|
|
24
|
+
self._gov = AgentGovernance(gov_url, app_id, hmac_secret, **kwargs)
|
|
25
|
+
self._root_run_id: UUID | None = None
|
|
26
|
+
self._tool_starts: dict[UUID, float] = {}
|
|
27
|
+
|
|
28
|
+
# --- Chain events ---
|
|
29
|
+
|
|
30
|
+
def on_chain_start(self, serialized: dict, inputs: dict, *, run_id: UUID, parent_run_id: UUID | None = None, **kwargs):
|
|
31
|
+
"""Root chain start → AIGP CHECK."""
|
|
32
|
+
if parent_run_id is None:
|
|
33
|
+
self._root_run_id = run_id
|
|
34
|
+
agent_name = serialized.get("name", serialized.get("id", ["agent"])[-1])
|
|
35
|
+
model_id = kwargs.get("model_id", "unknown")
|
|
36
|
+
self._gov.pre_invoke(agent_name, model_id, kwargs.get("user_id", ""))
|
|
37
|
+
|
|
38
|
+
def on_chain_end(self, outputs: dict, *, run_id: UUID, parent_run_id: UUID | None = None, **kwargs):
|
|
39
|
+
"""Root chain end → RECORD + TRACE + evidence."""
|
|
40
|
+
if parent_run_id is None and run_id == self._root_run_id:
|
|
41
|
+
self._run_async(self._gov.post_invoke())
|
|
42
|
+
|
|
43
|
+
def on_chain_error(self, error: BaseException, *, run_id: UUID, parent_run_id: UUID | None = None, **kwargs):
|
|
44
|
+
"""Root chain error → RECORD with ERROR."""
|
|
45
|
+
if parent_run_id is None:
|
|
46
|
+
self._run_async(self._gov.on_error(error))
|
|
47
|
+
|
|
48
|
+
# --- LLM events ---
|
|
49
|
+
|
|
50
|
+
def on_llm_start(self, serialized: dict, prompts: list[str], *, run_id: UUID, **kwargs):
|
|
51
|
+
"""LLM invocation starting."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def on_llm_end(self, response: Any, *, run_id: UUID, **kwargs):
|
|
55
|
+
"""LLM invocation complete — accumulate tokens."""
|
|
56
|
+
usage = {}
|
|
57
|
+
if hasattr(response, "llm_output") and response.llm_output:
|
|
58
|
+
usage = response.llm_output.get("usage", response.llm_output.get("token_usage", {}))
|
|
59
|
+
elif hasattr(response, "usage_metadata") and response.usage_metadata:
|
|
60
|
+
usage = response.usage_metadata
|
|
61
|
+
self._gov.on_model_call(usage=usage)
|
|
62
|
+
|
|
63
|
+
def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# --- Tool events ---
|
|
67
|
+
|
|
68
|
+
def on_tool_start(self, serialized: dict, input_str: str, *, run_id: UUID, **kwargs):
|
|
69
|
+
"""Tool execution starting."""
|
|
70
|
+
self._tool_starts[run_id] = time.time()
|
|
71
|
+
|
|
72
|
+
def on_tool_end(self, output: str, *, run_id: UUID, **kwargs):
|
|
73
|
+
"""Tool execution complete."""
|
|
74
|
+
start = self._tool_starts.pop(run_id, time.time())
|
|
75
|
+
duration_ms = int((time.time() - start) * 1000)
|
|
76
|
+
tool_name = kwargs.get("name", "unknown_tool")
|
|
77
|
+
self._run_async(self._gov.on_tool_call(tool_name, duration_ms=duration_ms))
|
|
78
|
+
|
|
79
|
+
def on_tool_error(self, error: BaseException, *, run_id: UUID, **kwargs):
|
|
80
|
+
self._tool_starts.pop(run_id, None)
|
|
81
|
+
|
|
82
|
+
# --- Retriever events ---
|
|
83
|
+
|
|
84
|
+
def on_retriever_start(self, serialized: dict, query: str, *, run_id: UUID, **kwargs):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
def on_retriever_end(self, documents: list, *, run_id: UUID, **kwargs):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
# --- Helpers ---
|
|
91
|
+
|
|
92
|
+
def _run_async(self, coro):
|
|
93
|
+
try:
|
|
94
|
+
loop = asyncio.get_event_loop()
|
|
95
|
+
if loop.is_running():
|
|
96
|
+
asyncio.ensure_future(coro)
|
|
97
|
+
else:
|
|
98
|
+
loop.run_until_complete(coro)
|
|
99
|
+
except RuntimeError:
|
|
100
|
+
asyncio.run(coro)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aigp-langchain"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "AIGP governance adapter for LangChain/LangGraph"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["aigp-agent-core>=1.0.0"]
|