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.
Files changed (81) hide show
  1. quantalogic/agent.py +268 -24
  2. quantalogic/agent_config.py +5 -5
  3. quantalogic/agent_factory.py +2 -2
  4. quantalogic/codeact/__init__.py +0 -0
  5. quantalogic/codeact/agent.py +499 -0
  6. quantalogic/codeact/cli.py +232 -0
  7. quantalogic/codeact/constants.py +9 -0
  8. quantalogic/codeact/events.py +78 -0
  9. quantalogic/codeact/llm_util.py +76 -0
  10. quantalogic/codeact/prompts/error_format.j2 +11 -0
  11. quantalogic/codeact/prompts/generate_action.j2 +26 -0
  12. quantalogic/codeact/prompts/generate_program.j2 +39 -0
  13. quantalogic/codeact/prompts/response_format.j2 +11 -0
  14. quantalogic/codeact/tools_manager.py +135 -0
  15. quantalogic/codeact/utils.py +135 -0
  16. quantalogic/coding_agent.py +2 -2
  17. quantalogic/create_custom_agent.py +26 -78
  18. quantalogic/prompts/chat_system_prompt.j2 +10 -7
  19. quantalogic/prompts/code_2_system_prompt.j2 +190 -0
  20. quantalogic/prompts/code_system_prompt.j2 +142 -0
  21. quantalogic/prompts/doc_system_prompt.j2 +178 -0
  22. quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
  23. quantalogic/prompts/legal_system_prompt.j2 +140 -0
  24. quantalogic/prompts/system_prompt.j2 +6 -2
  25. quantalogic/prompts/tools_prompt.j2 +2 -4
  26. quantalogic/prompts.py +23 -4
  27. quantalogic/python_interpreter/__init__.py +23 -0
  28. quantalogic/python_interpreter/assignment_visitors.py +63 -0
  29. quantalogic/python_interpreter/base_visitors.py +20 -0
  30. quantalogic/python_interpreter/class_visitors.py +22 -0
  31. quantalogic/python_interpreter/comprehension_visitors.py +172 -0
  32. quantalogic/python_interpreter/context_visitors.py +59 -0
  33. quantalogic/python_interpreter/control_flow_visitors.py +88 -0
  34. quantalogic/python_interpreter/exception_visitors.py +109 -0
  35. quantalogic/python_interpreter/exceptions.py +39 -0
  36. quantalogic/python_interpreter/execution.py +202 -0
  37. quantalogic/python_interpreter/function_utils.py +386 -0
  38. quantalogic/python_interpreter/function_visitors.py +209 -0
  39. quantalogic/python_interpreter/import_visitors.py +28 -0
  40. quantalogic/python_interpreter/interpreter_core.py +358 -0
  41. quantalogic/python_interpreter/literal_visitors.py +74 -0
  42. quantalogic/python_interpreter/misc_visitors.py +148 -0
  43. quantalogic/python_interpreter/operator_visitors.py +108 -0
  44. quantalogic/python_interpreter/scope.py +10 -0
  45. quantalogic/python_interpreter/visit_handlers.py +110 -0
  46. quantalogic/server/agent_server.py +1 -1
  47. quantalogic/tools/__init__.py +6 -3
  48. quantalogic/tools/action_gen.py +366 -0
  49. quantalogic/tools/duckduckgo_search_tool.py +1 -0
  50. quantalogic/tools/execute_bash_command_tool.py +114 -57
  51. quantalogic/tools/file_tracker_tool.py +49 -0
  52. quantalogic/tools/google_packages/google_news_tool.py +3 -0
  53. quantalogic/tools/image_generation/dalle_e.py +89 -137
  54. quantalogic/tools/python_tool.py +13 -0
  55. quantalogic/tools/rag_tool/__init__.py +2 -9
  56. quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
  57. quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
  58. quantalogic/tools/replace_in_file_tool.py +1 -1
  59. quantalogic/tools/{search_definition_names.py → search_definition_names_tool.py} +2 -2
  60. quantalogic/tools/terminal_capture_tool.py +293 -0
  61. quantalogic/tools/tool.py +120 -22
  62. quantalogic/tools/utilities/__init__.py +2 -0
  63. quantalogic/tools/utilities/download_file_tool.py +3 -5
  64. quantalogic/tools/utilities/llm_tool.py +283 -0
  65. quantalogic/tools/utilities/selenium_tool.py +296 -0
  66. quantalogic/tools/utilities/vscode_tool.py +1 -1
  67. quantalogic/tools/web_navigation/__init__.py +5 -0
  68. quantalogic/tools/web_navigation/web_tool.py +145 -0
  69. quantalogic/tools/write_file_tool.py +72 -36
  70. quantalogic/utils/__init__.py +0 -1
  71. quantalogic/utils/test_python_interpreter.py +119 -0
  72. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/METADATA +7 -2
  73. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/RECORD +76 -35
  74. quantalogic/tools/rag_tool/document_metadata.py +0 -15
  75. quantalogic/tools/rag_tool/query_response.py +0 -20
  76. quantalogic/tools/rag_tool/rag_tool.py +0 -566
  77. quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
  78. quantalogic/utils/python_interpreter.py +0 -905
  79. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/LICENSE +0 -0
  80. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/WHEEL +0 -0
  81. {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
- tool = Tool(name="my_tool", description="A simple tool", arguments=[ToolArgument(name="arg1", arg_type="string")])
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", description="A simple tool", arguments=[ToolArgument(name="field1", arg_type="string")]
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 tool2",
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("\nAsynchronous Tool:")
479
+ print("Asynchronous Tool Markdown:")
401
480
  print(async_tool.to_markdown())
402
- print("Execution result:", asyncio.run(async_tool.async_execute(name="Alice")))
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 = True
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())