langchain-mljunction 0.1.0__py3-none-any.whl
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.
- langchain_mljunction/__init__.py +28 -0
- langchain_mljunction/_client.py +120 -0
- langchain_mljunction/chat_models.py +651 -0
- langchain_mljunction/embeddings.py +77 -0
- langchain_mljunction/outcomes.py +305 -0
- langchain_mljunction/telemetry.py +287 -0
- langchain_mljunction/tracer.py +901 -0
- langchain_mljunction-0.1.0.dist-info/METADATA +327 -0
- langchain_mljunction-0.1.0.dist-info/RECORD +11 -0
- langchain_mljunction-0.1.0.dist-info/WHEEL +4 -0
- langchain_mljunction-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""LangChain integrations for ML Junction."""
|
|
2
|
+
|
|
3
|
+
from langchain_mljunction._client import MLJunctionAPIError
|
|
4
|
+
from langchain_mljunction.chat_models import ChatMLJunction
|
|
5
|
+
from langchain_mljunction.embeddings import MLJunctionEmbeddings
|
|
6
|
+
from langchain_mljunction.outcomes import OutcomeReceipt, Outcomes, request_id_of
|
|
7
|
+
from langchain_mljunction.telemetry import (
|
|
8
|
+
MAX_AGENT_DEPTH,
|
|
9
|
+
MLJunction,
|
|
10
|
+
RunContext,
|
|
11
|
+
expose_agent_as_tool,
|
|
12
|
+
)
|
|
13
|
+
from langchain_mljunction.tracer import MLJunctionTracer
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"MAX_AGENT_DEPTH",
|
|
17
|
+
"ChatMLJunction",
|
|
18
|
+
"MLJunction",
|
|
19
|
+
"MLJunctionAPIError",
|
|
20
|
+
"MLJunctionEmbeddings",
|
|
21
|
+
"MLJunctionTracer",
|
|
22
|
+
"OutcomeReceipt",
|
|
23
|
+
"Outcomes",
|
|
24
|
+
"RunContext",
|
|
25
|
+
"expose_agent_as_tool",
|
|
26
|
+
"request_id_of",
|
|
27
|
+
]
|
|
28
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import AsyncIterator, Iterator
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MLJunctionAPIError(RuntimeError):
|
|
11
|
+
"""Structured gateway failure with status, request ID, code, and details."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, response: httpx.Response) -> None:
|
|
14
|
+
self.status_code = response.status_code
|
|
15
|
+
self.request_id = response.headers.get("x-request-id") or response.headers.get("request-id")
|
|
16
|
+
try:
|
|
17
|
+
body = response.json()
|
|
18
|
+
except ValueError:
|
|
19
|
+
body = {}
|
|
20
|
+
error = body.get("error", body) if isinstance(body, dict) else {}
|
|
21
|
+
self.code = error.get("code") if isinstance(error, dict) else None
|
|
22
|
+
self.error_type = error.get("type") if isinstance(error, dict) else None
|
|
23
|
+
self.details = error.get("details", {}) if isinstance(error, dict) else {}
|
|
24
|
+
message = (
|
|
25
|
+
error.get("message") if isinstance(error, dict) else None
|
|
26
|
+
) or response.reason_phrase
|
|
27
|
+
super().__init__(
|
|
28
|
+
f"ML Junction request failed ({self.status_code}, {self.code or 'unknown'}): {message}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
33
|
+
if response.is_error:
|
|
34
|
+
if not response.is_closed:
|
|
35
|
+
response.read()
|
|
36
|
+
raise MLJunctionAPIError(response)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def _araise_for_status(response: httpx.Response) -> None:
|
|
40
|
+
if response.is_error:
|
|
41
|
+
if not response.is_closed:
|
|
42
|
+
await response.aread()
|
|
43
|
+
raise MLJunctionAPIError(response)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MLJunctionClient:
|
|
47
|
+
"""Small native transport shared by LangChain integrations."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, *, api_key: str, base_url: str, timeout: float, app_name: str | None = None
|
|
51
|
+
) -> None:
|
|
52
|
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
53
|
+
if app_name:
|
|
54
|
+
headers["X-App"] = app_name
|
|
55
|
+
self.sync = httpx.Client(base_url=base_url.rstrip("/"), headers=headers, timeout=timeout)
|
|
56
|
+
self.async_ = httpx.AsyncClient(
|
|
57
|
+
base_url=base_url.rstrip("/"), headers=headers, timeout=timeout
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
+
response = self.sync.post(path, json=payload)
|
|
62
|
+
_raise_for_status(response)
|
|
63
|
+
return response.json()
|
|
64
|
+
|
|
65
|
+
async def apost(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
66
|
+
response = await self.async_.post(path, json=payload)
|
|
67
|
+
_raise_for_status(response)
|
|
68
|
+
return response.json()
|
|
69
|
+
|
|
70
|
+
def put(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
71
|
+
"""Upsert. Used by outcome-definition registration, which is idempotent
|
|
72
|
+
by name rather than creating a new row per call."""
|
|
73
|
+
response = self.sync.put(path, json=payload)
|
|
74
|
+
_raise_for_status(response)
|
|
75
|
+
return response.json()
|
|
76
|
+
|
|
77
|
+
async def aput(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
78
|
+
response = await self.async_.put(path, json=payload)
|
|
79
|
+
await _araise_for_status(response)
|
|
80
|
+
return response.json()
|
|
81
|
+
|
|
82
|
+
def stream(self, path: str, payload: dict[str, Any]) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
83
|
+
with self.sync.stream("POST", path, json=payload) as response:
|
|
84
|
+
_raise_for_status(response)
|
|
85
|
+
yield from _parse_sse(response.iter_lines())
|
|
86
|
+
|
|
87
|
+
async def astream(
|
|
88
|
+
self, path: str, payload: dict[str, Any]
|
|
89
|
+
) -> AsyncIterator[tuple[str, dict[str, Any]]]:
|
|
90
|
+
async with self.async_.stream("POST", path, json=payload) as response:
|
|
91
|
+
await _araise_for_status(response)
|
|
92
|
+
event: str | None = None
|
|
93
|
+
data: list[str] = []
|
|
94
|
+
async for line in response.aiter_lines():
|
|
95
|
+
if line.startswith("event:"):
|
|
96
|
+
event = line[6:].strip()
|
|
97
|
+
elif line.startswith("data:"):
|
|
98
|
+
data.append(line[5:].strip())
|
|
99
|
+
elif not line and event and data:
|
|
100
|
+
yield event, json.loads("\n".join(data))
|
|
101
|
+
event, data = None, []
|
|
102
|
+
|
|
103
|
+
def close(self) -> None:
|
|
104
|
+
self.sync.close()
|
|
105
|
+
|
|
106
|
+
async def aclose(self) -> None:
|
|
107
|
+
await self.async_.aclose()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _parse_sse(lines: Iterator[str]) -> Iterator[tuple[str, dict[str, Any]]]:
|
|
111
|
+
event: str | None = None
|
|
112
|
+
data: list[str] = []
|
|
113
|
+
for line in lines:
|
|
114
|
+
if line.startswith("event:"):
|
|
115
|
+
event = line[6:].strip()
|
|
116
|
+
elif line.startswith("data:"):
|
|
117
|
+
data.append(line[5:].strip())
|
|
118
|
+
elif not line and event and data:
|
|
119
|
+
yield event, json.loads("\n".join(data))
|
|
120
|
+
event, data = None, []
|