quantalogic 0.59.3__py3-none-any.whl → 0.61.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.
- quantalogic/agent.py +268 -24
- quantalogic/agent_config.py +5 -5
- quantalogic/agent_factory.py +2 -2
- quantalogic/codeact/__init__.py +0 -0
- quantalogic/codeact/agent.py +499 -0
- quantalogic/codeact/cli.py +232 -0
- quantalogic/codeact/constants.py +9 -0
- quantalogic/codeact/events.py +78 -0
- quantalogic/codeact/llm_util.py +76 -0
- quantalogic/codeact/prompts/error_format.j2 +11 -0
- quantalogic/codeact/prompts/generate_action.j2 +26 -0
- quantalogic/codeact/prompts/generate_program.j2 +39 -0
- quantalogic/codeact/prompts/response_format.j2 +11 -0
- quantalogic/codeact/tools_manager.py +135 -0
- quantalogic/codeact/utils.py +135 -0
- quantalogic/coding_agent.py +2 -2
- quantalogic/create_custom_agent.py +26 -78
- quantalogic/prompts/chat_system_prompt.j2 +10 -7
- quantalogic/prompts/code_2_system_prompt.j2 +190 -0
- quantalogic/prompts/code_system_prompt.j2 +142 -0
- quantalogic/prompts/doc_system_prompt.j2 +178 -0
- quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
- quantalogic/prompts/legal_system_prompt.j2 +140 -0
- quantalogic/prompts/system_prompt.j2 +6 -2
- quantalogic/prompts/tools_prompt.j2 +2 -4
- quantalogic/prompts.py +23 -4
- quantalogic/python_interpreter/__init__.py +23 -0
- quantalogic/python_interpreter/assignment_visitors.py +63 -0
- quantalogic/python_interpreter/base_visitors.py +20 -0
- quantalogic/python_interpreter/class_visitors.py +22 -0
- quantalogic/python_interpreter/comprehension_visitors.py +172 -0
- quantalogic/python_interpreter/context_visitors.py +59 -0
- quantalogic/python_interpreter/control_flow_visitors.py +88 -0
- quantalogic/python_interpreter/exception_visitors.py +109 -0
- quantalogic/python_interpreter/exceptions.py +39 -0
- quantalogic/python_interpreter/execution.py +202 -0
- quantalogic/python_interpreter/function_utils.py +386 -0
- quantalogic/python_interpreter/function_visitors.py +209 -0
- quantalogic/python_interpreter/import_visitors.py +28 -0
- quantalogic/python_interpreter/interpreter_core.py +358 -0
- quantalogic/python_interpreter/literal_visitors.py +74 -0
- quantalogic/python_interpreter/misc_visitors.py +148 -0
- quantalogic/python_interpreter/operator_visitors.py +108 -0
- quantalogic/python_interpreter/scope.py +10 -0
- quantalogic/python_interpreter/visit_handlers.py +110 -0
- quantalogic/server/agent_server.py +1 -1
- quantalogic/tools/__init__.py +6 -3
- quantalogic/tools/action_gen.py +366 -0
- quantalogic/tools/duckduckgo_search_tool.py +1 -0
- quantalogic/tools/execute_bash_command_tool.py +114 -57
- quantalogic/tools/file_tracker_tool.py +49 -0
- quantalogic/tools/google_packages/google_news_tool.py +3 -0
- quantalogic/tools/image_generation/dalle_e.py +89 -137
- quantalogic/tools/python_tool.py +13 -0
- quantalogic/tools/rag_tool/__init__.py +2 -9
- quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
- quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
- quantalogic/tools/replace_in_file_tool.py +1 -1
- quantalogic/tools/{search_definition_names.py → search_definition_names_tool.py} +2 -2
- quantalogic/tools/terminal_capture_tool.py +293 -0
- quantalogic/tools/tool.py +120 -22
- quantalogic/tools/utilities/__init__.py +2 -0
- quantalogic/tools/utilities/download_file_tool.py +3 -5
- quantalogic/tools/utilities/llm_tool.py +283 -0
- quantalogic/tools/utilities/selenium_tool.py +296 -0
- quantalogic/tools/utilities/vscode_tool.py +1 -1
- quantalogic/tools/web_navigation/__init__.py +5 -0
- quantalogic/tools/web_navigation/web_tool.py +145 -0
- quantalogic/tools/write_file_tool.py +72 -36
- quantalogic/utils/__init__.py +0 -1
- quantalogic/utils/test_python_interpreter.py +119 -0
- {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/METADATA +7 -2
- {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/RECORD +76 -35
- quantalogic/tools/rag_tool/document_metadata.py +0 -15
- quantalogic/tools/rag_tool/query_response.py +0 -20
- quantalogic/tools/rag_tool/rag_tool.py +0 -566
- quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
- quantalogic/utils/python_interpreter.py +0 -905
- {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/entry_points.txt +0 -0
quantalogic/tools/tool.py
CHANGED
@@ -38,7 +38,6 @@ class ToolArgument(BaseModel):
|
|
38
38
|
)
|
39
39
|
example: str | None = Field(default=None, description="An example value to illustrate the argument's usage.")
|
40
40
|
|
41
|
-
|
42
41
|
class ToolDefinition(BaseModel):
|
43
42
|
"""Base class for defining tool configurations without execution logic.
|
44
43
|
|
@@ -46,6 +45,7 @@ class ToolDefinition(BaseModel):
|
|
46
45
|
name: Unique name of the tool.
|
47
46
|
description: Brief description of the tool's functionality.
|
48
47
|
arguments: List of arguments the tool accepts.
|
48
|
+
return_type: The return type of the tool's execution method. Defaults to "str".
|
49
49
|
need_validation: Flag to indicate if tool requires validation.
|
50
50
|
"""
|
51
51
|
|
@@ -54,10 +54,15 @@ class ToolDefinition(BaseModel):
|
|
54
54
|
name: str = Field(..., description="The unique name of the tool.")
|
55
55
|
description: str = Field(..., description="A brief description of what the tool does.")
|
56
56
|
arguments: list[ToolArgument] = Field(default_factory=list, description="A list of arguments the tool accepts.")
|
57
|
+
return_type: str = Field(default="str", description="The return type of the tool's execution method.")
|
57
58
|
need_validation: bool = Field(
|
58
59
|
default=False,
|
59
60
|
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
60
61
|
)
|
62
|
+
need_post_process: bool = Field(
|
63
|
+
default=True,
|
64
|
+
description="When True, requires user confirmation before execution. Useful for tools that perform potentially destructive operations.",
|
65
|
+
)
|
61
66
|
need_variables: bool = Field(
|
62
67
|
default=False,
|
63
68
|
description="When True, provides access to the agent's variable store. Required for tools that need to interpolate variables (e.g., Jinja templates).",
|
@@ -81,6 +86,7 @@ class ToolDefinition(BaseModel):
|
|
81
86
|
"name",
|
82
87
|
"description",
|
83
88
|
"arguments",
|
89
|
+
"return_type",
|
84
90
|
"need_validation",
|
85
91
|
"need_variables",
|
86
92
|
"need_caller_context_memory",
|
@@ -119,10 +125,6 @@ class ToolDefinition(BaseModel):
|
|
119
125
|
parameters = ""
|
120
126
|
for arg in self.arguments:
|
121
127
|
# Skip if parameter name matches an object property with non-None value
|
122
|
-
# This enables automatic property injection during execution:
|
123
|
-
# When an object has a property matching an argument name,
|
124
|
-
# the agent will inject the property value at runtime,
|
125
|
-
# reducing manual input and improving flexibility
|
126
128
|
if properties_injectable.get(arg.name) is not None:
|
127
129
|
continue
|
128
130
|
|
@@ -180,6 +182,58 @@ class ToolDefinition(BaseModel):
|
|
180
182
|
# For ToolDefinition, it returns an empty dict since it has no execution context yet
|
181
183
|
return {}
|
182
184
|
|
185
|
+
def to_docstring(self) -> str:
|
186
|
+
"""Convert the tool definition into a Google-style docstring with function signature.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
A string formatted as a valid Python docstring representing the tool's configuration,
|
190
|
+
including the function signature and return type.
|
191
|
+
"""
|
192
|
+
# Construct the function signature
|
193
|
+
signature_parts = []
|
194
|
+
for arg in self.arguments:
|
195
|
+
# Base argument: name and type
|
196
|
+
arg_str = f"{arg.name}: {arg.arg_type}"
|
197
|
+
# Add default value if present
|
198
|
+
if arg.default is not None:
|
199
|
+
arg_str += f" = {arg.default}"
|
200
|
+
signature_parts.append(arg_str)
|
201
|
+
signature = f"def {self.name}({', '.join(signature_parts)}) -> {self.return_type}:"
|
202
|
+
|
203
|
+
# Start with the signature and description
|
204
|
+
docstring = f'"""\n{signature}\n\n{self.description}\n\n'
|
205
|
+
|
206
|
+
# Add Arguments section if there are any
|
207
|
+
if self.arguments:
|
208
|
+
docstring += "Args:\n"
|
209
|
+
for arg in self.arguments:
|
210
|
+
# Base argument line: name and type
|
211
|
+
arg_line = f" {arg.name} ({arg.arg_type})"
|
212
|
+
|
213
|
+
# Add optional/required status and default/example if present
|
214
|
+
details = []
|
215
|
+
if not arg.required:
|
216
|
+
details.append("optional")
|
217
|
+
if arg.default is not None:
|
218
|
+
details.append(f"defaults to {arg.default}")
|
219
|
+
if arg.example is not None:
|
220
|
+
details.append(f"e.g., {arg.example}")
|
221
|
+
if details:
|
222
|
+
arg_line += f" [{', '.join(details)}]"
|
223
|
+
|
224
|
+
# Add description if present
|
225
|
+
if arg.description:
|
226
|
+
arg_line += f": {arg.description}"
|
227
|
+
|
228
|
+
docstring += f"{arg_line}\n"
|
229
|
+
|
230
|
+
# Add Returns section
|
231
|
+
docstring += f"Returns:\n {self.return_type}: The result of the tool execution.\n"
|
232
|
+
|
233
|
+
# Close the docstring
|
234
|
+
docstring += '"""'
|
235
|
+
|
236
|
+
return docstring
|
183
237
|
|
184
238
|
class Tool(ToolDefinition):
|
185
239
|
"""Extended class for tools with execution capabilities.
|
@@ -257,7 +311,6 @@ class Tool(ToolDefinition):
|
|
257
311
|
properties = self.get_properties(exclude=["arguments"])
|
258
312
|
return {name: value for name, value in properties.items() if value is not None and name in argument_names}
|
259
313
|
|
260
|
-
|
261
314
|
def create_tool(func: F) -> Tool:
|
262
315
|
"""Create a Tool instance from a Python function using AST analysis.
|
263
316
|
|
@@ -330,10 +383,14 @@ def create_tool(func: F) -> Tool:
|
|
330
383
|
example=default if default else None
|
331
384
|
))
|
332
385
|
|
386
|
+
# Determine return type from type hints
|
387
|
+
return_type = type_hints.get("return", str)
|
388
|
+
return_type_str = type_map.get(return_type, "string")
|
389
|
+
|
333
390
|
# Define Tool subclass
|
334
391
|
class GeneratedTool(Tool):
|
335
392
|
def __init__(self, *args: Any, **kwargs: Any):
|
336
|
-
super().__init__(*args, name=name, description=description, arguments=arguments, **kwargs)
|
393
|
+
super().__init__(*args, name=name, description=description, arguments=arguments, return_type=return_type_str, **kwargs)
|
337
394
|
self._func = func
|
338
395
|
|
339
396
|
if is_async:
|
@@ -347,28 +404,48 @@ def create_tool(func: F) -> Tool:
|
|
347
404
|
|
348
405
|
return GeneratedTool()
|
349
406
|
|
350
|
-
|
351
407
|
if __name__ == "__main__":
|
352
|
-
|
408
|
+
# Basic tool with argument
|
409
|
+
tool = Tool(
|
410
|
+
name="my_tool",
|
411
|
+
description="A simple tool",
|
412
|
+
arguments=[ToolArgument(name="arg1", arg_type="string")]
|
413
|
+
)
|
414
|
+
print("Basic Tool Markdown:")
|
353
415
|
print(tool.to_markdown())
|
416
|
+
print("Basic Tool Docstring:")
|
417
|
+
print(tool.to_docstring())
|
418
|
+
print()
|
354
419
|
|
420
|
+
# Tool with injectable field (undefined)
|
355
421
|
class MyTool(Tool):
|
356
422
|
field1: str | None = Field(default=None, description="Field 1 description")
|
357
423
|
|
358
424
|
tool_with_fields = MyTool(
|
359
|
-
name="my_tool1",
|
425
|
+
name="my_tool1",
|
426
|
+
description="A simple tool with a field",
|
427
|
+
arguments=[ToolArgument(name="field1", arg_type="string")]
|
360
428
|
)
|
429
|
+
print("Tool with Undefined Field Markdown:")
|
361
430
|
print(tool_with_fields.to_markdown())
|
362
|
-
print(tool_with_fields.get_injectable_properties_in_execution())
|
431
|
+
print("Injectable Properties (should be empty):", tool_with_fields.get_injectable_properties_in_execution())
|
432
|
+
print("Tool with Undefined Field Docstring:")
|
433
|
+
print(tool_with_fields.to_docstring())
|
434
|
+
print()
|
363
435
|
|
436
|
+
# Tool with defined injectable field
|
364
437
|
tool_with_fields_defined = MyTool(
|
365
438
|
name="my_tool2",
|
366
|
-
description="A simple
|
439
|
+
description="A simple tool with a defined field",
|
367
440
|
field1="field1_value",
|
368
|
-
arguments=[ToolArgument(name="field1", arg_type="string")]
|
441
|
+
arguments=[ToolArgument(name="field1", arg_type="string")]
|
369
442
|
)
|
443
|
+
print("Tool with Defined Field Markdown:")
|
370
444
|
print(tool_with_fields_defined.to_markdown())
|
371
|
-
print(tool_with_fields_defined.get_injectable_properties_in_execution())
|
445
|
+
print("Injectable Properties (should include field1):", tool_with_fields_defined.get_injectable_properties_in_execution())
|
446
|
+
print("Tool with Defined Field Docstring:")
|
447
|
+
print(tool_with_fields_defined.to_docstring())
|
448
|
+
print()
|
372
449
|
|
373
450
|
# Test create_tool with synchronous function
|
374
451
|
def add(a: int, b: int = 0) -> int:
|
@@ -380,6 +457,14 @@ if __name__ == "__main__":
|
|
380
457
|
"""
|
381
458
|
return a + b
|
382
459
|
|
460
|
+
sync_tool = create_tool(add)
|
461
|
+
print("Synchronous Tool Markdown:")
|
462
|
+
print(sync_tool.to_markdown())
|
463
|
+
print("Synchronous Tool Docstring:")
|
464
|
+
print(sync_tool.to_docstring())
|
465
|
+
print("Execution result:", sync_tool.execute(a=5, b=3))
|
466
|
+
print()
|
467
|
+
|
383
468
|
# Test create_tool with asynchronous function
|
384
469
|
async def greet(name: str) -> str:
|
385
470
|
"""Greet a person.
|
@@ -390,13 +475,26 @@ if __name__ == "__main__":
|
|
390
475
|
await asyncio.sleep(0.1) # Simulate async work
|
391
476
|
return f"Hello, {name}"
|
392
477
|
|
393
|
-
# Create and test tools
|
394
|
-
sync_tool = create_tool(add)
|
395
|
-
print("\nSynchronous Tool:")
|
396
|
-
print(sync_tool.to_markdown())
|
397
|
-
print("Execution result:", sync_tool.execute(a=5, b=3))
|
398
|
-
|
399
478
|
async_tool = create_tool(greet)
|
400
|
-
print("
|
479
|
+
print("Asynchronous Tool Markdown:")
|
401
480
|
print(async_tool.to_markdown())
|
402
|
-
print("
|
481
|
+
print("Asynchronous Tool Docstring:")
|
482
|
+
print(async_tool.to_docstring())
|
483
|
+
print("Execution result:", asyncio.run(async_tool.async_execute(name="Alice")))
|
484
|
+
print()
|
485
|
+
|
486
|
+
# Comprehensive tool for to_docstring demonstration with custom return type
|
487
|
+
docstring_tool = Tool(
|
488
|
+
name="sample_tool",
|
489
|
+
description="A sample tool for testing docstring generation.",
|
490
|
+
arguments=[
|
491
|
+
ToolArgument(name="x", arg_type="int", description="The first number", required=True),
|
492
|
+
ToolArgument(name="y", arg_type="float", description="The second number", default="0.0", example="1.5"),
|
493
|
+
ToolArgument(name="verbose", arg_type="boolean", description="Print extra info", default="False")
|
494
|
+
],
|
495
|
+
return_type="int" # Custom return type
|
496
|
+
)
|
497
|
+
print("Comprehensive Tool Markdown:")
|
498
|
+
print(docstring_tool.to_markdown())
|
499
|
+
print("Comprehensive Tool Docstring with Custom Return Type:")
|
500
|
+
print(docstring_tool.to_docstring())
|
@@ -11,6 +11,7 @@ from .csv_processor_tool import CSVProcessorTool
|
|
11
11
|
from .download_file_tool import PrepareDownloadTool
|
12
12
|
from .mermaid_validator_tool import MermaidValidatorTool
|
13
13
|
from .vscode_tool import VSCodeServerTool
|
14
|
+
from .llm_tool import OrientedLLMTool
|
14
15
|
|
15
16
|
# Define __all__ to control what is imported with `from ... import *`
|
16
17
|
__all__ = [
|
@@ -18,6 +19,7 @@ __all__ = [
|
|
18
19
|
'PrepareDownloadTool',
|
19
20
|
'MermaidValidatorTool',
|
20
21
|
'VSCodeServerTool',
|
22
|
+
'OrientedLLMTool',
|
21
23
|
]
|
22
24
|
|
23
25
|
# Optional: Add logging for import confirmation
|
@@ -20,7 +20,7 @@ class PrepareDownloadTool(Tool):
|
|
20
20
|
"If it's a directory, it will be zipped. "
|
21
21
|
"Returns an HTML link for downloading."
|
22
22
|
)
|
23
|
-
need_validation: bool =
|
23
|
+
need_validation: bool = False
|
24
24
|
arguments: list = [
|
25
25
|
ToolArgument(
|
26
26
|
name="path",
|
@@ -106,10 +106,8 @@ class PrepareDownloadTool(Tool):
|
|
106
106
|
)
|
107
107
|
|
108
108
|
# Create the HTML link with embedded styles
|
109
|
-
html = f'''<a href="{url}"
|
110
|
-
style="{style}"
|
111
|
-
onmouseover="this.style.backgroundColor='#0066cc'; this.style.color='white';"
|
112
|
-
onmouseout="this.style.backgroundColor='transparent'; this.style.color='#0066cc';"
|
109
|
+
html = f'''Using this download functionnal download link : <a href="{url}"
|
110
|
+
style="{style}"
|
113
111
|
download="{filename}">{text}</a>'''
|
114
112
|
|
115
113
|
return html
|
@@ -0,0 +1,283 @@
|
|
1
|
+
"""Legal-oriented LLM Tool for generating answers using retrieved legal context."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from typing import Callable, Dict, List, Optional, Union
|
5
|
+
|
6
|
+
from loguru import logger
|
7
|
+
from pydantic import ConfigDict, Field
|
8
|
+
|
9
|
+
from quantalogic.console_print_token import console_print_token
|
10
|
+
from quantalogic.event_emitter import EventEmitter
|
11
|
+
from quantalogic.generative_model import GenerativeModel, Message
|
12
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
13
|
+
|
14
|
+
|
15
|
+
class OrientedLLMTool(Tool):
|
16
|
+
"""Advanced LLM tool specialized for legal analysis and response generation."""
|
17
|
+
|
18
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
19
|
+
|
20
|
+
name: str = Field(default="legal_llm_tool")
|
21
|
+
description: str = Field(
|
22
|
+
default=(
|
23
|
+
"A specialized legal language model tool that generates expert legal analysis and responses. "
|
24
|
+
"Features:\n"
|
25
|
+
"- Legal expertise: Specialized in legal analysis and interpretation\n"
|
26
|
+
"- Context integration: Utilizes retrieved legal documents and precedents\n"
|
27
|
+
"- Multi-jurisdictional: Handles multiple legal systems and languages\n"
|
28
|
+
"- Citation support: Properly cites legal sources and precedents\n"
|
29
|
+
"\nCapabilities:\n"
|
30
|
+
"- Legal document analysis\n"
|
31
|
+
"- Statutory interpretation\n"
|
32
|
+
"- Case law analysis\n"
|
33
|
+
"- Regulatory compliance guidance"
|
34
|
+
)
|
35
|
+
)
|
36
|
+
arguments: list = Field(
|
37
|
+
default=[
|
38
|
+
ToolArgument(
|
39
|
+
name="role",
|
40
|
+
arg_type="string",
|
41
|
+
description="Legal specialization (e.g., 'commercial_law', 'environmental_law', 'constitutional_law')",
|
42
|
+
required=True,
|
43
|
+
default="general_legal_expert",
|
44
|
+
),
|
45
|
+
ToolArgument(
|
46
|
+
name="legal_context",
|
47
|
+
arg_type="string",
|
48
|
+
description="Retrieved legal documents, precedents, and relevant context from RAG tool",
|
49
|
+
required=True,
|
50
|
+
),
|
51
|
+
ToolArgument(
|
52
|
+
name="jurisdiction",
|
53
|
+
arg_type="string",
|
54
|
+
description="Relevant legal jurisdiction(s) for analysis",
|
55
|
+
required=True,
|
56
|
+
default="general",
|
57
|
+
),
|
58
|
+
ToolArgument(
|
59
|
+
name="query_type",
|
60
|
+
arg_type="string",
|
61
|
+
description="Type of legal analysis needed (e.g., 'interpretation', 'compliance', 'comparison')",
|
62
|
+
required=True,
|
63
|
+
default="interpretation",
|
64
|
+
),
|
65
|
+
ToolArgument(
|
66
|
+
name="prompt",
|
67
|
+
arg_type="string",
|
68
|
+
description="Specific legal question or analysis request",
|
69
|
+
required=True,
|
70
|
+
),
|
71
|
+
ToolArgument(
|
72
|
+
name="temperature",
|
73
|
+
arg_type="float",
|
74
|
+
description="Response precision (0.0 for strict legal interpretation, 1.0 for creative analysis)",
|
75
|
+
required=False,
|
76
|
+
default="0.3",
|
77
|
+
),
|
78
|
+
]
|
79
|
+
)
|
80
|
+
|
81
|
+
model_name: str = Field(..., description="The name of the language model to use")
|
82
|
+
role: str = Field(default="general_legal_expert", description="The specific role or persona for the LLM")
|
83
|
+
jurisdiction: str = Field(default="general", description="The relevant jurisdiction for the LLM")
|
84
|
+
on_token: Callable | None = Field(default=None, exclude=True)
|
85
|
+
generative_model: GenerativeModel | None = Field(default=None, exclude=True)
|
86
|
+
event_emitter: EventEmitter | None = Field(default=None, exclude=True)
|
87
|
+
|
88
|
+
def __init__(
|
89
|
+
self,
|
90
|
+
model_name: str,
|
91
|
+
name: str = "legal_llm_tool",
|
92
|
+
role: str = "general_legal_expert",
|
93
|
+
jurisdiction: str = "general",
|
94
|
+
on_token: Optional[Callable] = None,
|
95
|
+
event_emitter: Optional[EventEmitter] = None,
|
96
|
+
):
|
97
|
+
"""Initialize the Legal LLM Tool.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
model_name: Name of the language model
|
101
|
+
role: Legal specialization role
|
102
|
+
jurisdiction: Default jurisdiction
|
103
|
+
on_token: Optional callback for token streaming
|
104
|
+
event_emitter: Optional event emitter for streaming
|
105
|
+
"""
|
106
|
+
super().__init__(
|
107
|
+
model_name=model_name,
|
108
|
+
name=name,
|
109
|
+
role=role,
|
110
|
+
jurisdiction=jurisdiction,
|
111
|
+
on_token=on_token,
|
112
|
+
event_emitter=event_emitter,
|
113
|
+
)
|
114
|
+
|
115
|
+
self.model_name = model_name
|
116
|
+
self.role = role
|
117
|
+
self.jurisdiction = jurisdiction
|
118
|
+
self.on_token = on_token
|
119
|
+
self.event_emitter = event_emitter
|
120
|
+
self.generative_model = None
|
121
|
+
|
122
|
+
# Initialize the model
|
123
|
+
self.model_post_init(None)
|
124
|
+
|
125
|
+
def model_post_init(self, __context):
|
126
|
+
"""Initialize the generative model with legal-specific configuration."""
|
127
|
+
if self.generative_model is None:
|
128
|
+
self.generative_model = GenerativeModel(
|
129
|
+
model=self.model_name,
|
130
|
+
event_emitter=self.event_emitter
|
131
|
+
)
|
132
|
+
logger.debug(f"Initialized Legal LLM Tool with model: {self.model_name}")
|
133
|
+
|
134
|
+
if self.on_token is not None:
|
135
|
+
self.generative_model.event_emitter.on("stream_chunk", self.on_token)
|
136
|
+
|
137
|
+
def _build_legal_system_prompt(
|
138
|
+
self,
|
139
|
+
role: str,
|
140
|
+
jurisdiction: str,
|
141
|
+
query_type: str,
|
142
|
+
legal_context: Union[str, Dict, List]
|
143
|
+
) -> str:
|
144
|
+
"""Build a specialized legal system prompt.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
role: Legal specialization role
|
148
|
+
jurisdiction: Relevant jurisdiction
|
149
|
+
query_type: Type of legal analysis
|
150
|
+
legal_context: Retrieved legal context from RAG
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
Structured system prompt for legal analysis
|
154
|
+
"""
|
155
|
+
# Format legal context if it's not a string
|
156
|
+
if not isinstance(legal_context, str):
|
157
|
+
legal_context = str(legal_context)
|
158
|
+
|
159
|
+
system_prompt = f"""You are an expert legal advisor specialized in {role}.
|
160
|
+
Jurisdiction: {jurisdiction}
|
161
|
+
Analysis Type: {query_type}
|
162
|
+
|
163
|
+
Your task is to provide a well-reasoned legal analysis based on the following context:
|
164
|
+
|
165
|
+
{legal_context}
|
166
|
+
|
167
|
+
Guidelines:
|
168
|
+
1. Base your analysis strictly on provided legal sources
|
169
|
+
2. Cite specific articles, sections, and precedents
|
170
|
+
3. Consider jurisdictional context and limitations
|
171
|
+
4. Highlight any legal uncertainties or ambiguities
|
172
|
+
5. Provide clear, actionable conclusions
|
173
|
+
|
174
|
+
Format your response as follows:
|
175
|
+
1. Legal Analysis
|
176
|
+
2. Relevant Citations
|
177
|
+
3. Key Considerations
|
178
|
+
4. Conclusion
|
179
|
+
|
180
|
+
Remember: If a legal point is not supported by the provided context, acknowledge the limitation."""
|
181
|
+
|
182
|
+
return system_prompt
|
183
|
+
|
184
|
+
async def async_execute(
|
185
|
+
self,
|
186
|
+
prompt: str,
|
187
|
+
legal_context: Union[str, Dict, List],
|
188
|
+
role: Optional[str] = None,
|
189
|
+
jurisdiction: Optional[str] = None,
|
190
|
+
query_type: str = "interpretation",
|
191
|
+
temperature: float = 0.3
|
192
|
+
) -> str:
|
193
|
+
"""Execute legal analysis asynchronously.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
prompt: Legal question or analysis request
|
197
|
+
legal_context: Retrieved legal documents and context
|
198
|
+
role: Optional override for legal specialization
|
199
|
+
jurisdiction: Optional override for jurisdiction
|
200
|
+
query_type: Type of legal analysis needed
|
201
|
+
temperature: Response precision (0.0-1.0)
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
Detailed legal analysis and response
|
205
|
+
"""
|
206
|
+
try:
|
207
|
+
if not (0.0 <= temperature <= 1.0):
|
208
|
+
raise ValueError("Temperature must be between 0 and 1")
|
209
|
+
|
210
|
+
used_role = role or self.role
|
211
|
+
used_jurisdiction = jurisdiction or self.jurisdiction
|
212
|
+
|
213
|
+
system_prompt = self._build_legal_system_prompt(
|
214
|
+
used_role,
|
215
|
+
used_jurisdiction,
|
216
|
+
query_type,
|
217
|
+
legal_context
|
218
|
+
)
|
219
|
+
|
220
|
+
messages = [
|
221
|
+
Message(role="system", content=system_prompt),
|
222
|
+
Message(role="user", content=prompt)
|
223
|
+
]
|
224
|
+
|
225
|
+
if self.generative_model:
|
226
|
+
self.generative_model.temperature = temperature
|
227
|
+
|
228
|
+
is_streaming = self.on_token is not None
|
229
|
+
result = await self.generative_model.async_generate_with_history(
|
230
|
+
messages_history=messages,
|
231
|
+
prompt=prompt,
|
232
|
+
streaming=is_streaming
|
233
|
+
)
|
234
|
+
|
235
|
+
if is_streaming:
|
236
|
+
response = ""
|
237
|
+
async for chunk in result:
|
238
|
+
response += chunk
|
239
|
+
else:
|
240
|
+
response = result.response
|
241
|
+
|
242
|
+
logger.info(f"Generated legal analysis for {query_type} query in {used_jurisdiction} jurisdiction")
|
243
|
+
return response
|
244
|
+
else:
|
245
|
+
raise ValueError("Generative model not initialized")
|
246
|
+
|
247
|
+
except Exception as e:
|
248
|
+
logger.error(f"Error in legal analysis: {str(e)}")
|
249
|
+
raise
|
250
|
+
|
251
|
+
def execute(self, *args, **kwargs) -> str:
|
252
|
+
"""Synchronous wrapper for async_execute."""
|
253
|
+
return asyncio.run(self.async_execute(*args, **kwargs))
|
254
|
+
|
255
|
+
|
256
|
+
if __name__ == "__main__":
|
257
|
+
# Example usage of OrientedLLMTool
|
258
|
+
tool = OrientedLLMTool(model_name="openrouter/openai/gpt-4o-mini")
|
259
|
+
legal_context = "Retrieved legal documents and context from RAG tool"
|
260
|
+
question = "What is the meaning of life?"
|
261
|
+
temperature = 0.7
|
262
|
+
|
263
|
+
# Synchronous execution
|
264
|
+
answer = tool.execute(prompt=question, legal_context=legal_context, temperature=temperature)
|
265
|
+
print("Synchronous Answer:")
|
266
|
+
print(answer)
|
267
|
+
|
268
|
+
# Asynchronous execution with streaming
|
269
|
+
pirate = OrientedLLMTool(
|
270
|
+
model_name="openrouter/openai/gpt-4o-mini", on_token=console_print_token
|
271
|
+
)
|
272
|
+
pirate_answer = asyncio.run(
|
273
|
+
pirate.async_execute(prompt=question, legal_context=legal_context, temperature=temperature)
|
274
|
+
)
|
275
|
+
print("\nAsynchronous Pirate Answer:")
|
276
|
+
print(f"Answer: {pirate_answer}")
|
277
|
+
|
278
|
+
# Display tool configuration in Markdown
|
279
|
+
custom_tool = OrientedLLMTool(
|
280
|
+
model_name="openrouter/openai/gpt-4o-mini", on_token=console_print_token
|
281
|
+
)
|
282
|
+
print("\nTool Configuration:")
|
283
|
+
print(custom_tool.to_markdown())
|