org.slashlib.py.agent 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/__init__.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+
16
+ from src.org.slashlib.py.agent.agent import Agent
17
+ from src.org.slashlib.py.agent.tool import tool
18
+ from src.org.slashlib.py.agent.agent_response import AgentResponse
19
+ from src.org.slashlib.py.agent.inference_bases import (
20
+ InferenceAdapter,
21
+ InferenceResult,
22
+ InferenceError,
23
+ InferenceConnectionError,
24
+ InferenceConfigError,
25
+ InferencePayloadError
26
+ )
27
+ from src.org.slashlib.py.agent.inference_complements_for_ollama import (
28
+ OllamaInferenceAdapter,
29
+ OllamaInferenceResult
30
+ )
31
+
32
+ __all__ = [
33
+ "Agent",
34
+ "tool",
35
+ "AgentResponse",
36
+ "InferenceAdapter",
37
+ "InferenceResult",
38
+ "InferenceError",
39
+ "InferenceConnectionError",
40
+ "InferenceConfigError",
41
+ "InferencePayloadError",
42
+ "OllamaInferenceAdapter",
43
+ "OllamaInferenceResult"
44
+ ]
45
+
46
+ # end of file src/org/slashlib/py/agent/__init__.py
@@ -0,0 +1,91 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/__main__.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+ #
16
+ # pragma: no cover
17
+ #
18
+ """
19
+ Entry point for the org.slashlib.py.agent package.
20
+ """
21
+
22
+ # Import python packages
23
+ import argparse
24
+ import logging
25
+ import pathlib
26
+ import sys
27
+ from importlib import metadata
28
+
29
+ # Compatibility layer for TOML (Python 3.11+ uses tomllib, older use tomli)
30
+ if sys.version_info >= (3, 11):
31
+ import tomllib
32
+ else:
33
+ try:
34
+ import tomli as tomllib
35
+ except ImportError:
36
+ tomllib = None
37
+
38
+ # Import thirdparty packages
39
+ import org.slashlib.py.configloader
40
+
41
+ # setup logging
42
+ org.slashlib.py.configloader.setup_logging()
43
+
44
+ log = logging.getLogger(f"org.slashlib.py.agent.{pathlib.Path(__file__).stem}")
45
+
46
+ def get_version() -> str:
47
+ """
48
+ Retrieve the version of the package from metadata or local pyproject.toml.
49
+ """
50
+ # 1. Try metadata (works if installed via pip)
51
+ try:
52
+ return metadata.version("org.slashlib.py.agent")
53
+ except metadata.PackageNotFoundError:
54
+ pass
55
+
56
+ # 2. Fallback: Parse pyproject.toml directly (works during development)
57
+ if tomllib is not None:
58
+ try:
59
+ # Search for pyproject.toml relative to this file's location
60
+ # Path: src/org/slashlib/py/agent/__main__.py -> root is 4 levels up
61
+ root_path = pathlib.Path(__file__).parents[5]
62
+ toml_path = root_path / "pyproject.toml"
63
+
64
+ if toml_path.exists():
65
+ with open(toml_path, "rb") as f:
66
+ data = tomllib.load(f)
67
+ return data.get("project", {}).get("version", "unknown (local)")
68
+ except Exception:
69
+ pass
70
+
71
+ return "unknown"
72
+
73
+ def start():
74
+ """
75
+ Bootstrap function to instantiate and run the bot.
76
+ """
77
+ parser = argparse.ArgumentParser(description="org.slashlib.py.agent CLI")
78
+ parser.add_argument("--version", action="store_true", help="Show the package version and exit")
79
+
80
+ args, unknown = parser.parse_known_args()
81
+
82
+ if args.version:
83
+ print(f"org.slashlib.py.agent version {get_version()}")
84
+ sys.exit(0)
85
+ else:
86
+ parser.print_help()
87
+
88
+ if __name__ == "__main__":
89
+ start()
90
+
91
+ # end of file src/org/slashlib/py/agent/__main__.py
@@ -0,0 +1,334 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/agent.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+ #
16
+
17
+ # Python imports
18
+ import asyncio
19
+ import logging
20
+ import pathlib
21
+ import typing
22
+
23
+ # Third party imports
24
+ import org.slashlib.py.configloader as config
25
+
26
+ # Internal imports
27
+ import src.org.slashlib.py.agent.tool as tool
28
+ import src.org.slashlib.py.agent.agent_response as response
29
+ import src.org.slashlib.py.agent.inference_bases as inference
30
+
31
+
32
+ # Module-level instance registry to implement the Multiton pattern
33
+ _instances: typing.Dict[str, "Agent"] = {}
34
+
35
+
36
+ class Agent:
37
+ """
38
+ Base class for an AI Agent.
39
+
40
+ This class orchestrates the interaction between an inference adapter and a set of tools.
41
+ """
42
+
43
+ def __new__(cls, identifier: str, tools: typing.List[tool.Tool], adapter: inference.InferenceAdapter, multi: bool = True):
44
+ """
45
+ Create a new instance or return an existing one based on the identifier.
46
+
47
+ Args:
48
+ identifier (str): A unique identifier for the agent instance.
49
+ tools (typing.List[tool.Tool]): A list of Tool objects the agent can use.
50
+ adapter (inference.InferenceAdapter): The adapter to use for inference.
51
+ multi (bool): Whether the agent can run multiple tasks simultaneously.
52
+
53
+ Returns:
54
+ Agent: The new or existing agent instance.
55
+ """
56
+ if identifier in _instances:
57
+ return _instances[identifier]
58
+
59
+ instance = super(Agent, cls).__new__(cls)
60
+ _instances[identifier] = instance
61
+ return instance
62
+
63
+ def __init__(self, identifier: str, tools: typing.List[tool.Tool], adapter: inference.InferenceAdapter, multi: bool = True):
64
+ """
65
+ Initialize the Agent.
66
+
67
+ Args:
68
+ identifier (str): A unique identifier for the agent instance.
69
+ tools (typing.List[tool.Tool]): A list of Tool objects. Must not be empty.
70
+ adapter (inference.InferenceAdapter): The adapter providing the inference capabilities.
71
+ multi (bool): If False, the agent allows only one active task at a time. Defaults to True.
72
+
73
+ Raises:
74
+ ValueError: If tools or adapter is None.
75
+ """
76
+ # Skip initialization if already initialized (for __new__ multiton logic)
77
+ if hasattr(self, "_initialized") and self._initialized:
78
+ return
79
+
80
+ self.log = logging.getLogger(f"org.slashlib.py.agent.{pathlib.Path(__file__).stem}.{self.__class__.__name__}")
81
+ self._identifier = identifier
82
+ self._multi = multi
83
+
84
+ if not tools:
85
+ raise ValueError("The tools list must not be empty.")
86
+
87
+ if not adapter:
88
+ raise ValueError("An InferenceAdapter is required.")
89
+
90
+ self._tools = tools
91
+ self._adapter = adapter
92
+ self._active_tasks: typing.Set[asyncio.Task] = set()
93
+ self._initialized = True
94
+ self.log.debug(f"Agent initialized with {len(self._tools)} tools and adapter {type(adapter).__name__} (multi={self._multi})")
95
+
96
+ def __del__(self):
97
+ """
98
+ Destructor called when the agent object is about to be destroyed.
99
+ Ensures all running tasks are cancelled.
100
+ """
101
+ # Note: During interpreter shutdown, globals/imports might be None.
102
+ try:
103
+ if hasattr(self, "_active_tasks") and self._active_tasks:
104
+ self.cancel()
105
+ except Exception:
106
+ # Destructors should not raise exceptions
107
+ pass
108
+
109
+ @property
110
+ def identifier(self) -> str:
111
+ """
112
+ Get the identifier of the agent.
113
+
114
+ Returns:
115
+ str: The agent's identifier.
116
+ """
117
+ return self._identifier
118
+
119
+ @property
120
+ def tools(self) -> typing.List[tool.Tool]:
121
+ """
122
+ Get the list of tools available to the agent.
123
+
124
+ Returns:
125
+ List[tool.Tool]: The registered tools.
126
+ """
127
+ return self._tools
128
+
129
+ @property
130
+ def has_active_tasks(self) -> bool:
131
+ """
132
+ Check if the agent is currently processing any tasks.
133
+
134
+ Returns:
135
+ bool: True if there are running background tasks.
136
+ """
137
+ return len(self._active_tasks) > 0
138
+
139
+ def get_tool_schemas(self) -> typing.List[dict]:
140
+ """
141
+ Collects all JSON schemas from the registered tools.
142
+
143
+ Returns:
144
+ typing.List[dict]: A list of tool schemas for LLM consumption.
145
+ """
146
+ return [tool_obj.get_schema() for tool_obj in self._tools]
147
+
148
+ def _init_response_object(self, **kwargs) -> response.AgentResponse:
149
+ """
150
+ Initializes a new AgentResponse object and sets up the initial context.
151
+
152
+ Args:
153
+ **kwargs: Arguments containing 'user_prompt' and optional 'system_prompt'.
154
+
155
+ Returns:
156
+ response.AgentResponse: The initialized response object.
157
+ """
158
+ response_obj = response.AgentResponse()
159
+
160
+ user_prompt = kwargs.get("user_prompt")
161
+ system_prompt = kwargs.get("system_prompt")
162
+
163
+ if not user_prompt:
164
+ err = ValueError("A 'user_prompt' is required to run the agent.")
165
+ response_obj.append_error(err)
166
+ return response_obj
167
+
168
+ if system_prompt:
169
+ response_obj.append_context(role="system", content=system_prompt)
170
+
171
+ response_obj.append_context(role="user", content=user_prompt)
172
+ return response_obj
173
+
174
+ async def _execute_tool(self, name: str, arguments: dict) -> typing.Any:
175
+ """
176
+ Internal helper to find and execute a tool by its name.
177
+
178
+ Args:
179
+ name (str): The name of the tool to execute.
180
+ arguments (dict): The arguments to pass to the tool.
181
+
182
+ Returns:
183
+ Any: The result of the tool execution.
184
+
185
+ Raises:
186
+ ValueError: If no tool with the given name is found.
187
+ """
188
+ for tool_obj in self._tools:
189
+ if tool_obj.name == name:
190
+ self.log.info(f"Agent {self.identifier} executing tool '{name}' with {arguments}")
191
+ return await tool_obj(**arguments)
192
+
193
+ raise ValueError(f"Tool '{name}' not found in agent's toolbox.")
194
+
195
+ async def _run_tool_calls(self, response_obj: response.AgentResponse, tool_calls: typing.Optional[typing.List[dict]]) -> bool:
196
+ """
197
+ Processes tool calls if present.
198
+
199
+ Args:
200
+ response_obj (response.AgentResponse): The current response object to update.
201
+ tool_calls (Optional[List[dict]]): The list of tool calls from the LLM.
202
+
203
+ Returns:
204
+ bool: True if tool calls were processed, False otherwise.
205
+ """
206
+ if not tool_calls:
207
+ return False
208
+
209
+ self.log.info(f"Agent {self.identifier} received {len(tool_calls)} tool call(s).")
210
+
211
+ for call in tool_calls:
212
+ tool_name = call.get("function", {}).get("name")
213
+ tool_args = call.get("function", {}).get("arguments")
214
+
215
+ try:
216
+ result = await self._execute_tool(tool_name, tool_args)
217
+ response_obj.append_context(
218
+ role="tool",
219
+ content=result,
220
+ name=tool_name
221
+ )
222
+ except Exception as tool_err:
223
+ self.log.error(f"Tool execution failed: {tool_err}")
224
+ response_obj.append_tool_error(tool_err)
225
+ response_obj.append_context(
226
+ role="tool",
227
+ content=f"Error: {str(tool_err)}",
228
+ name=tool_name
229
+ )
230
+ return True
231
+
232
+ async def _run(self, **kwargs) -> response.AgentResponse:
233
+ """
234
+ Internal asynchronous task execution.
235
+
236
+ Orchestrates the loop between the inference adapter and tool execution.
237
+
238
+ Args:
239
+ **kwargs: Arbitrary keyword arguments.
240
+ Supports 'user_prompt', 'system_prompt', 'model', 'timeout' and 'think'.
241
+
242
+ Returns:
243
+ response.AgentResponse: The result object containing the history and any errors.
244
+ """
245
+ response_obj = self._init_response_object(**kwargs)
246
+
247
+ if response_obj.has_error:
248
+ return response_obj
249
+
250
+ try:
251
+ is_running = True
252
+ while is_running:
253
+ self.log.debug(f"Agent {self.identifier} initiating inference call...")
254
+
255
+ # The adapter handles all provider-specific details and config resolution
256
+ inference_result = await self._adapter.chat(
257
+ messages=response_obj.get_context(),
258
+ tools=self.get_tool_schemas(),
259
+ **kwargs
260
+ )
261
+
262
+ # Use the standardized to_dict() for AgentResponse
263
+ response_obj.append_context(**inference_result.to_dict())
264
+
265
+ if await self._run_tool_calls(response_obj, inference_result.tool_calls):
266
+ continue
267
+
268
+ is_running = False
269
+ self.log.info(f"Agent {self.identifier} finished task.")
270
+
271
+ except asyncio.CancelledError:
272
+ self.log.debug(f"Task for agent {self.identifier} was cancelled.")
273
+ raise
274
+ except inference.InferenceError as ie:
275
+ self.log.error(f"Inference failed for agent {self.identifier}: {ie}")
276
+ response_obj.append_error(ie)
277
+ except Exception as e:
278
+ self.log.error(f"Unexpected error in background task for agent {self.identifier}: {e}", exc_info=True)
279
+ response_obj.append_error(e)
280
+ finally:
281
+ current_task = asyncio.current_task()
282
+ if current_task in self._active_tasks:
283
+ self._active_tasks.remove(current_task)
284
+
285
+ return response_obj
286
+
287
+ def run(self, **kwargs) -> asyncio.Task:
288
+ """
289
+ Starts the agent logic in a non-blocking background task.
290
+
291
+ Args:
292
+ **kwargs: Arbitrary keyword arguments passed to the internal _run method.
293
+
294
+ Returns:
295
+ asyncio.Task: The task object managing the background execution.
296
+ The result of this task will be an AgentResponse object.
297
+
298
+ Raises:
299
+ RuntimeError: If multi is False and a task is already running.
300
+ """
301
+ if not self._multi and self.has_active_tasks:
302
+ raise RuntimeError(f"Agent {self.identifier} is already running a task and multi-tasking is disabled.")
303
+
304
+ task = asyncio.create_task(self._run(**kwargs))
305
+ self._active_tasks.add(task)
306
+ return task
307
+
308
+ def cancel(self, task: typing.Optional[asyncio.Task] = None):
309
+ """
310
+ Cancels running tasks.
311
+
312
+ Args:
313
+ task (Optional[asyncio.Task]): The specific task to cancel.
314
+ """
315
+ if task is not None:
316
+ if task in self._active_tasks:
317
+ task.cancel()
318
+ self.log.debug(f"Specific task cancelled for agent {self.identifier}.")
319
+ else:
320
+ self.log.warning(f"Task cancel requested but task not found in active tasks for agent {self.identifier}.")
321
+ return
322
+
323
+ if not self._active_tasks:
324
+ self.log.debug(f"No active tasks to cancel for agent {self.identifier}.")
325
+ return
326
+
327
+ self.log.debug(f"Cancelling all ({len(self._active_tasks)}) active tasks for agent {self.identifier}.")
328
+ for t in list(self._active_tasks):
329
+ t.cancel()
330
+
331
+
332
+ __all__ = ["Agent"]
333
+
334
+ # end of file src/org/slashlib/py/agent/agent.py
@@ -0,0 +1,207 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/agent_response.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+
16
+ # Python imports
17
+ import copy
18
+ import json
19
+ import logging
20
+ import pathlib
21
+ import typing
22
+
23
+
24
+ class AgentResponse:
25
+ """
26
+ Represents the result of an Agent execution, including history and errors.
27
+
28
+ Separates process errors (fatal to the run) from tool errors (non-fatal execution errors).
29
+ """
30
+
31
+ def __init__(self):
32
+ """
33
+ Initialize a new AgentResponse instance.
34
+ """
35
+ self.log = logging.getLogger(f"org.slashlib.py.agent.{pathlib.Path(__file__).stem}.{self.__class__.__name__}")
36
+ self._context: typing.List[typing.Dict[str, typing.Any]] = []
37
+ self._errors: typing.List[Exception] = []
38
+ self._tool_errors: typing.List[Exception] = []
39
+
40
+ def append_context(self, **kwargs):
41
+ """
42
+ Public interface to add a message to the context.
43
+
44
+ Args:
45
+ **kwargs: Arbitrary keyword arguments (e.g., role, content).
46
+ """
47
+ self._append_context(**kwargs)
48
+
49
+ def append_error(self, error: Exception):
50
+ """
51
+ Public interface to record a process error.
52
+
53
+ Args:
54
+ error (Exception): The exception instance to record.
55
+ """
56
+ self._append_error(error)
57
+
58
+ def append_tool_error(self, error: Exception):
59
+ """
60
+ Public interface to record a tool error.
61
+
62
+ Args:
63
+ error (Exception): The exception instance to record.
64
+ """
65
+ self._append_tool_error(error)
66
+
67
+ def _append_context(self, **kwargs):
68
+ """
69
+ Internal method to append a message to the context.
70
+
71
+ Args:
72
+ **kwargs: Arbitrary keyword arguments. Expected to contain 'role' (str)
73
+ and 'content' (str, dict, or list).
74
+
75
+ Raises:
76
+ TypeError: If 'content' is not a string, dict, or list.
77
+ ValueError: If 'content' cannot be serialized to JSON.
78
+ """
79
+ role = kwargs.get("role")
80
+ content = kwargs.get("content")
81
+
82
+ if (not role) or (content is None):
83
+ self.log.warning(f"Skipped context update: 'role' or 'content' missing in {kwargs}")
84
+ return
85
+
86
+ # Ensure content is a string (convert if it's a JSON-like structure)
87
+ if isinstance(content, (dict, list)):
88
+ try:
89
+ kwargs["content"] = json.dumps(content, ensure_ascii=False)
90
+ except (TypeError, ValueError) as e:
91
+ self.log.error(f"Failed to serialize context content to JSON: {e}")
92
+ raise
93
+ elif not isinstance(content, str):
94
+ msg = f"Unsupported content type: {type(content)}. Expected str, dict or list."
95
+ self.log.error(msg)
96
+ raise TypeError(msg)
97
+
98
+ self._context.append(kwargs)
99
+
100
+ def _append_error(self, error: Exception):
101
+ """
102
+ Appends a process exception (fatal) to the error list.
103
+
104
+ Args:
105
+ error (Exception): The exception instance to record.
106
+ """
107
+ if isinstance(error, Exception):
108
+ self._errors.append(error)
109
+ self.log.debug(f"Process error recorded in AgentResponse: {error}")
110
+
111
+ def _append_tool_error(self, error: Exception):
112
+ """
113
+ Appends a tool-specific exception (non-fatal) to the tool error list.
114
+
115
+ Args:
116
+ error (Exception): The exception instance to record.
117
+ """
118
+ if isinstance(error, Exception):
119
+ self._tool_errors.append(error)
120
+ self.log.debug(f"Tool error recorded in AgentResponse: {error}")
121
+
122
+ def get_context(self, index: typing.Optional[int] = None) -> typing.Union[typing.List[dict], dict]:
123
+ """
124
+ Returns a deep copy of the context.
125
+
126
+ Args:
127
+ index (Optional[int]): If provided, returns only the node at this index.
128
+ Otherwise, returns the entire list. Defaults to None.
129
+
130
+ Returns:
131
+ Union[List[dict], dict]: A deep copy of the entire context list or a single
132
+ context dictionary if an index was provided.
133
+ """
134
+ if index is not None:
135
+ return copy.deepcopy(self._context[index])
136
+ return copy.deepcopy(self._context)
137
+
138
+ @property
139
+ def response(self) -> typing.Optional[str]:
140
+ """
141
+ Returns the content of the last assistant message in the context.
142
+
143
+ Returns:
144
+ Optional[str]: The final response string from the assistant,
145
+ or None if no assistant message exists in the history.
146
+ """
147
+ for msg in reversed(self._context):
148
+ if msg.get("role") == "assistant":
149
+ return msg.get("content")
150
+ return None
151
+
152
+ @property
153
+ def context_size(self) -> int:
154
+ """
155
+ Returns the number of messages in the context.
156
+
157
+ Returns:
158
+ int: The total count of context entries.
159
+ """
160
+ return len(self._context)
161
+
162
+ @property
163
+ def has_error(self) -> bool:
164
+ """
165
+ Checks if any process errors occurred during execution.
166
+
167
+ Returns:
168
+ bool: True if one or more process errors were recorded.
169
+ """
170
+ return len(self._errors) > 0
171
+
172
+ @property
173
+ def has_tool_error(self) -> bool:
174
+ """
175
+ Checks if any tool-specific errors occurred during execution.
176
+
177
+ Returns:
178
+ bool: True if one or more tool errors were recorded.
179
+ """
180
+ return len(self._tool_errors) > 0
181
+
182
+ def raise_errors(self):
183
+ """
184
+ Raises the first process error if any exist.
185
+ Tool errors are ignored by this method as they are non-fatal.
186
+
187
+ Raises:
188
+ Exception: The first process exception stored in the error list.
189
+ """
190
+ if self.has_error:
191
+ if len(self._errors) > 1:
192
+ self.log.warning(f"Multiple process errors ({len(self._errors)}) found. Raising the first one.")
193
+ raise self._errors[0]
194
+
195
+ def get_tool_errors(self) -> typing.List[Exception]:
196
+ """
197
+ Returns all recorded tool errors.
198
+
199
+ Returns:
200
+ List[Exception]: A list of exceptions that occurred during tool execution.
201
+ """
202
+ return self._tool_errors
203
+
204
+
205
+ __all__ = ["AgentResponse"]
206
+
207
+ # end of file src/org/slashlib/py/agent/agent_response.py
@@ -0,0 +1,120 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/inference_bases.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+
16
+ # Python imports
17
+ import abc
18
+ import typing
19
+
20
+
21
+ class InferenceError(Exception):
22
+ """Base exception for all inference related errors."""
23
+ pass
24
+
25
+
26
+ class InferenceConnectionError(InferenceError):
27
+ """Raised when the connection to the AI provider fails."""
28
+ pass
29
+
30
+
31
+ class InferenceConfigError(InferenceError):
32
+ """Raised when the model configuration (e.g. model name) is invalid."""
33
+ pass
34
+
35
+
36
+ class InferencePayloadError(InferenceError):
37
+ """Raised when the response payload is malformed or invalid."""
38
+ pass
39
+
40
+
41
+ class InferenceResult(abc.ABC):
42
+ """
43
+ Abstract interface for the result of an inference execution.
44
+ Standardizes how the Agent accesses model responses and tool calls.
45
+ """
46
+
47
+ @property
48
+ @abc.abstractmethod
49
+ def role(self) -> str:
50
+ """Returns the role of the message (e.g., 'assistant')."""
51
+ pass
52
+
53
+ @property
54
+ @abc.abstractmethod
55
+ def content(self) -> typing.Optional[str]:
56
+ """Returns the text content of the response."""
57
+ pass
58
+
59
+ @property
60
+ @abc.abstractmethod
61
+ def tool_calls(self) -> typing.Optional[typing.List[typing.Dict[str, typing.Any]]]:
62
+ """
63
+ Returns tool calls in a standardized format:
64
+ [{"function": {"name": str, "arguments": dict}}]
65
+ """
66
+ pass
67
+
68
+ @abc.abstractmethod
69
+ def to_dict(self) -> typing.Dict[str, typing.Any]:
70
+ """
71
+ Converts the result into a dictionary compatible with the
72
+ internal storage format of AgentResponse.
73
+ """
74
+ pass
75
+
76
+
77
+ class InferenceAdapter(abc.ABC):
78
+ """
79
+ Abstract interface for an Inference Adapter.
80
+ Encapsulates the communication with a specific AI model provider.
81
+ """
82
+
83
+ @abc.abstractmethod
84
+ async def chat(
85
+ self,
86
+ model: typing.Optional[str] = None,
87
+ messages: typing.List[typing.Dict[str, typing.Any]] = None,
88
+ tools: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None,
89
+ **kwargs
90
+ ) -> InferenceResult:
91
+ """
92
+ Executes a chat request and returns a standardized InferenceResult.
93
+
94
+ Args:
95
+ model (str): The name/identifier of the model to use.
96
+ messages (list): The full conversation context.
97
+ tools (list, optional): Available tool schemas.
98
+ **kwargs: Provider-specific options (timeout, think, temperature, etc.)
99
+
100
+ Returns:
101
+ InferenceResult: The validated and standardized result.
102
+
103
+ Raises:
104
+ InferenceConnectionError: If the provider is unreachable.
105
+ InferencePayloadError: If the response is malformed.
106
+ InferenceConfigError: If configuration/model is wrong.
107
+ """
108
+ pass
109
+
110
+
111
+ __all__ = [
112
+ "InferenceResult",
113
+ "InferenceAdapter",
114
+ "InferenceError",
115
+ "InferenceConnectionError",
116
+ "InferenceConfigError",
117
+ "InferencePayloadError"
118
+ ]
119
+
120
+ # end of file src/org/slashlib/py/agent/inference_bases.py
@@ -0,0 +1,186 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/inference_complements_for_ollama.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+ #
16
+
17
+ # Python imports
18
+ import logging
19
+ import pathlib
20
+ import typing
21
+
22
+ # Third party imports
23
+ import ollama
24
+ import org.slashlib.py.configloader as config
25
+
26
+ # Internal imports
27
+ import src.org.slashlib.py.agent.inference_bases as inference
28
+
29
+
30
+ class OllamaInferenceResult(inference.InferenceResult):
31
+ """
32
+ Specific implementation of InferenceResult for Ollama.
33
+
34
+ This class takes the raw dictionary response from the Ollama API,
35
+ validates its mandatory fields (role, content/tool_calls), and
36
+ provides standardized accessors for the Agent.
37
+ """
38
+
39
+ def __init__(self, raw_response: typing.Dict[str, typing.Any]):
40
+ """
41
+ Initialize and validate the Ollama response.
42
+
43
+ Args:
44
+ raw_response (Dict[str, Any]): The 'message' part of the Ollama API response.
45
+
46
+ Raises:
47
+ inference.InferencePayloadError: If the 'role' is missing or if both
48
+ 'content' and 'tool_calls' are empty/None.
49
+ """
50
+ self._role = raw_response.get("role")
51
+ self._content = raw_response.get("content")
52
+ self._tool_calls = raw_response.get("tool_calls")
53
+
54
+ if not self._role:
55
+ raise inference.InferencePayloadError(f"Invalid Ollama response: 'role' is missing. Data: {raw_response}")
56
+
57
+ # Note: content can be None if tool_calls are present, which is valid.
58
+ if self._content is None and not self._tool_calls:
59
+ raise inference.InferencePayloadError(f"Invalid Ollama response: Both 'content' and 'tool_calls' are empty.")
60
+
61
+ @property
62
+ def role(self) -> str:
63
+ """
64
+ The role of the message sender.
65
+
66
+ Returns:
67
+ str: Typically 'assistant'.
68
+ """
69
+ return self._role
70
+
71
+ @property
72
+ def content(self) -> typing.Optional[str]:
73
+ """
74
+ The text content of the model's response.
75
+
76
+ Returns:
77
+ Optional[str]: The message text or None if only tool calls are present.
78
+ """
79
+ return self._content
80
+
81
+ @property
82
+ def tool_calls(self) -> typing.Optional[typing.List[typing.Dict[str, typing.Any]]]:
83
+ """
84
+ A list of tool calls generated by the model.
85
+
86
+ Returns:
87
+ Optional[List[Dict[str, Any]]]: Standardized tool call objects or None.
88
+ """
89
+ return self._tool_calls
90
+
91
+ def to_dict(self) -> typing.Dict[str, typing.Any]:
92
+ """
93
+ Converts the result into a standardized dictionary.
94
+
95
+ Returns:
96
+ Dict[str, Any]: A dictionary containing 'role', 'content', and 'tool_calls'.
97
+ """
98
+ return {
99
+ "role": self._role,
100
+ "content": self._content,
101
+ "tool_calls": self._tool_calls
102
+ }
103
+
104
+
105
+ class OllamaInferenceAdapter(inference.InferenceAdapter):
106
+ """
107
+ Inference Adapter for the Ollama API.
108
+
109
+ Handles asynchronous communication with an Ollama server, including
110
+ parameter resolution from config and error mapping to the neutral
111
+ inference exception hierarchy.
112
+ """
113
+
114
+ def __init__(self):
115
+ """
116
+ Initialize the adapter and its logger.
117
+ """
118
+ self.log = logging.getLogger(f"org.slashlib.py.agent.{pathlib.Path(__file__).stem}.{self.__class__.__name__}")
119
+
120
+ async def chat(
121
+ self,
122
+ model: typing.Optional[str] = None,
123
+ messages: typing.List[typing.Dict[str, typing.Any]] = None,
124
+ tools: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None,
125
+ **kwargs
126
+ ) -> inference.InferenceResult:
127
+ """
128
+ Sends a chat request to Ollama and returns a validated result.
129
+
130
+ Resolves model parameters using a priority chain:
131
+ 1. Explicit kwargs, 2. Method arguments, 3. pyproject.json via configloader.
132
+
133
+ Args:
134
+ model (Optional[str]): The model name. Defaults to config value.
135
+ messages (List[Dict[str, Any]]): The message history/context.
136
+ tools (Optional[List[Dict[str, Any]]]): JSON schemas of available tools.
137
+ **kwargs: Additional parameters like 'timeout' or 'think'.
138
+
139
+ Returns:
140
+ inference.InferenceResult: An instance of OllamaInferenceResult.
141
+
142
+ Raises:
143
+ inference.InferenceConfigError: If the model is not found or config is invalid.
144
+ inference.InferenceConnectionError: If the Ollama server is unreachable or times out.
145
+ inference.InferencePayloadError: If the response from Ollama is malformed.
146
+ inference.InferenceError: For any other unexpected errors during inference.
147
+ """
148
+ # Resolve parameters: priority is kwargs -> method arg -> pyproject.json
149
+ target_model = model or kwargs.get("model", config.resolve("adapter.ollama.model"))
150
+ timeout = kwargs.get("timeout", config.resolve("adapter.ollama.timeout"))
151
+ think = kwargs.get("think", config.resolve("adapter.ollama.think"))
152
+
153
+ try:
154
+ self.log.debug(f"Ollama request: model={target_model}, timeout={timeout}, think={think}")
155
+
156
+ client = ollama.AsyncClient(timeout=timeout)
157
+ response = await client.chat(
158
+ model=target_model,
159
+ messages=messages,
160
+ tools=tools,
161
+ think=think
162
+ )
163
+
164
+ message = response.get("message")
165
+ if not message:
166
+ raise inference.InferencePayloadError(f"Ollama API returned an empty response for model '{target_model}'.")
167
+
168
+ return OllamaInferenceResult(message)
169
+
170
+ except (ollama.ResponseError) as e:
171
+ self.log.error(f"Ollama logical/config error: {e}")
172
+ raise inference.InferenceConfigError(f"Ollama model or config invalid: {str(e)}")
173
+ except (ollama.RequestError) as e:
174
+ self.log.error(f"Ollama communication error: {e}")
175
+ raise inference.InferenceConnectionError(f"Failed to connect to Ollama: {str(e)}")
176
+ except inference.InferenceError:
177
+ # Re-raise internal inference errors to prevent them from being caught by the general Exception block
178
+ raise
179
+ except Exception as e:
180
+ self.log.error(f"Unexpected error in OllamaInferenceAdapter: {e}", exc_info=True)
181
+ raise inference.InferenceError(f"Internal adapter error: {str(e)}")
182
+
183
+
184
+ __all__ = ["OllamaInferenceAdapter", "OllamaInferenceResult"]
185
+
186
+ # end of file src/org/slashlib/py/agent/inference_complements_for_ollama.py
@@ -0,0 +1,158 @@
1
+ # -*- coding: utf-8 -*-
2
+ # file src/org/slashlib/py/agent/tool.py
3
+ # @AI:
4
+ # - INTEGRITY RULES:
5
+ # - STRICT PRESERVATION: Do not remove, move, or modify ANY existing lines of code or comments
6
+ # unless they are the explicit target of the requested change.
7
+ # - DEBUG MARKERS: Commented-out code (e.g., debug prints) MUST be kept exactly where they are.
8
+ # - WHITESPACE & STRUCTURE: Maintain all original empty lines and the existing file structure.
9
+ # Structural integrity takes precedence over "clean code" or "elegance".
10
+ # - LEAD-IN/OUT: The very first and last lines (and all comments in between) are immutable anchors.
11
+ # - MAINTENANCE:
12
+ # - Only update pydoc strings (args, returns, raises) if the function signature changes.
13
+ # - Do NOT delete existing examples or descriptions in pydoc.
14
+ # - LANGUAGE: en-US for all comments and documentation.
15
+ #
16
+
17
+ # Python imports
18
+ import functools
19
+ import inspect
20
+ import typing
21
+
22
+ # Third party imports
23
+
24
+
25
+ class Tool:
26
+ """
27
+ A wrapper class that transforms a Python function into a schema-aware tool.
28
+
29
+ This class is designed to be used by AI agents (like Gemma 4) to understand
30
+ the capabilities, parameters, and requirements of a specific function.
31
+ """
32
+
33
+ def __init__(self, func, name=None, description=None):
34
+ """
35
+ Initialize the Tool instance.
36
+
37
+ Args:
38
+ func (Callable): The function to be wrapped as a tool.
39
+ name (Optional[str]): Override for the function's name. Defaults to func.__name__.
40
+ description (Optional[str]): Override for the function's description.
41
+ Defaults to the function's docstring.
42
+ """
43
+ self._func = func
44
+ self.name = name or func.__name__
45
+ self.description = description or func.__doc__ or "No description provided."
46
+ # Wir kopieren Metadaten der Originalfunktion (für Dokumentation etc.)
47
+ functools.update_wrapper(self, func)
48
+
49
+ def _map_type(self, annotation: typing.Any) -> str:
50
+ """
51
+ Maps Python type annotations to JSON schema compatible type strings.
52
+
53
+ Args:
54
+ annotation (Any): The Python type annotation to map.
55
+
56
+ Returns:
57
+ str: The corresponding JSON schema type (e.g., 'string', 'integer', 'array').
58
+ """
59
+ # Handle Optional[T] or Union[T, None]
60
+ origin = typing.get_origin(annotation)
61
+ if origin is typing.Union:
62
+ args = typing.get_args(annotation)
63
+ # Filter out NoneType to find the actual type
64
+ actual_types = [a for a in args if a is not type(None)]
65
+ if actual_types:
66
+ return self._map_type(actual_types[0])
67
+
68
+ mapping = {
69
+ int: "integer",
70
+ float: "number",
71
+ str: "string",
72
+ bool: "boolean",
73
+ list: "array",
74
+ dict: "object",
75
+ typing.List: "array",
76
+ typing.Dict: "object",
77
+ }
78
+
79
+ return mapping.get(annotation if origin is None else origin, "string")
80
+
81
+ def get_schema(self) -> dict:
82
+ """
83
+ Generates a JSON schema based on the function's signature and annotations.
84
+
85
+ The schema follows the standard expected by modern LLMs for tool calling.
86
+
87
+ Returns:
88
+ dict: A dictionary representing the tool's schema, including name,
89
+ description, and parameter definitions.
90
+ """
91
+ sig = inspect.signature(self._func)
92
+ parameters = {"type": "object", "properties": {}, "required": []}
93
+
94
+ for param_name, param in sig.parameters.items():
95
+ # Skip 'self' or 'cls' if decorated inside a class (though unlikely here)
96
+ if param_name in ("self", "cls"):
97
+ continue
98
+
99
+ param_type = self._map_type(param.annotation)
100
+
101
+ param_info = {
102
+ "type": param_type,
103
+ "description": f"Parameter {param_name}"
104
+ }
105
+
106
+ # If there's a default value, mention it in the description
107
+ if param.default is not inspect.Parameter.empty:
108
+ param_info["default"] = param.default
109
+ param_info["description"] += f" (defaults to {param.default})"
110
+ else:
111
+ parameters["required"].append(param_name)
112
+
113
+ parameters["properties"][param_name] = param_info
114
+
115
+ return {
116
+ "name": self.name,
117
+ "description": self.description.strip(),
118
+ "parameters": parameters
119
+ }
120
+
121
+ async def __call__(self, *args, **kwargs):
122
+ """
123
+ Executes the wrapped function.
124
+
125
+ Supports both synchronous and asynchronous functions transparently.
126
+
127
+ Args:
128
+ *args: Positional arguments for the wrapped function.
129
+ **kwargs: Keyword arguments for the wrapped function.
130
+
131
+ Returns:
132
+ Any: The result of the function execution.
133
+ """
134
+ if inspect.iscoroutinefunction(self._func):
135
+ return await self._func(*args, **kwargs)
136
+ return self._func(*args, **kwargs)
137
+
138
+
139
+ def tool(name: str = None, description: str = None):
140
+ """
141
+ A decorator that converts a function into a Tool object.
142
+
143
+ Args:
144
+ name (Optional[str]): A custom name for the tool.
145
+ description (Optional[str]): A custom description for the tool.
146
+
147
+ Returns:
148
+ Callable: A decorator function that wraps the target function in a Tool instance.
149
+ """
150
+ def decorator(func):
151
+ # Wir geben eine Instanz von Tool zurück
152
+ return Tool(func, name=name, description=description)
153
+ return decorator
154
+
155
+
156
+ __all__ = ["tool", "Tool"]
157
+
158
+ # end of file src/org/slashlib/py/agent/tool.py
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: org.slashlib.py.agent
3
+ Version: 0.1.0
4
+ Summary: python agent for inference handling
5
+ Author-email: Dirk Brenckmann <db.developer@gmx.de>
6
+ Project-URL: Homepage, https://github.com/org-slashlib/org.slashlib.py.agent
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.md
13
+ Dynamic: license-file
14
+
15
+ [Bottom](#license) [AI](AI.md) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE.md)
16
+ # org.slashlib.py.agent
17
+
18
+ A highly decoupled, asynchronous framework for building AI agents in Python.
19
+
20
+ [![PyPI version](https://img.shields.io/pypi/v/org.slashlib.py.agent.svg)](https://pypi.org/project/org.slashlib.py.agent/)
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
+
23
+ ## Core Concept
24
+
25
+ This package provides a robust infrastructure to connect AI models (Inference Engines) with functional tools. The focus lies on **Provider Agnosticism**: The agent does not need to know whether it is communicating with Ollama, OpenAI, or a local model—it uses standardized adapters to ensure seamless integration.
26
+
27
+ ### Key Features
28
+ - **Asynchronous Core**: Built on `asyncio` for non-blocking task execution.
29
+ - **Provider Agnostic**: Easily swap the AI engine using the Adapter pattern.
30
+ - **Automatic Tool Schemas**: Automatically transforms Python functions into JSON schemas for LLMs via decorators.
31
+ - **Multiton Pattern**: Ensures unique agent instances by identifier, preventing redundant resource allocation.
32
+ - **Robust Exception Hierarchy**: Clearly separates connection, configuration, and tool execution errors.
33
+
34
+ ---
35
+ ## Installation
36
+
37
+ Install the package via pip:
38
+
39
+ ```bash
40
+ pip install org.slashlib.py.agent
41
+ ```
42
+
43
+ ---
44
+ ## Quick Start
45
+
46
+ Setting up an agent with a tool and the Ollama adapter is straightforward:
47
+
48
+ ```python
49
+ import asyncio
50
+ from org.slashlib.py.agent import Agent, OllamaInferenceAdapter, tool
51
+
52
+ # 1. Define a tool
53
+ @tool(description="Adds two numbers.")
54
+ async def add_numbers(a: int, b: int) -> int:
55
+ return a + b
56
+
57
+ async def main():
58
+ # 2. Configure Adapter and Agent
59
+ adapter = OllamaInferenceAdapter()
60
+ my_agent = Agent(
61
+ identifier="MathExpert",
62
+ tools=[add_numbers],
63
+ adapter=adapter
64
+ )
65
+
66
+ # 3. Start task (non-blocking)
67
+ task = my_agent.run(user_prompt="What is 123 + 456?")
68
+
69
+ # 4. Retrieve result
70
+ response = await task
71
+ print(f"Response: {response.get_last_content()}")
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(main())
75
+ ```
76
+
77
+ ---
78
+ ## Documentation & Obsidian
79
+
80
+ The project root is pre-configured as an **Obsidian Vault**. If you open this folder directly in Obsidian, all settings and documentation links will be available immediately via the included `.obsidian` directory.
81
+
82
+ The following community plugins are pre-configured in the vault to enhance the documentation experience:
83
+
84
+ * **[File Include](https://github.com/tillahoffmann/obsidian-file-include)**: Embed code files directly into your markdown documentation.
85
+ * **[Folder Notes](https://github.com/LostPaul/obsidian-folder-notes)**: Add descriptions at the folder level.
86
+ * **[Front Matter Title](https://github.com/snezhig/obsidian-front-matter-title)**: Use metadata for descriptive file titles.
87
+ * **[Hide Folders](https://github.com/JonasDoesThings/obsidian-hide-folders)**: Keeps the structure clean by hiding internal directories.
88
+ * **[Iconic](https://github.com/gfxholo/iconic)** & **[Icons](https://github.com/visini/obsidian-icons-plugin)**: Improved visual navigation.
89
+
90
+ [More docs](docs)
91
+
92
+ ---
93
+ ## License
94
+
95
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
96
+
97
+ ---
98
+ © 2026 org.slashlib
99
+
100
+ [TOP](#org-slashlib-py-agent) [AI](AI.md) [CHANGELOG](CHANGELOG.md) [LICENSE](LICENSE.md)
@@ -0,0 +1,13 @@
1
+ org/slashlib/py/agent/__init__.py,sha256=XMV_X4I7bKJjRYrEOMf9_Hs9Tw94lDDaylpKJ_2HNzo,1738
2
+ org/slashlib/py/agent/__main__.py,sha256=_Ldk6mqVZO9gEDD_Tq7GvY__GMdoqafOO8iWuSMxY2A,3105
3
+ org/slashlib/py/agent/agent.py,sha256=eGBXVc804dlJ8TA_Veg6SkTbqnZCYYczv65eLuq_tXI,12665
4
+ org/slashlib/py/agent/agent_response.py,sha256=Km1AA8ZrQ8heG42EbfIAcWAOGWn61H3y26_PLBXTtEk,7274
5
+ org/slashlib/py/agent/inference_bases.py,sha256=MdbbxBwAQGJKGxOYB6VKvLQYJcWxZ0ULFS7Ylekhxuc,3920
6
+ org/slashlib/py/agent/inference_complements_for_ollama.py,sha256=4dosSeZIiLr9jQGX3JPw9bxXv0rv9kzbMboWnktQCuo,7477
7
+ org/slashlib/py/agent/tool.py,sha256=rvBt-zCeaBtY9GNrQ-uaDCje2YOMNUX3diO8c6tn7DQ,5838
8
+ org_slashlib_py_agent-0.1.0.dist-info/licenses/LICENSE.md,sha256=u_SrMZcAG0bjypv4AKPAgE9VFf-2vn8L0fRdizujhfI,1144
9
+ org_slashlib_py_agent-0.1.0.dist-info/METADATA,sha256=mdkOpZF9eCTxxVCAsEGA5Ref9YBFam-6xwt8cm92WgQ,3992
10
+ org_slashlib_py_agent-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ org_slashlib_py_agent-0.1.0.dist-info/org.slashlib.py.agent.egg-info.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ org_slashlib_py_agent-0.1.0.dist-info/top_level.txt,sha256=Y-Q3pQ6MMlBKzHVfq3nXGvODPPpbfj5OBXrcr17O02w,4
13
+ org_slashlib_py_agent-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,23 @@
1
+ [AI](AI.md) [CHANGELOG](CHANGELOG.md) [README](README.md)
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2019 Dirk Brenckmann, db-developer
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.