ironshell 1.5.1__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.
- ironshell/__init__.py +682 -0
- ironshell/cloud.py +967 -0
- ironshell/distributed.py +691 -0
- ironshell/langchain.py +1074 -0
- ironshell/metrics.py +603 -0
- ironshell/py.typed +0 -0
- ironshell/workflows.py +539 -0
- ironshell-1.5.1.dist-info/METADATA +349 -0
- ironshell-1.5.1.dist-info/RECORD +11 -0
- ironshell-1.5.1.dist-info/WHEEL +5 -0
- ironshell-1.5.1.dist-info/top_level.txt +1 -0
ironshell/langchain.py
ADDED
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IronShell LangChain Integration
|
|
3
|
+
|
|
4
|
+
Enhanced LangChain tools for IronShell:
|
|
5
|
+
- Core execution tools (code, pipelines, agents)
|
|
6
|
+
- Workflow orchestration tools (MapReduce, Saga, Fan-Out)
|
|
7
|
+
- Metrics and observability tools
|
|
8
|
+
- Distributed agent tools
|
|
9
|
+
- Service registry tools
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import urllib.request
|
|
17
|
+
import urllib.error
|
|
18
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from langchain.tools import BaseTool
|
|
22
|
+
from langchain.callbacks.manager import CallbackManagerForToolRun
|
|
23
|
+
from pydantic import BaseModel, Field
|
|
24
|
+
LANGCHAIN_AVAILABLE = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
LANGCHAIN_AVAILABLE = False
|
|
27
|
+
BaseTool = object
|
|
28
|
+
BaseModel = object
|
|
29
|
+
Field = lambda **kwargs: None
|
|
30
|
+
CallbackManagerForToolRun = None
|
|
31
|
+
|
|
32
|
+
from . import IronShellRuntime, Agent as IronShellAgent, _ironshell_value
|
|
33
|
+
from .workflows import (
|
|
34
|
+
MapReduceWorkflow,
|
|
35
|
+
SagaWorkflow,
|
|
36
|
+
SagaStep,
|
|
37
|
+
FanOutWorkflow,
|
|
38
|
+
PipelineWorkflow,
|
|
39
|
+
CircuitBreaker,
|
|
40
|
+
WorkflowResult,
|
|
41
|
+
)
|
|
42
|
+
from .metrics import (
|
|
43
|
+
MetricsCollector,
|
|
44
|
+
get_metrics_collector,
|
|
45
|
+
Tracer,
|
|
46
|
+
Timer,
|
|
47
|
+
)
|
|
48
|
+
from .distributed import (
|
|
49
|
+
ServiceRegistry,
|
|
50
|
+
LeaderElection,
|
|
51
|
+
AgentRouter,
|
|
52
|
+
LoadBalancer,
|
|
53
|
+
DistributedSwarm,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Original tools
|
|
58
|
+
"IronShellTool",
|
|
59
|
+
"IronShellPipelineTool",
|
|
60
|
+
"IronShellAgentTool",
|
|
61
|
+
# New enhanced tools
|
|
62
|
+
"IronShellWorkflowTool",
|
|
63
|
+
"IronShellMapReduceTool",
|
|
64
|
+
"IronShellSagaTool",
|
|
65
|
+
"IronShellMetricsTool",
|
|
66
|
+
"IronShellTracingTool",
|
|
67
|
+
"IronShellDistributedAgentTool",
|
|
68
|
+
"IronShellServiceRegistryTool",
|
|
69
|
+
# Factory functions
|
|
70
|
+
"get_ironshell_tools",
|
|
71
|
+
"get_all_ironshell_tools",
|
|
72
|
+
"create_workflow_agent",
|
|
73
|
+
# HTTP Agent API tools
|
|
74
|
+
"AgentAPIClient",
|
|
75
|
+
"IronShellAgentAPITool",
|
|
76
|
+
"IronShellAgentAPIEvalTool",
|
|
77
|
+
"IronShellAgentAPIBuiltinsTool",
|
|
78
|
+
"get_agent_api_tools",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _check_langchain():
|
|
83
|
+
if not LANGCHAIN_AVAILABLE:
|
|
84
|
+
raise ImportError(
|
|
85
|
+
"langchain is required for LangChain integration. "
|
|
86
|
+
"Install with: pip install ironshell[langchain]"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ============================================================================
|
|
92
|
+
# HTTP Agent API Client
|
|
93
|
+
# ============================================================================
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AgentAPIClient:
|
|
97
|
+
"""
|
|
98
|
+
HTTP client for the IronShell Agent API server.
|
|
99
|
+
|
|
100
|
+
Connects to a running ``ish agent serve`` instance (default port 3002)
|
|
101
|
+
and sends structured JSON requests for code evaluation, builtin listing,
|
|
102
|
+
and schema retrieval.
|
|
103
|
+
|
|
104
|
+
Usage::
|
|
105
|
+
|
|
106
|
+
client = AgentAPIClient()
|
|
107
|
+
client = AgentAPIClient("http://myhost:3002")
|
|
108
|
+
|
|
109
|
+
result = client.eval('[1,2,3] | map(fn(x) => x * 2)')
|
|
110
|
+
builtins = client.list_builtins()
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, base_url: str = "http://127.0.0.1:3002", timeout: int = 30):
|
|
114
|
+
self.base_url = base_url.rstrip("/")
|
|
115
|
+
self.timeout = timeout
|
|
116
|
+
|
|
117
|
+
def _post(self, payload: dict) -> dict:
|
|
118
|
+
"""Send a JSON POST to the Agent API /request endpoint."""
|
|
119
|
+
data = json.dumps(payload).encode("utf-8")
|
|
120
|
+
req = urllib.request.Request(
|
|
121
|
+
f"{self.base_url}/request",
|
|
122
|
+
data=data,
|
|
123
|
+
headers={"Content-Type": "application/json"},
|
|
124
|
+
method="POST",
|
|
125
|
+
)
|
|
126
|
+
try:
|
|
127
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
128
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
129
|
+
except urllib.error.URLError as exc:
|
|
130
|
+
raise ConnectionError(
|
|
131
|
+
f"Cannot reach Agent API at {self.base_url}. "
|
|
132
|
+
f"Start the server with: ish agent serve -- {exc}"
|
|
133
|
+
) from exc
|
|
134
|
+
|
|
135
|
+
def eval(self, code: str) -> Any:
|
|
136
|
+
"""Evaluate IronShell code via the Agent API."""
|
|
137
|
+
resp = self._post({"action": "eval", "code": code})
|
|
138
|
+
if resp.get("success"):
|
|
139
|
+
return resp.get("result")
|
|
140
|
+
raise RuntimeError(resp.get("error", "Unknown Agent API error"))
|
|
141
|
+
|
|
142
|
+
def list_builtins(self, category: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
143
|
+
"""List available IronShell builtins, optionally filtered by category."""
|
|
144
|
+
payload: Dict[str, Any] = {"action": "list_builtins"}
|
|
145
|
+
if category:
|
|
146
|
+
payload["category"] = category
|
|
147
|
+
resp = self._post(payload)
|
|
148
|
+
if resp.get("success"):
|
|
149
|
+
result = resp.get("result", {})
|
|
150
|
+
return result.get("builtins", [])
|
|
151
|
+
raise RuntimeError(resp.get("error", "Unknown Agent API error"))
|
|
152
|
+
|
|
153
|
+
def get_schema(self, fmt: str = "compact") -> str:
|
|
154
|
+
"""Get the IronShell language schema for AI agents."""
|
|
155
|
+
resp = self._post({"action": "schema", "format": fmt})
|
|
156
|
+
if resp.get("success"):
|
|
157
|
+
return json.dumps(resp.get("result", {}), indent=2)
|
|
158
|
+
raise RuntimeError(resp.get("error", "Unknown Agent API error"))
|
|
159
|
+
|
|
160
|
+
def ping(self) -> bool:
|
|
161
|
+
"""Check if the Agent API server is reachable."""
|
|
162
|
+
try:
|
|
163
|
+
self._post({"action": "list_builtins"})
|
|
164
|
+
return True
|
|
165
|
+
except (ConnectionError, Exception):
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
# ============================================================================
|
|
169
|
+
# Core Tools (Enhanced)
|
|
170
|
+
# ============================================================================
|
|
171
|
+
|
|
172
|
+
if LANGCHAIN_AVAILABLE:
|
|
173
|
+
|
|
174
|
+
class IronShellInput(BaseModel):
|
|
175
|
+
"""Input schema for IronShell tool"""
|
|
176
|
+
code: str = Field(description="IronShell code to evaluate")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class IronShellTool(BaseTool):
|
|
180
|
+
"""
|
|
181
|
+
LangChain tool for executing IronShell code.
|
|
182
|
+
|
|
183
|
+
IronShell is a typed shell with AI capabilities. It supports:
|
|
184
|
+
- Typed data pipelines: `[1,2,3] | map(fn(x) => x * 2)`
|
|
185
|
+
- Records and tables: `{name: "John", age: 30}`
|
|
186
|
+
- File operations: `ls "." | where(fn(r) => r.size > 1000)`
|
|
187
|
+
- HTTP requests: `http_get("https://api.example.com")`
|
|
188
|
+
- JSON processing: `parse_json(data) | get("items")`
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
name: str = "ironshell"
|
|
192
|
+
description: str = (
|
|
193
|
+
"Execute IronShell code for data processing, file operations, "
|
|
194
|
+
"and system tasks. IronShell uses typed pipelines for data "
|
|
195
|
+
"transformation. Example: `[1,2,3] | map(fn(x) => x * 2)` returns [2,4,6]"
|
|
196
|
+
)
|
|
197
|
+
args_schema: Type[BaseModel] = IronShellInput
|
|
198
|
+
|
|
199
|
+
runtime: IronShellRuntime = None
|
|
200
|
+
|
|
201
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
202
|
+
super().__init__(**kwargs)
|
|
203
|
+
self.runtime = runtime or IronShellRuntime()
|
|
204
|
+
|
|
205
|
+
def _run(
|
|
206
|
+
self,
|
|
207
|
+
code: str,
|
|
208
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
209
|
+
) -> str:
|
|
210
|
+
"""Execute IronShell code"""
|
|
211
|
+
try:
|
|
212
|
+
result = self.runtime.eval(code)
|
|
213
|
+
if isinstance(result, (dict, list)):
|
|
214
|
+
return json.dumps(result, indent=2)
|
|
215
|
+
return str(result)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
return f"Error: {e}"
|
|
218
|
+
|
|
219
|
+
async def _arun(
|
|
220
|
+
self,
|
|
221
|
+
code: str,
|
|
222
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
223
|
+
) -> str:
|
|
224
|
+
"""Execute IronShell code asynchronously"""
|
|
225
|
+
result = await asyncio.to_thread(self.runtime.eval, code)
|
|
226
|
+
return json.dumps(result, default=str) if isinstance(result, (dict, list)) else str(result)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class IronShellPipelineInput(BaseModel):
|
|
230
|
+
"""Input schema for pipeline tool"""
|
|
231
|
+
data: str = Field(description="JSON data to process")
|
|
232
|
+
operations: str = Field(
|
|
233
|
+
description="Pipeline operations (e.g., 'map(fn(x) => x * 2) | filter(fn(x) => x > 0)')"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class IronShellPipelineTool(BaseTool):
|
|
238
|
+
"""
|
|
239
|
+
LangChain tool for executing IronShell pipelines on data.
|
|
240
|
+
|
|
241
|
+
Pipelines transform data through a series of operations:
|
|
242
|
+
- map: Transform each element
|
|
243
|
+
- filter: Keep elements matching condition
|
|
244
|
+
- reduce: Aggregate elements
|
|
245
|
+
- sort, reverse, unique, flatten
|
|
246
|
+
- select, where: Table/record operations
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
name: str = "ironshell_pipeline"
|
|
250
|
+
description: str = (
|
|
251
|
+
"Process data through IronShell pipelines. "
|
|
252
|
+
"Provide JSON data and pipeline operations. "
|
|
253
|
+
"Example: data='[1,2,3]', operations='map(fn(x) => x * 2)' returns [2,4,6]"
|
|
254
|
+
)
|
|
255
|
+
args_schema: Type[BaseModel] = IronShellPipelineInput
|
|
256
|
+
|
|
257
|
+
runtime: IronShellRuntime = None
|
|
258
|
+
|
|
259
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
260
|
+
super().__init__(**kwargs)
|
|
261
|
+
self.runtime = runtime or IronShellRuntime()
|
|
262
|
+
|
|
263
|
+
def _run(
|
|
264
|
+
self,
|
|
265
|
+
data: str,
|
|
266
|
+
operations: str,
|
|
267
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
268
|
+
) -> str:
|
|
269
|
+
"""Execute pipeline on data"""
|
|
270
|
+
try:
|
|
271
|
+
code = f"{data} | {operations}"
|
|
272
|
+
result = self.runtime.eval(code)
|
|
273
|
+
if isinstance(result, (dict, list)):
|
|
274
|
+
return json.dumps(result, indent=2)
|
|
275
|
+
return str(result)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
return f"Error: {e}"
|
|
278
|
+
|
|
279
|
+
async def _arun(
|
|
280
|
+
self,
|
|
281
|
+
data: str,
|
|
282
|
+
operations: str,
|
|
283
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
284
|
+
) -> str:
|
|
285
|
+
"""Execute pipeline asynchronously"""
|
|
286
|
+
code = f"{data} | {operations}"
|
|
287
|
+
result = await asyncio.to_thread(self.runtime.eval, code)
|
|
288
|
+
return json.dumps(result, default=str) if isinstance(result, (dict, list)) else str(result)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class IronShellAgentInput(BaseModel):
|
|
292
|
+
"""Input schema for agent tool"""
|
|
293
|
+
goal: str = Field(description="Goal for the agent to accomplish")
|
|
294
|
+
tools: List[str] = Field(
|
|
295
|
+
default=[],
|
|
296
|
+
description="Tools the agent can use (e.g., ['http_get', 'read_text'])"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class IronShellAgentTool(BaseTool):
|
|
301
|
+
"""
|
|
302
|
+
LangChain tool for running IronShell AI agents.
|
|
303
|
+
|
|
304
|
+
Creates an AI agent that can use tools to accomplish goals.
|
|
305
|
+
The agent reasons about the goal, plans steps, and executes
|
|
306
|
+
tools to achieve the objective.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
name: str = "ironshell_agent"
|
|
310
|
+
description: str = (
|
|
311
|
+
"Run an AI agent with tools to accomplish a goal. "
|
|
312
|
+
"The agent will reason and use tools like http_get, read_text, write_file. "
|
|
313
|
+
"Example: goal='Find Python docs homepage', tools=['http_get']"
|
|
314
|
+
)
|
|
315
|
+
args_schema: Type[BaseModel] = IronShellAgentInput
|
|
316
|
+
|
|
317
|
+
runtime: IronShellRuntime = None
|
|
318
|
+
model: str = "openai:gpt-4o-mini"
|
|
319
|
+
max_steps: int = 10
|
|
320
|
+
|
|
321
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
322
|
+
super().__init__(**kwargs)
|
|
323
|
+
self.runtime = runtime or IronShellRuntime()
|
|
324
|
+
|
|
325
|
+
def _run(
|
|
326
|
+
self,
|
|
327
|
+
goal: str,
|
|
328
|
+
tools: List[str] = None,
|
|
329
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
330
|
+
) -> str:
|
|
331
|
+
"""Run agent with goal"""
|
|
332
|
+
async def run_agent():
|
|
333
|
+
agent = self.runtime.create_agent(
|
|
334
|
+
name="langchain_agent",
|
|
335
|
+
model=self.model,
|
|
336
|
+
tools=tools or [],
|
|
337
|
+
max_steps=self.max_steps,
|
|
338
|
+
)
|
|
339
|
+
result = await agent.run(goal)
|
|
340
|
+
return result
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
result = asyncio.run(run_agent())
|
|
344
|
+
if result.success:
|
|
345
|
+
return str(result.result)
|
|
346
|
+
else:
|
|
347
|
+
return f"Agent failed: {result.result}"
|
|
348
|
+
except Exception as e:
|
|
349
|
+
return f"Error: {e}"
|
|
350
|
+
|
|
351
|
+
async def _arun(
|
|
352
|
+
self,
|
|
353
|
+
goal: str,
|
|
354
|
+
tools: List[str] = None,
|
|
355
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
356
|
+
) -> str:
|
|
357
|
+
"""Run agent asynchronously"""
|
|
358
|
+
agent = self.runtime.create_agent(
|
|
359
|
+
name="langchain_agent",
|
|
360
|
+
model=self.model,
|
|
361
|
+
tools=tools or [],
|
|
362
|
+
max_steps=self.max_steps,
|
|
363
|
+
)
|
|
364
|
+
result = await agent.run(goal)
|
|
365
|
+
return str(result.result) if result.success else f"Agent failed: {result.result}"
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# ============================================================================
|
|
369
|
+
# Workflow Tools
|
|
370
|
+
# ============================================================================
|
|
371
|
+
|
|
372
|
+
class WorkflowInput(BaseModel):
|
|
373
|
+
"""Input for workflow execution"""
|
|
374
|
+
input_data: str = Field(description="JSON input data for the workflow")
|
|
375
|
+
workflow_type: str = Field(
|
|
376
|
+
default="pipeline",
|
|
377
|
+
description="Workflow type: 'pipeline', 'map_reduce', 'saga', 'fan_out'"
|
|
378
|
+
)
|
|
379
|
+
steps: str = Field(
|
|
380
|
+
default="[]",
|
|
381
|
+
description="JSON array of step configurations"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class IronShellWorkflowTool(BaseTool):
|
|
386
|
+
"""
|
|
387
|
+
Execute IronShell workflows.
|
|
388
|
+
|
|
389
|
+
Supports multiple workflow patterns for distributed processing:
|
|
390
|
+
- pipeline: Sequential processing stages
|
|
391
|
+
- map_reduce: Parallel map, then reduce
|
|
392
|
+
- saga: Steps with compensation on failure
|
|
393
|
+
- fan_out: Parallel workers with result gathering
|
|
394
|
+
"""
|
|
395
|
+
name: str = "ironshell_workflow"
|
|
396
|
+
description: str = """Execute an IronShell workflow.
|
|
397
|
+
Workflow types:
|
|
398
|
+
- 'pipeline': Sequential processing stages
|
|
399
|
+
- 'map_reduce': Parallel map, then reduce
|
|
400
|
+
- 'saga': Steps with compensation on failure
|
|
401
|
+
- 'fan_out': Parallel workers with result gathering
|
|
402
|
+
|
|
403
|
+
Example: input_data='[1,2,3]' workflow_type='pipeline' steps='[{"code": "x * 2"}]'
|
|
404
|
+
"""
|
|
405
|
+
args_schema: Type[BaseModel] = WorkflowInput
|
|
406
|
+
|
|
407
|
+
runtime: IronShellRuntime = None
|
|
408
|
+
|
|
409
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
410
|
+
super().__init__(**kwargs)
|
|
411
|
+
self.runtime = runtime or IronShellRuntime()
|
|
412
|
+
|
|
413
|
+
def _run(
|
|
414
|
+
self,
|
|
415
|
+
input_data: str,
|
|
416
|
+
workflow_type: str = "pipeline",
|
|
417
|
+
steps: str = "[]",
|
|
418
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
419
|
+
) -> str:
|
|
420
|
+
"""Execute workflow synchronously"""
|
|
421
|
+
data = json.loads(input_data)
|
|
422
|
+
step_configs = json.loads(steps)
|
|
423
|
+
|
|
424
|
+
result = asyncio.run(self._execute_workflow(data, workflow_type, step_configs))
|
|
425
|
+
return json.dumps({
|
|
426
|
+
"success": result.success,
|
|
427
|
+
"result": result.result,
|
|
428
|
+
"steps_completed": result.steps_completed,
|
|
429
|
+
"total_steps": result.total_steps,
|
|
430
|
+
"duration_ms": result.duration_ms,
|
|
431
|
+
"errors": result.errors,
|
|
432
|
+
}, default=str)
|
|
433
|
+
|
|
434
|
+
async def _arun(
|
|
435
|
+
self,
|
|
436
|
+
input_data: str,
|
|
437
|
+
workflow_type: str = "pipeline",
|
|
438
|
+
steps: str = "[]",
|
|
439
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
440
|
+
) -> str:
|
|
441
|
+
"""Execute workflow asynchronously"""
|
|
442
|
+
data = json.loads(input_data)
|
|
443
|
+
step_configs = json.loads(steps)
|
|
444
|
+
|
|
445
|
+
result = await self._execute_workflow(data, workflow_type, step_configs)
|
|
446
|
+
return json.dumps({
|
|
447
|
+
"success": result.success,
|
|
448
|
+
"result": result.result,
|
|
449
|
+
"steps_completed": result.steps_completed,
|
|
450
|
+
"total_steps": result.total_steps,
|
|
451
|
+
"duration_ms": result.duration_ms,
|
|
452
|
+
"errors": result.errors,
|
|
453
|
+
}, default=str)
|
|
454
|
+
|
|
455
|
+
async def _execute_workflow(
|
|
456
|
+
self,
|
|
457
|
+
data: Any,
|
|
458
|
+
workflow_type: str,
|
|
459
|
+
step_configs: List[Dict],
|
|
460
|
+
) -> WorkflowResult:
|
|
461
|
+
"""Execute the specified workflow type"""
|
|
462
|
+
if workflow_type == "pipeline":
|
|
463
|
+
stages = [
|
|
464
|
+
lambda d, code=s.get("code", "x"): self.runtime.eval(
|
|
465
|
+
f'let x = {_ironshell_value(d)}; {code}'
|
|
466
|
+
)
|
|
467
|
+
for s in step_configs
|
|
468
|
+
]
|
|
469
|
+
workflow = PipelineWorkflow("langchain_pipeline", stages=stages)
|
|
470
|
+
return await workflow.run(data)
|
|
471
|
+
|
|
472
|
+
elif workflow_type == "map_reduce":
|
|
473
|
+
map_code = step_configs[0].get("map", "x") if step_configs else "x"
|
|
474
|
+
reduce_code = step_configs[0].get("reduce", "a + b") if step_configs else "a + b"
|
|
475
|
+
|
|
476
|
+
def map_fn(item):
|
|
477
|
+
return self.runtime.eval(f'let x = {_ironshell_value(item)}; {map_code}')
|
|
478
|
+
|
|
479
|
+
def reduce_fn(a, b):
|
|
480
|
+
return self.runtime.eval(
|
|
481
|
+
f'let a = {_ironshell_value(a)}; let b = {_ironshell_value(b)}; {reduce_code}'
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
workflow = MapReduceWorkflow("langchain_mapreduce", map_fn=map_fn, reduce_fn=reduce_fn)
|
|
485
|
+
return await workflow.run(data)
|
|
486
|
+
|
|
487
|
+
else:
|
|
488
|
+
workflow = PipelineWorkflow("langchain_default")
|
|
489
|
+
return await workflow.run(data)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class MapReduceInput(BaseModel):
|
|
493
|
+
"""Input for MapReduce operations"""
|
|
494
|
+
data: str = Field(description="JSON array of data to process")
|
|
495
|
+
map_expr: str = Field(description="IronShell expression for map (use 'x' for item)")
|
|
496
|
+
reduce_expr: str = Field(description="IronShell expression for reduce (use 'a', 'b')")
|
|
497
|
+
chunk_size: int = Field(default=100, description="Size of chunks for parallel processing")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class IronShellMapReduceTool(BaseTool):
|
|
501
|
+
"""
|
|
502
|
+
Execute MapReduce operations on data.
|
|
503
|
+
|
|
504
|
+
Splits data into chunks, processes in parallel, then combines.
|
|
505
|
+
"""
|
|
506
|
+
name: str = "ironshell_mapreduce"
|
|
507
|
+
description: str = """Execute a MapReduce operation.
|
|
508
|
+
Example: data='[1,2,3,4,5]' map_expr='x * 2' reduce_expr='a + b'
|
|
509
|
+
Result: Sums doubled values = 30
|
|
510
|
+
"""
|
|
511
|
+
args_schema: Type[BaseModel] = MapReduceInput
|
|
512
|
+
runtime: IronShellRuntime = None
|
|
513
|
+
|
|
514
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
515
|
+
super().__init__(**kwargs)
|
|
516
|
+
self.runtime = runtime or IronShellRuntime()
|
|
517
|
+
|
|
518
|
+
def _run(
|
|
519
|
+
self,
|
|
520
|
+
data: str,
|
|
521
|
+
map_expr: str,
|
|
522
|
+
reduce_expr: str,
|
|
523
|
+
chunk_size: int = 100,
|
|
524
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
525
|
+
) -> str:
|
|
526
|
+
"""Execute MapReduce synchronously"""
|
|
527
|
+
result = asyncio.run(self._mapreduce(data, map_expr, reduce_expr, chunk_size))
|
|
528
|
+
return json.dumps(result, default=str)
|
|
529
|
+
|
|
530
|
+
async def _arun(
|
|
531
|
+
self,
|
|
532
|
+
data: str,
|
|
533
|
+
map_expr: str,
|
|
534
|
+
reduce_expr: str,
|
|
535
|
+
chunk_size: int = 100,
|
|
536
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
537
|
+
) -> str:
|
|
538
|
+
"""Execute MapReduce asynchronously"""
|
|
539
|
+
result = await self._mapreduce(data, map_expr, reduce_expr, chunk_size)
|
|
540
|
+
return json.dumps(result, default=str)
|
|
541
|
+
|
|
542
|
+
async def _mapreduce(self, data: str, map_expr: str, reduce_expr: str, chunk_size: int) -> Dict[str, Any]:
|
|
543
|
+
"""Perform MapReduce"""
|
|
544
|
+
items = json.loads(data)
|
|
545
|
+
|
|
546
|
+
def map_fn(item):
|
|
547
|
+
return self.runtime.eval(f'let x = {_ironshell_value(item)}; {map_expr}')
|
|
548
|
+
|
|
549
|
+
def reduce_fn(a, b):
|
|
550
|
+
return self.runtime.eval(f'let a = {_ironshell_value(a)}; let b = {_ironshell_value(b)}; {reduce_expr}')
|
|
551
|
+
|
|
552
|
+
workflow = MapReduceWorkflow("langchain_mapreduce", map_fn=map_fn, reduce_fn=reduce_fn, chunk_size=chunk_size)
|
|
553
|
+
result = await workflow.run(items)
|
|
554
|
+
return {"success": result.success, "result": result.result, "duration_ms": result.duration_ms}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class SagaInput(BaseModel):
|
|
558
|
+
"""Input for Saga workflow"""
|
|
559
|
+
steps: str = Field(description="JSON array of saga steps with 'action' and 'compensate' code")
|
|
560
|
+
input_data: str = Field(description="JSON input data")
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
class IronShellSagaTool(BaseTool):
|
|
564
|
+
"""
|
|
565
|
+
Execute Saga workflow pattern.
|
|
566
|
+
|
|
567
|
+
Runs steps in sequence; on failure, executes compensation in reverse.
|
|
568
|
+
"""
|
|
569
|
+
name: str = "ironshell_saga"
|
|
570
|
+
description: str = """Execute a Saga workflow with compensation.
|
|
571
|
+
Steps run sequentially. On failure, compensations run in reverse.
|
|
572
|
+
Example: steps='[{"action": "x + 1", "compensate": "x - 1"}]'
|
|
573
|
+
"""
|
|
574
|
+
args_schema: Type[BaseModel] = SagaInput
|
|
575
|
+
runtime: IronShellRuntime = None
|
|
576
|
+
|
|
577
|
+
def __init__(self, runtime: Optional[IronShellRuntime] = None, **kwargs):
|
|
578
|
+
super().__init__(**kwargs)
|
|
579
|
+
self.runtime = runtime or IronShellRuntime()
|
|
580
|
+
|
|
581
|
+
def _run(
|
|
582
|
+
self,
|
|
583
|
+
steps: str,
|
|
584
|
+
input_data: str,
|
|
585
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
586
|
+
) -> str:
|
|
587
|
+
"""Execute Saga synchronously"""
|
|
588
|
+
result = asyncio.run(self._saga(steps, input_data))
|
|
589
|
+
return json.dumps(result, default=str)
|
|
590
|
+
|
|
591
|
+
async def _saga(self, steps: str, input_data: str) -> Dict[str, Any]:
|
|
592
|
+
"""Perform Saga"""
|
|
593
|
+
step_configs = json.loads(steps)
|
|
594
|
+
data = json.loads(input_data)
|
|
595
|
+
|
|
596
|
+
saga = SagaWorkflow("langchain_saga")
|
|
597
|
+
for i, cfg in enumerate(step_configs):
|
|
598
|
+
action_code = cfg.get("action", "x")
|
|
599
|
+
compensate_code = cfg.get("compensate", "x")
|
|
600
|
+
|
|
601
|
+
def make_action(code):
|
|
602
|
+
return lambda d: self.runtime.eval(f'let x = {_ironshell_value(d)}; {code}')
|
|
603
|
+
|
|
604
|
+
saga.add_saga_step(f"step_{i}", make_action(action_code), make_action(compensate_code))
|
|
605
|
+
|
|
606
|
+
result = await saga.run(data)
|
|
607
|
+
return {"success": result.success, "result": result.result, "steps_completed": result.steps_completed}
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
# ============================================================================
|
|
611
|
+
# Metrics Tools
|
|
612
|
+
# ============================================================================
|
|
613
|
+
|
|
614
|
+
class MetricsInput(BaseModel):
|
|
615
|
+
"""Input for metrics operations"""
|
|
616
|
+
operation: str = Field(description="Operation: 'get', 'increment', 'set', 'record', 'export'")
|
|
617
|
+
metric_name: str = Field(description="Name of the metric")
|
|
618
|
+
value: Optional[float] = Field(default=None, description="Value for set/record operations")
|
|
619
|
+
metric_type: str = Field(default="counter", description="Type: 'counter', 'gauge', 'histogram'")
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
class IronShellMetricsTool(BaseTool):
|
|
623
|
+
"""
|
|
624
|
+
Access IronShell metrics for monitoring.
|
|
625
|
+
|
|
626
|
+
Monitor and observe agent/workflow performance.
|
|
627
|
+
"""
|
|
628
|
+
name: str = "ironshell_metrics"
|
|
629
|
+
description: str = """Access metrics for monitoring.
|
|
630
|
+
Operations:
|
|
631
|
+
- 'get': Get current metric value
|
|
632
|
+
- 'increment': Increment a counter
|
|
633
|
+
- 'set': Set a gauge value
|
|
634
|
+
- 'record': Record histogram observation
|
|
635
|
+
- 'export': Export all metrics (Prometheus format)
|
|
636
|
+
"""
|
|
637
|
+
args_schema: Type[BaseModel] = MetricsInput
|
|
638
|
+
collector: MetricsCollector = None
|
|
639
|
+
|
|
640
|
+
def __init__(self, collector: Optional[MetricsCollector] = None, **kwargs):
|
|
641
|
+
super().__init__(**kwargs)
|
|
642
|
+
self.collector = collector or get_metrics_collector()
|
|
643
|
+
|
|
644
|
+
def _run(
|
|
645
|
+
self,
|
|
646
|
+
operation: str,
|
|
647
|
+
metric_name: str,
|
|
648
|
+
value: Optional[float] = None,
|
|
649
|
+
metric_type: str = "counter",
|
|
650
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
651
|
+
) -> str:
|
|
652
|
+
"""Execute metrics operation"""
|
|
653
|
+
if operation == "get":
|
|
654
|
+
if metric_type == "counter":
|
|
655
|
+
counter = self.collector.counter(metric_name)
|
|
656
|
+
return json.dumps({"name": metric_name, "value": counter.value})
|
|
657
|
+
elif metric_type == "gauge":
|
|
658
|
+
gauge = self.collector.gauge(metric_name)
|
|
659
|
+
return json.dumps({"name": metric_name, "value": gauge.value})
|
|
660
|
+
elif metric_type == "histogram":
|
|
661
|
+
hist = self.collector.histogram(metric_name)
|
|
662
|
+
return json.dumps({"name": metric_name, "count": hist.count, "sum": hist.sum, "mean": hist.mean})
|
|
663
|
+
|
|
664
|
+
elif operation == "increment":
|
|
665
|
+
counter = self.collector.counter(metric_name)
|
|
666
|
+
counter.inc(value or 1.0)
|
|
667
|
+
return json.dumps({"name": metric_name, "value": counter.value})
|
|
668
|
+
|
|
669
|
+
elif operation == "set":
|
|
670
|
+
gauge = self.collector.gauge(metric_name)
|
|
671
|
+
gauge.set(value or 0.0)
|
|
672
|
+
return json.dumps({"name": metric_name, "value": gauge.value})
|
|
673
|
+
|
|
674
|
+
elif operation == "record":
|
|
675
|
+
hist = self.collector.histogram(metric_name)
|
|
676
|
+
hist.observe(value or 0.0)
|
|
677
|
+
return json.dumps({"name": metric_name, "recorded": value})
|
|
678
|
+
|
|
679
|
+
elif operation == "export":
|
|
680
|
+
return self.collector.to_prometheus()
|
|
681
|
+
|
|
682
|
+
return json.dumps({"error": f"Unknown operation: {operation}"})
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class TracingInput(BaseModel):
|
|
686
|
+
"""Input for tracing operations"""
|
|
687
|
+
operation: str = Field(description="Operation: 'get_trace', 'clear'")
|
|
688
|
+
trace_id: Optional[str] = Field(default=None, description="Trace ID to query")
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class IronShellTracingTool(BaseTool):
|
|
692
|
+
"""
|
|
693
|
+
Distributed tracing for debugging.
|
|
694
|
+
"""
|
|
695
|
+
name: str = "ironshell_tracing"
|
|
696
|
+
description: str = """Manage distributed traces.
|
|
697
|
+
Operations:
|
|
698
|
+
- 'get_trace': Get all spans (JSON format)
|
|
699
|
+
- 'clear': Clear recorded spans
|
|
700
|
+
"""
|
|
701
|
+
args_schema: Type[BaseModel] = TracingInput
|
|
702
|
+
tracer: Tracer = None
|
|
703
|
+
|
|
704
|
+
def __init__(self, tracer: Optional[Tracer] = None, **kwargs):
|
|
705
|
+
super().__init__(**kwargs)
|
|
706
|
+
self.tracer = tracer or get_metrics_collector().tracer()
|
|
707
|
+
|
|
708
|
+
def _run(
|
|
709
|
+
self,
|
|
710
|
+
operation: str,
|
|
711
|
+
trace_id: Optional[str] = None,
|
|
712
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
713
|
+
) -> str:
|
|
714
|
+
"""Execute tracing operation"""
|
|
715
|
+
if operation == "get_trace":
|
|
716
|
+
spans = self.tracer.get_spans()
|
|
717
|
+
if trace_id:
|
|
718
|
+
spans = [s for s in spans if s.trace_id == trace_id]
|
|
719
|
+
return json.dumps([s.to_dict() for s in spans], default=str)
|
|
720
|
+
elif operation == "clear":
|
|
721
|
+
self.tracer.clear_spans()
|
|
722
|
+
return json.dumps({"status": "cleared"})
|
|
723
|
+
return json.dumps({"error": f"Unknown operation: {operation}"})
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
# ============================================================================
|
|
727
|
+
# Distributed Agent Tools
|
|
728
|
+
# ============================================================================
|
|
729
|
+
|
|
730
|
+
class DistributedAgentInput(BaseModel):
|
|
731
|
+
"""Input for distributed agent operations"""
|
|
732
|
+
goal: str = Field(description="Goal for the agent(s)")
|
|
733
|
+
agent_name: Optional[str] = Field(default=None, description="Specific agent to use")
|
|
734
|
+
capability: Optional[str] = Field(default=None, description="Required capability")
|
|
735
|
+
broadcast: bool = Field(default=False, description="Broadcast to all agents")
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class IronShellDistributedAgentTool(BaseTool):
|
|
739
|
+
"""
|
|
740
|
+
Run distributed AI agents.
|
|
741
|
+
|
|
742
|
+
Routes tasks to agents across a cluster.
|
|
743
|
+
"""
|
|
744
|
+
name: str = "ironshell_distributed_agent"
|
|
745
|
+
description: str = """Run a task on distributed agents.
|
|
746
|
+
Can route by:
|
|
747
|
+
- agent_name: Specific agent
|
|
748
|
+
- capability: Any agent with that capability
|
|
749
|
+
- broadcast=True: All agents
|
|
750
|
+
"""
|
|
751
|
+
args_schema: Type[BaseModel] = DistributedAgentInput
|
|
752
|
+
swarm: DistributedSwarm = None
|
|
753
|
+
|
|
754
|
+
def __init__(self, swarm: Optional[DistributedSwarm] = None, registry: Optional[ServiceRegistry] = None, **kwargs):
|
|
755
|
+
super().__init__(**kwargs)
|
|
756
|
+
if swarm:
|
|
757
|
+
self.swarm = swarm
|
|
758
|
+
else:
|
|
759
|
+
registry = registry or ServiceRegistry()
|
|
760
|
+
self.swarm = DistributedSwarm("langchain_swarm", registry)
|
|
761
|
+
|
|
762
|
+
def _run(
|
|
763
|
+
self,
|
|
764
|
+
goal: str,
|
|
765
|
+
agent_name: Optional[str] = None,
|
|
766
|
+
capability: Optional[str] = None,
|
|
767
|
+
broadcast: bool = False,
|
|
768
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
769
|
+
) -> str:
|
|
770
|
+
"""Execute distributed agent task"""
|
|
771
|
+
result = asyncio.run(self._dispatch(goal, agent_name, capability, broadcast))
|
|
772
|
+
return json.dumps(result, default=str)
|
|
773
|
+
|
|
774
|
+
async def _arun(
|
|
775
|
+
self,
|
|
776
|
+
goal: str,
|
|
777
|
+
agent_name: Optional[str] = None,
|
|
778
|
+
capability: Optional[str] = None,
|
|
779
|
+
broadcast: bool = False,
|
|
780
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
781
|
+
) -> str:
|
|
782
|
+
"""Execute distributed agent task asynchronously"""
|
|
783
|
+
result = await self._dispatch(goal, agent_name, capability, broadcast)
|
|
784
|
+
return json.dumps(result, default=str)
|
|
785
|
+
|
|
786
|
+
async def _dispatch(self, goal: str, agent_name: Optional[str], capability: Optional[str], broadcast: bool) -> Any:
|
|
787
|
+
"""Dispatch task to agents"""
|
|
788
|
+
if broadcast:
|
|
789
|
+
return await self.swarm.broadcast(goal)
|
|
790
|
+
else:
|
|
791
|
+
return await self.swarm.dispatch(goal, agent_name=agent_name, capability=capability)
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
class ServiceRegistryInput(BaseModel):
|
|
795
|
+
"""Input for service registry operations"""
|
|
796
|
+
operation: str = Field(description="Operation: 'list', 'register', 'deregister', 'health'")
|
|
797
|
+
service_name: Optional[str] = Field(default=None, description="Service name")
|
|
798
|
+
host: Optional[str] = Field(default=None, description="Service host")
|
|
799
|
+
port: Optional[int] = Field(default=None, description="Service port")
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
class IronShellServiceRegistryTool(BaseTool):
|
|
803
|
+
"""
|
|
804
|
+
Manage the service registry.
|
|
805
|
+
"""
|
|
806
|
+
name: str = "ironshell_service_registry"
|
|
807
|
+
description: str = """Manage agent service registry.
|
|
808
|
+
Operations:
|
|
809
|
+
- 'list': List all registered services
|
|
810
|
+
- 'register': Register a new service
|
|
811
|
+
- 'deregister': Remove a service
|
|
812
|
+
- 'health': Get service health status
|
|
813
|
+
"""
|
|
814
|
+
args_schema: Type[BaseModel] = ServiceRegistryInput
|
|
815
|
+
registry: ServiceRegistry = None
|
|
816
|
+
|
|
817
|
+
def __init__(self, registry: Optional[ServiceRegistry] = None, **kwargs):
|
|
818
|
+
super().__init__(**kwargs)
|
|
819
|
+
self.registry = registry or ServiceRegistry()
|
|
820
|
+
|
|
821
|
+
def _run(
|
|
822
|
+
self,
|
|
823
|
+
operation: str,
|
|
824
|
+
service_name: Optional[str] = None,
|
|
825
|
+
host: Optional[str] = None,
|
|
826
|
+
port: Optional[int] = None,
|
|
827
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
828
|
+
) -> str:
|
|
829
|
+
"""Execute registry operation"""
|
|
830
|
+
if operation == "list":
|
|
831
|
+
services = self.registry.get_all_services(healthy_only=False)
|
|
832
|
+
return json.dumps([s.to_dict() for s in services], default=str)
|
|
833
|
+
|
|
834
|
+
elif operation == "register":
|
|
835
|
+
if not all([service_name, host, port]):
|
|
836
|
+
return json.dumps({"error": "name, host, and port required"})
|
|
837
|
+
service = self.registry.register(service_name, host, port)
|
|
838
|
+
return json.dumps(service.to_dict(), default=str)
|
|
839
|
+
|
|
840
|
+
elif operation == "deregister":
|
|
841
|
+
if not service_name:
|
|
842
|
+
return json.dumps({"error": "service_name required"})
|
|
843
|
+
services = self.registry.get_services_by_name(service_name, healthy_only=False)
|
|
844
|
+
removed = sum(1 for s in services if self.registry.deregister(s.service_id))
|
|
845
|
+
return json.dumps({"removed": removed})
|
|
846
|
+
|
|
847
|
+
elif operation == "health":
|
|
848
|
+
services = self.registry.get_all_services(healthy_only=False)
|
|
849
|
+
return json.dumps({
|
|
850
|
+
"total": len(services),
|
|
851
|
+
"healthy": sum(1 for s in services if s.is_healthy),
|
|
852
|
+
"unhealthy": sum(1 for s in services if not s.is_healthy),
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
return json.dumps({"error": f"Unknown operation: {operation}"})
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
# ============================================================================
|
|
860
|
+
# HTTP Agent API LangChain Tools
|
|
861
|
+
# ============================================================================
|
|
862
|
+
|
|
863
|
+
if LANGCHAIN_AVAILABLE:
|
|
864
|
+
|
|
865
|
+
class IronShellAgentAPIEvalTool(BaseTool):
|
|
866
|
+
"""LangChain tool for evaluating IronShell code via the HTTP Agent API."""
|
|
867
|
+
|
|
868
|
+
name: str = "ironshell_agent_api_eval"
|
|
869
|
+
description: str = (
|
|
870
|
+
"Evaluate IronShell code on a remote Agent API server. "
|
|
871
|
+
"Use this to run shell commands, data pipelines, or AI queries "
|
|
872
|
+
"via a running ish agent serve instance."
|
|
873
|
+
)
|
|
874
|
+
client: Any = None
|
|
875
|
+
|
|
876
|
+
def __init__(self, client: Optional[AgentAPIClient] = None, **kwargs):
|
|
877
|
+
super().__init__(**kwargs)
|
|
878
|
+
self.client = client or AgentAPIClient()
|
|
879
|
+
|
|
880
|
+
def _run(self, code: str) -> str:
|
|
881
|
+
try:
|
|
882
|
+
result = self.client.eval(code)
|
|
883
|
+
return json.dumps(result) if not isinstance(result, str) else result
|
|
884
|
+
except Exception as e:
|
|
885
|
+
return f"Error: {e}"
|
|
886
|
+
|
|
887
|
+
async def _arun(self, code: str) -> str:
|
|
888
|
+
return self._run(code)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
class IronShellAgentAPITool(BaseTool):
|
|
892
|
+
"""LangChain tool for interacting with the IronShell Agent API.
|
|
893
|
+
|
|
894
|
+
Supports eval, list_builtins, and schema actions.
|
|
895
|
+
"""
|
|
896
|
+
|
|
897
|
+
name: str = "ironshell_agent_api"
|
|
898
|
+
description: str = (
|
|
899
|
+
"Interact with an IronShell Agent API server. "
|
|
900
|
+
"Actions: eval (run code), list_builtins (show available builtins), "
|
|
901
|
+
'schema (get language schema). Input is JSON: {"action": "eval", "code": "..."}'
|
|
902
|
+
)
|
|
903
|
+
client: Any = None
|
|
904
|
+
|
|
905
|
+
def __init__(self, client: Optional[AgentAPIClient] = None, **kwargs):
|
|
906
|
+
super().__init__(**kwargs)
|
|
907
|
+
self.client = client or AgentAPIClient()
|
|
908
|
+
|
|
909
|
+
def _run(self, input_str: str) -> str:
|
|
910
|
+
try:
|
|
911
|
+
params = json.loads(input_str) if isinstance(input_str, str) else input_str
|
|
912
|
+
action = params.get("action", "eval")
|
|
913
|
+
if action == "eval":
|
|
914
|
+
result = self.client.eval(params.get("code", ""))
|
|
915
|
+
return json.dumps(result) if not isinstance(result, str) else result
|
|
916
|
+
elif action == "list_builtins":
|
|
917
|
+
builtins = self.client.list_builtins(params.get("category"))
|
|
918
|
+
return json.dumps(builtins)
|
|
919
|
+
elif action == "schema":
|
|
920
|
+
return self.client.get_schema(params.get("format", "compact"))
|
|
921
|
+
else:
|
|
922
|
+
return json.dumps({"error": f"Unknown action: {action}"})
|
|
923
|
+
except Exception as e:
|
|
924
|
+
return f"Error: {e}"
|
|
925
|
+
|
|
926
|
+
async def _arun(self, input_str: str) -> str:
|
|
927
|
+
return self._run(input_str)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
class IronShellAgentAPIBuiltinsTool(BaseTool):
|
|
931
|
+
"""LangChain tool for listing IronShell builtins via the Agent API."""
|
|
932
|
+
|
|
933
|
+
name: str = "ironshell_agent_api_builtins"
|
|
934
|
+
description: str = (
|
|
935
|
+
"List available IronShell builtins from the Agent API server. "
|
|
936
|
+
"Optionally filter by category (e.g., 'io', 'data', 'ai')."
|
|
937
|
+
)
|
|
938
|
+
client: Any = None
|
|
939
|
+
|
|
940
|
+
def __init__(self, client: Optional[AgentAPIClient] = None, **kwargs):
|
|
941
|
+
super().__init__(**kwargs)
|
|
942
|
+
self.client = client or AgentAPIClient()
|
|
943
|
+
|
|
944
|
+
def _run(self, category: str = "") -> str:
|
|
945
|
+
try:
|
|
946
|
+
cat = category.strip() if category else None
|
|
947
|
+
builtins = self.client.list_builtins(cat)
|
|
948
|
+
return json.dumps(builtins)
|
|
949
|
+
except Exception as e:
|
|
950
|
+
return f"Error: {e}"
|
|
951
|
+
|
|
952
|
+
async def _arun(self, category: str = "") -> str:
|
|
953
|
+
return self._run(category)
|
|
954
|
+
|
|
955
|
+
# ============================================================================
|
|
956
|
+
# Factory Functions
|
|
957
|
+
# ============================================================================
|
|
958
|
+
|
|
959
|
+
def get_ironshell_tools(runtime: Optional[IronShellRuntime] = None) -> List:
|
|
960
|
+
"""
|
|
961
|
+
Get the basic set of IronShell LangChain tools.
|
|
962
|
+
|
|
963
|
+
Args:
|
|
964
|
+
runtime: Optional IronShellRuntime instance
|
|
965
|
+
|
|
966
|
+
Returns:
|
|
967
|
+
List of LangChain tools
|
|
968
|
+
"""
|
|
969
|
+
_check_langchain()
|
|
970
|
+
runtime = runtime or IronShellRuntime()
|
|
971
|
+
return [
|
|
972
|
+
IronShellTool(runtime=runtime),
|
|
973
|
+
IronShellPipelineTool(runtime=runtime),
|
|
974
|
+
IronShellAgentTool(runtime=runtime),
|
|
975
|
+
]
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def get_all_ironshell_tools(
|
|
979
|
+
runtime: Optional[IronShellRuntime] = None,
|
|
980
|
+
metrics_collector: Optional[MetricsCollector] = None,
|
|
981
|
+
service_registry: Optional[ServiceRegistry] = None,
|
|
982
|
+
) -> List:
|
|
983
|
+
"""
|
|
984
|
+
Get all IronShell LangChain tools including advanced features.
|
|
985
|
+
|
|
986
|
+
Args:
|
|
987
|
+
runtime: Optional IronShellRuntime instance
|
|
988
|
+
metrics_collector: Optional MetricsCollector
|
|
989
|
+
service_registry: Optional ServiceRegistry
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
List of all available LangChain tools
|
|
993
|
+
"""
|
|
994
|
+
_check_langchain()
|
|
995
|
+
runtime = runtime or IronShellRuntime()
|
|
996
|
+
metrics = metrics_collector or get_metrics_collector()
|
|
997
|
+
registry = service_registry or ServiceRegistry()
|
|
998
|
+
|
|
999
|
+
return [
|
|
1000
|
+
# Basic tools
|
|
1001
|
+
IronShellTool(runtime=runtime),
|
|
1002
|
+
IronShellPipelineTool(runtime=runtime),
|
|
1003
|
+
IronShellAgentTool(runtime=runtime),
|
|
1004
|
+
# Workflow tools
|
|
1005
|
+
IronShellWorkflowTool(runtime=runtime),
|
|
1006
|
+
IronShellMapReduceTool(runtime=runtime),
|
|
1007
|
+
IronShellSagaTool(runtime=runtime),
|
|
1008
|
+
# Metrics tools
|
|
1009
|
+
IronShellMetricsTool(collector=metrics),
|
|
1010
|
+
IronShellTracingTool(tracer=metrics.tracer()),
|
|
1011
|
+
# Distributed tools
|
|
1012
|
+
IronShellDistributedAgentTool(registry=registry),
|
|
1013
|
+
IronShellServiceRegistryTool(registry=registry),
|
|
1014
|
+
]
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def create_workflow_agent(model: str = "openai:gpt-4o-mini", runtime: Optional[IronShellRuntime] = None) -> IronShellAgent:
|
|
1018
|
+
"""
|
|
1019
|
+
Create an agent configured for workflow orchestration.
|
|
1020
|
+
|
|
1021
|
+
The orchestration instructions are passed as a system prompt, which the
|
|
1022
|
+
agent builtin cannot take, so this raises ValueError until the shell gains
|
|
1023
|
+
a system prompt option.
|
|
1024
|
+
|
|
1025
|
+
Args:
|
|
1026
|
+
model: Must be the default "openai:gpt-4o-mini" (see Agent)
|
|
1027
|
+
runtime: Optional IronShellRuntime
|
|
1028
|
+
|
|
1029
|
+
Returns:
|
|
1030
|
+
Configured Agent instance
|
|
1031
|
+
|
|
1032
|
+
Raises:
|
|
1033
|
+
ValueError: always, because of the system prompt (see Agent).
|
|
1034
|
+
"""
|
|
1035
|
+
from . import Agent
|
|
1036
|
+
runtime = runtime or IronShellRuntime()
|
|
1037
|
+
|
|
1038
|
+
return Agent(
|
|
1039
|
+
name="workflow_orchestrator",
|
|
1040
|
+
model=model,
|
|
1041
|
+
system_prompt="""You are a workflow orchestration agent for IronShell.
|
|
1042
|
+
You can create and execute data pipelines, MapReduce operations, and distributed agent tasks.
|
|
1043
|
+
Prefer pipelines for sequential transformations, MapReduce for large parallel processing.""",
|
|
1044
|
+
runtime=runtime,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def get_agent_api_tools(base_url: str = "http://127.0.0.1:3002") -> List:
|
|
1049
|
+
"""
|
|
1050
|
+
Get LangChain tools that connect to a running IronShell Agent API server.
|
|
1051
|
+
|
|
1052
|
+
These tools use HTTP to communicate with ``ish agent serve`` and do not
|
|
1053
|
+
require a local IronShell binary or Python subprocess.
|
|
1054
|
+
|
|
1055
|
+
Args:
|
|
1056
|
+
base_url: Agent API server URL (default: http://127.0.0.1:3002)
|
|
1057
|
+
|
|
1058
|
+
Returns:
|
|
1059
|
+
List of LangChain tools for the Agent API
|
|
1060
|
+
|
|
1061
|
+
Example::
|
|
1062
|
+
|
|
1063
|
+
from ironshell.langchain import get_agent_api_tools
|
|
1064
|
+
tools = get_agent_api_tools("http://localhost:3002")
|
|
1065
|
+
# Use with a LangChain agent
|
|
1066
|
+
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
|
|
1067
|
+
"""
|
|
1068
|
+
_check_langchain()
|
|
1069
|
+
client = AgentAPIClient(base_url=base_url)
|
|
1070
|
+
return [
|
|
1071
|
+
IronShellAgentAPIEvalTool(client=client),
|
|
1072
|
+
IronShellAgentAPITool(client=client),
|
|
1073
|
+
IronShellAgentAPIBuiltinsTool(client=client),
|
|
1074
|
+
]
|