rakam-systems-agent 0.1.1rc7__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.
@@ -0,0 +1,605 @@
1
+ """
2
+ LLM Gateway tools for calling generation functions through the tool system.
3
+
4
+ These tools allow agents to use LLM generation capabilities as tools,
5
+ enabling meta-reasoning, delegation, and multi-model workflows.
6
+ """
7
+ from __future__ import annotations
8
+ import json
9
+ from typing import Any, Dict, Optional, Type
10
+ from pydantic import BaseModel
11
+
12
+ from rakam_systems_core.ai_core.interfaces.llm_gateway import LLMGateway, LLMRequest, LLMResponse
13
+ from rakam_system_agent.components.llm_gateway import get_llm_gateway
14
+
15
+
16
+ # === LLM Gateway Tool Functions ===
17
+
18
+ async def llm_generate(
19
+ user_prompt: str,
20
+ system_prompt: Optional[str] = None,
21
+ model: Optional[str] = None,
22
+ temperature: Optional[float] = None,
23
+ max_tokens: Optional[int] = None,
24
+ ) -> Dict[str, Any]:
25
+ """
26
+ Generate text using an LLM through the gateway.
27
+
28
+ This tool allows agents to call LLM generation as a tool, enabling:
29
+ - Multi-step reasoning
30
+ - Delegation to specialized models
31
+ - Meta-reasoning workflows
32
+
33
+ Args:
34
+ user_prompt: The main prompt/question for the LLM
35
+ system_prompt: Optional system prompt to set context/behavior
36
+ model: Optional model string (e.g., "openai:gpt-4o", "mistral:mistral-large-latest")
37
+ temperature: Optional temperature for generation (0.0-1.0)
38
+ max_tokens: Optional maximum tokens to generate
39
+
40
+ Returns:
41
+ Dictionary containing:
42
+ - content: The generated text
43
+ - model: Model used for generation
44
+ - usage: Token usage information
45
+ - finish_reason: Why generation stopped
46
+ """
47
+ # Create gateway with specified model or use default
48
+ gateway = get_llm_gateway(model=model, temperature=temperature)
49
+
50
+ # Create request
51
+ request = LLMRequest(
52
+ system_prompt=system_prompt,
53
+ user_prompt=user_prompt,
54
+ temperature=temperature,
55
+ max_tokens=max_tokens,
56
+ )
57
+
58
+ # Generate response
59
+ response = gateway.generate(request)
60
+
61
+ # Return structured result
62
+ return {
63
+ "content": response.content,
64
+ "model": response.model,
65
+ "usage": response.usage,
66
+ "finish_reason": response.finish_reason,
67
+ "metadata": response.metadata,
68
+ }
69
+
70
+
71
+ async def llm_generate_structured(
72
+ user_prompt: str,
73
+ schema: Dict[str, Any],
74
+ system_prompt: Optional[str] = None,
75
+ model: Optional[str] = None,
76
+ temperature: Optional[float] = None,
77
+ max_tokens: Optional[int] = None,
78
+ ) -> Dict[str, Any]:
79
+ """
80
+ Generate structured output using an LLM through the gateway.
81
+
82
+ This tool allows agents to request structured data from LLMs,
83
+ ensuring responses conform to a specific schema.
84
+
85
+ Args:
86
+ user_prompt: The main prompt/question for the LLM
87
+ schema: JSON schema defining the expected output structure
88
+ system_prompt: Optional system prompt to set context/behavior
89
+ model: Optional model string (e.g., "openai:gpt-4o")
90
+ temperature: Optional temperature for generation (0.0-1.0)
91
+ max_tokens: Optional maximum tokens to generate
92
+
93
+ Returns:
94
+ Dictionary containing:
95
+ - structured_output: The parsed structured output
96
+ - raw_content: The raw text response
97
+ - model: Model used for generation
98
+ - usage: Token usage information
99
+ """
100
+ # Create gateway with specified model or use default
101
+ gateway = get_llm_gateway(model=model, temperature=temperature)
102
+
103
+ # Create a dynamic Pydantic model from the schema
104
+ # For now, we'll use JSON mode and parse the response
105
+ request = LLMRequest(
106
+ system_prompt=system_prompt,
107
+ user_prompt=user_prompt,
108
+ temperature=temperature,
109
+ max_tokens=max_tokens,
110
+ response_format="json",
111
+ )
112
+
113
+ # Generate response
114
+ response = gateway.generate(request)
115
+
116
+ # Try to parse as JSON
117
+ try:
118
+ structured_output = json.loads(response.content)
119
+ except json.JSONDecodeError:
120
+ structured_output = {
121
+ "error": "Failed to parse structured output", "raw": response.content}
122
+
123
+ # Return structured result
124
+ return {
125
+ "structured_output": structured_output,
126
+ "raw_content": response.content,
127
+ "model": response.model,
128
+ "usage": response.usage,
129
+ }
130
+
131
+
132
+ async def llm_count_tokens(
133
+ text: str,
134
+ model: Optional[str] = None,
135
+ ) -> Dict[str, Any]:
136
+ """
137
+ Count tokens in text using the LLM gateway's tokenizer.
138
+
139
+ Useful for:
140
+ - Checking prompt lengths before generation
141
+ - Estimating costs
142
+ - Managing context windows
143
+
144
+ Args:
145
+ text: Text to count tokens for
146
+ model: Optional model string to use for tokenization
147
+
148
+ Returns:
149
+ Dictionary containing:
150
+ - token_count: Number of tokens in the text
151
+ - model: Model used for tokenization
152
+ - text_length: Character length of text
153
+ """
154
+ # Create gateway
155
+ gateway = get_llm_gateway(model=model)
156
+
157
+ # Count tokens
158
+ token_count = gateway.count_tokens(text, model=gateway.model)
159
+
160
+ return {
161
+ "token_count": token_count,
162
+ "model": gateway.model,
163
+ "text_length": len(text),
164
+ }
165
+
166
+
167
+ async def llm_multi_model_generate(
168
+ user_prompt: str,
169
+ models: list[str],
170
+ system_prompt: Optional[str] = None,
171
+ temperature: Optional[float] = None,
172
+ max_tokens: Optional[int] = None,
173
+ ) -> Dict[str, Any]:
174
+ """
175
+ Generate responses from multiple models in parallel.
176
+
177
+ This tool enables:
178
+ - Comparing outputs across models
179
+ - Consensus building
180
+ - Model ensemble approaches
181
+
182
+ Args:
183
+ user_prompt: The main prompt/question for the LLMs
184
+ models: List of model strings (e.g., ["openai:gpt-4o", "mistral:mistral-large-latest"])
185
+ system_prompt: Optional system prompt to set context/behavior
186
+ temperature: Optional temperature for generation (0.0-1.0)
187
+ max_tokens: Optional maximum tokens to generate
188
+
189
+ Returns:
190
+ Dictionary containing:
191
+ - responses: List of responses from each model
192
+ - model_count: Number of models queried
193
+ """
194
+ import asyncio
195
+
196
+ async def generate_from_model(model_string: str) -> Dict[str, Any]:
197
+ """Helper to generate from a single model."""
198
+ gateway = get_llm_gateway(model=model_string, temperature=temperature)
199
+
200
+ request = LLMRequest(
201
+ system_prompt=system_prompt,
202
+ user_prompt=user_prompt,
203
+ temperature=temperature,
204
+ max_tokens=max_tokens,
205
+ )
206
+
207
+ response = gateway.generate(request)
208
+
209
+ return {
210
+ "model": response.model,
211
+ "content": response.content,
212
+ "usage": response.usage,
213
+ "finish_reason": response.finish_reason,
214
+ }
215
+
216
+ # Generate from all models in parallel
217
+ tasks = [generate_from_model(model) for model in models]
218
+ responses = await asyncio.gather(*tasks)
219
+
220
+ return {
221
+ "responses": responses,
222
+ "model_count": len(models),
223
+ }
224
+
225
+
226
+ async def llm_summarize(
227
+ text: str,
228
+ model: Optional[str] = None,
229
+ max_length: Optional[int] = None,
230
+ ) -> Dict[str, Any]:
231
+ """
232
+ Summarize text using an LLM.
233
+
234
+ A convenience tool for text summarization.
235
+
236
+ Args:
237
+ text: Text to summarize
238
+ model: Optional model string
239
+ max_length: Optional maximum length for summary in words
240
+
241
+ Returns:
242
+ Dictionary containing:
243
+ - summary: The generated summary
244
+ - original_length: Length of original text
245
+ - summary_length: Length of summary
246
+ - model: Model used
247
+ """
248
+ gateway = get_llm_gateway(model=model)
249
+
250
+ # Build prompt
251
+ length_instruction = f" Keep it under {max_length} words." if max_length else ""
252
+ system_prompt = "You are a helpful assistant that creates concise, accurate summaries."
253
+ user_prompt = f"Please summarize the following text:{length_instruction}\n\n{text}"
254
+
255
+ request = LLMRequest(
256
+ system_prompt=system_prompt,
257
+ user_prompt=user_prompt,
258
+ )
259
+
260
+ response = gateway.generate(request)
261
+
262
+ return {
263
+ "summary": response.content,
264
+ "original_length": len(text.split()),
265
+ "summary_length": len(response.content.split()),
266
+ "model": response.model,
267
+ "usage": response.usage,
268
+ }
269
+
270
+
271
+ async def llm_extract_entities(
272
+ text: str,
273
+ entity_types: Optional[list[str]] = None,
274
+ model: Optional[str] = None,
275
+ ) -> Dict[str, Any]:
276
+ """
277
+ Extract named entities from text using an LLM.
278
+
279
+ Args:
280
+ text: Text to extract entities from
281
+ entity_types: Optional list of entity types to extract (e.g., ["person", "organization", "location"])
282
+ model: Optional model string
283
+
284
+ Returns:
285
+ Dictionary containing:
286
+ - entities: Extracted entities
287
+ - model: Model used
288
+ """
289
+ gateway = get_llm_gateway(model=model)
290
+
291
+ # Build prompt
292
+ if entity_types:
293
+ types_str = ", ".join(entity_types)
294
+ entity_instruction = f" Focus on these entity types: {types_str}."
295
+ else:
296
+ entity_instruction = ""
297
+
298
+ system_prompt = "You are a helpful assistant that extracts named entities from text. Return results as JSON."
299
+ user_prompt = f"Extract named entities from the following text.{entity_instruction} Return as JSON with entity type as keys and lists of entities as values.\n\nText: {text}"
300
+
301
+ request = LLMRequest(
302
+ system_prompt=system_prompt,
303
+ user_prompt=user_prompt,
304
+ response_format="json",
305
+ )
306
+
307
+ response = gateway.generate(request)
308
+
309
+ # Try to parse as JSON
310
+ try:
311
+ entities = json.loads(response.content)
312
+ except json.JSONDecodeError:
313
+ entities = {"error": "Failed to parse entities",
314
+ "raw": response.content}
315
+
316
+ return {
317
+ "entities": entities,
318
+ "model": response.model,
319
+ "usage": response.usage,
320
+ }
321
+
322
+
323
+ async def llm_translate(
324
+ text: str,
325
+ target_language: str,
326
+ source_language: Optional[str] = None,
327
+ model: Optional[str] = None,
328
+ ) -> Dict[str, Any]:
329
+ """
330
+ Translate text using an LLM.
331
+
332
+ Args:
333
+ text: Text to translate
334
+ target_language: Target language (e.g., "Spanish", "French", "German")
335
+ source_language: Optional source language (auto-detected if not specified)
336
+ model: Optional model string
337
+
338
+ Returns:
339
+ Dictionary containing:
340
+ - translation: The translated text
341
+ - source_language: Source language used
342
+ - target_language: Target language
343
+ - model: Model used
344
+ """
345
+ gateway = get_llm_gateway(model=model)
346
+
347
+ # Build prompt
348
+ source_instruction = f" from {source_language}" if source_language else ""
349
+ system_prompt = "You are a helpful assistant that translates text accurately."
350
+ user_prompt = f"Translate the following text{source_instruction} to {target_language}:\n\n{text}"
351
+
352
+ request = LLMRequest(
353
+ system_prompt=system_prompt,
354
+ user_prompt=user_prompt,
355
+ )
356
+
357
+ response = gateway.generate(request)
358
+
359
+ return {
360
+ "translation": response.content,
361
+ "source_language": source_language or "auto-detected",
362
+ "target_language": target_language,
363
+ "model": response.model,
364
+ "usage": response.usage,
365
+ }
366
+
367
+
368
+ # === Helper Functions for Tool Registration ===
369
+
370
+ def get_all_llm_gateway_tools() -> list[Dict[str, Any]]:
371
+ """
372
+ Get configuration for all LLM gateway tools.
373
+
374
+ Returns:
375
+ List of tool configuration dictionaries ready for registration
376
+ """
377
+ return [
378
+ {
379
+ "name": "llm_generate",
380
+ "function": llm_generate,
381
+ "description": "Generate text using an LLM through the gateway. Enables multi-step reasoning and delegation.",
382
+ "json_schema": {
383
+ "type": "object",
384
+ "properties": {
385
+ "user_prompt": {
386
+ "type": "string",
387
+ "description": "The main prompt/question for the LLM"
388
+ },
389
+ "system_prompt": {
390
+ "type": "string",
391
+ "description": "Optional system prompt to set context/behavior"
392
+ },
393
+ "model": {
394
+ "type": "string",
395
+ "description": "Optional model string (e.g., 'openai:gpt-4o', 'mistral:mistral-large-latest')"
396
+ },
397
+ "temperature": {
398
+ "type": "number",
399
+ "description": "Optional temperature for generation (0.0-1.0)",
400
+ "minimum": 0.0,
401
+ "maximum": 1.0
402
+ },
403
+ "max_tokens": {
404
+ "type": "integer",
405
+ "description": "Optional maximum tokens to generate",
406
+ "minimum": 1
407
+ }
408
+ },
409
+ "required": ["user_prompt"],
410
+ "additionalProperties": False,
411
+ },
412
+ "category": "llm",
413
+ "tags": ["generation", "llm", "delegation"],
414
+ },
415
+ {
416
+ "name": "llm_generate_structured",
417
+ "function": llm_generate_structured,
418
+ "description": "Generate structured output using an LLM, ensuring responses conform to a schema.",
419
+ "json_schema": {
420
+ "type": "object",
421
+ "properties": {
422
+ "user_prompt": {
423
+ "type": "string",
424
+ "description": "The main prompt/question for the LLM"
425
+ },
426
+ "schema": {
427
+ "type": "object",
428
+ "description": "JSON schema defining the expected output structure"
429
+ },
430
+ "system_prompt": {
431
+ "type": "string",
432
+ "description": "Optional system prompt to set context/behavior"
433
+ },
434
+ "model": {
435
+ "type": "string",
436
+ "description": "Optional model string"
437
+ },
438
+ "temperature": {
439
+ "type": "number",
440
+ "description": "Optional temperature for generation (0.0-1.0)",
441
+ "minimum": 0.0,
442
+ "maximum": 1.0
443
+ },
444
+ "max_tokens": {
445
+ "type": "integer",
446
+ "description": "Optional maximum tokens to generate",
447
+ "minimum": 1
448
+ }
449
+ },
450
+ "required": ["user_prompt", "schema"],
451
+ "additionalProperties": False,
452
+ },
453
+ "category": "llm",
454
+ "tags": ["generation", "structured", "llm"],
455
+ },
456
+ {
457
+ "name": "llm_count_tokens",
458
+ "function": llm_count_tokens,
459
+ "description": "Count tokens in text using the LLM gateway's tokenizer. Useful for checking prompt lengths.",
460
+ "json_schema": {
461
+ "type": "object",
462
+ "properties": {
463
+ "text": {
464
+ "type": "string",
465
+ "description": "Text to count tokens for"
466
+ },
467
+ "model": {
468
+ "type": "string",
469
+ "description": "Optional model string to use for tokenization"
470
+ }
471
+ },
472
+ "required": ["text"],
473
+ "additionalProperties": False,
474
+ },
475
+ "category": "llm",
476
+ "tags": ["tokens", "utility", "llm"],
477
+ },
478
+ {
479
+ "name": "llm_multi_model_generate",
480
+ "function": llm_multi_model_generate,
481
+ "description": "Generate responses from multiple models in parallel for comparison or consensus.",
482
+ "json_schema": {
483
+ "type": "object",
484
+ "properties": {
485
+ "user_prompt": {
486
+ "type": "string",
487
+ "description": "The main prompt/question for the LLMs"
488
+ },
489
+ "models": {
490
+ "type": "array",
491
+ "description": "List of model strings to query",
492
+ "items": {
493
+ "type": "string"
494
+ },
495
+ "minItems": 1
496
+ },
497
+ "system_prompt": {
498
+ "type": "string",
499
+ "description": "Optional system prompt to set context/behavior"
500
+ },
501
+ "temperature": {
502
+ "type": "number",
503
+ "description": "Optional temperature for generation (0.0-1.0)",
504
+ "minimum": 0.0,
505
+ "maximum": 1.0
506
+ },
507
+ "max_tokens": {
508
+ "type": "integer",
509
+ "description": "Optional maximum tokens to generate",
510
+ "minimum": 1
511
+ }
512
+ },
513
+ "required": ["user_prompt", "models"],
514
+ "additionalProperties": False,
515
+ },
516
+ "category": "llm",
517
+ "tags": ["generation", "multi-model", "ensemble", "llm"],
518
+ },
519
+ {
520
+ "name": "llm_summarize",
521
+ "function": llm_summarize,
522
+ "description": "Summarize text using an LLM.",
523
+ "json_schema": {
524
+ "type": "object",
525
+ "properties": {
526
+ "text": {
527
+ "type": "string",
528
+ "description": "Text to summarize"
529
+ },
530
+ "model": {
531
+ "type": "string",
532
+ "description": "Optional model string"
533
+ },
534
+ "max_length": {
535
+ "type": "integer",
536
+ "description": "Optional maximum length for summary in words",
537
+ "minimum": 1
538
+ }
539
+ },
540
+ "required": ["text"],
541
+ "additionalProperties": False,
542
+ },
543
+ "category": "llm",
544
+ "tags": ["summarization", "nlp", "llm"],
545
+ },
546
+ {
547
+ "name": "llm_extract_entities",
548
+ "function": llm_extract_entities,
549
+ "description": "Extract named entities from text using an LLM.",
550
+ "json_schema": {
551
+ "type": "object",
552
+ "properties": {
553
+ "text": {
554
+ "type": "string",
555
+ "description": "Text to extract entities from"
556
+ },
557
+ "entity_types": {
558
+ "type": "array",
559
+ "description": "Optional list of entity types to extract",
560
+ "items": {
561
+ "type": "string"
562
+ }
563
+ },
564
+ "model": {
565
+ "type": "string",
566
+ "description": "Optional model string"
567
+ }
568
+ },
569
+ "required": ["text"],
570
+ "additionalProperties": False,
571
+ },
572
+ "category": "llm",
573
+ "tags": ["entities", "nlp", "extraction", "llm"],
574
+ },
575
+ {
576
+ "name": "llm_translate",
577
+ "function": llm_translate,
578
+ "description": "Translate text using an LLM.",
579
+ "json_schema": {
580
+ "type": "object",
581
+ "properties": {
582
+ "text": {
583
+ "type": "string",
584
+ "description": "Text to translate"
585
+ },
586
+ "target_language": {
587
+ "type": "string",
588
+ "description": "Target language (e.g., 'Spanish', 'French', 'German')"
589
+ },
590
+ "source_language": {
591
+ "type": "string",
592
+ "description": "Optional source language (auto-detected if not specified)"
593
+ },
594
+ "model": {
595
+ "type": "string",
596
+ "description": "Optional model string"
597
+ }
598
+ },
599
+ "required": ["text", "target_language"],
600
+ "additionalProperties": False,
601
+ },
602
+ "category": "llm",
603
+ "tags": ["translation", "nlp", "llm"],
604
+ },
605
+ ]
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+ from typing import Any
3
+ # from rakam_systems_core.ai_core.interfaces.tool import ToolComponent
4
+ from rakam_systems_core.ai_core.interfaces.tool import ToolComponent
5
+
6
+
7
+ class SearchTool(ToolComponent):
8
+ """Abstract search tool placeholder.
9
+ Implementors should call an external search API or local index.
10
+ """
11
+
12
+ def run(self, query: str) -> Any:
13
+ raise NotImplementedError(
14
+ "SearchTool.run must be implemented by a concrete subclass")