agentify-core 0.1.2__tar.gz → 0.1.3__tar.gz

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 (50) hide show
  1. {agentify_core-0.1.2/agentify_core.egg-info → agentify_core-0.1.3}/PKG-INFO +15 -5
  2. {agentify_core-0.1.2 → agentify_core-0.1.3}/README.md +13 -3
  3. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/__init__.py +1 -1
  4. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/core/agent.py +78 -5
  5. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/core/config.py +1 -1
  6. agentify_core-0.1.3/agentify/extensions/tools/__init__.py +15 -0
  7. agentify_core-0.1.3/agentify/extensions/tools/calculator.py +55 -0
  8. agentify_core-0.1.3/agentify/extensions/tools/filesystem.py +126 -0
  9. agentify_core-0.1.3/agentify/extensions/tools/planning.py +76 -0
  10. agentify_core-0.1.3/agentify/extensions/tools/time.py +22 -0
  11. agentify_core-0.1.3/agentify/extensions/tools/weather.py +52 -0
  12. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/interfaces.py +10 -0
  13. agentify_core-0.1.3/agentify/memory/stores/__init__.py +5 -0
  14. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/multi_agent/hierarchical.py +1 -1
  15. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/multi_agent/pipeline.py +2 -1
  16. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/multi_agent/team.py +1 -1
  17. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/multi_agent/tool_wrapper.py +86 -1
  18. {agentify_core-0.1.2 → agentify_core-0.1.3/agentify_core.egg-info}/PKG-INFO +15 -5
  19. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify_core.egg-info/SOURCES.txt +5 -1
  20. {agentify_core-0.1.2 → agentify_core-0.1.3}/pyproject.toml +4 -4
  21. agentify_core-0.1.3/tests/test_filesystem_tools.py +62 -0
  22. agentify_core-0.1.3/tests/test_planning_tool.py +55 -0
  23. agentify_core-0.1.2/agentify/extensions/tools/__init__.py +0 -9
  24. agentify_core-0.1.2/agentify/extensions/tools/calculator.py +0 -56
  25. agentify_core-0.1.2/agentify/extensions/tools/time.py +0 -21
  26. agentify_core-0.1.2/agentify/extensions/tools/weather.py +0 -51
  27. agentify_core-0.1.2/agentify/memory/stores/__init__.py +0 -6
  28. {agentify_core-0.1.2 → agentify_core-0.1.3}/LICENSE +0 -0
  29. {agentify_core-0.1.2 → agentify_core-0.1.3}/MANIFEST.in +0 -0
  30. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/core/__init__.py +0 -0
  31. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/core/callbacks.py +0 -0
  32. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/core/tool.py +0 -0
  33. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/extensions/__init__.py +0 -0
  34. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/extensions/prompts/__init__.py +0 -0
  35. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/extensions/prompts/assistant.py +0 -0
  36. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/llm/__init__.py +0 -0
  37. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/llm/client.py +0 -0
  38. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/__init__.py +0 -0
  39. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/policies.py +0 -0
  40. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/service.py +0 -0
  41. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/stores/in_memory_store.py +0 -0
  42. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/memory/stores/redis_store.py +0 -0
  43. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/multi_agent/__init__.py +0 -0
  44. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/utils/__init__.py +0 -0
  45. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify/utils/style.py +0 -0
  46. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify_core.egg-info/dependency_links.txt +0 -0
  47. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify_core.egg-info/requires.txt +0 -0
  48. {agentify_core-0.1.2 → agentify_core-0.1.3}/agentify_core.egg-info/top_level.txt +0 -0
  49. {agentify_core-0.1.2 → agentify_core-0.1.3}/requirements.txt +0 -0
  50. {agentify_core-0.1.2 → agentify_core-0.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentify-core
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Framework-agnostic AI agent library for building single and multi-agent systems
5
5
  Author-email: Fabian M <fabian@example.com>
6
6
  License: MIT
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
- Requires-Python: >=3.9
22
+ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: openai>=1.0.0
@@ -47,9 +47,10 @@ Dynamic: license-file
47
47
 
48
48
  # Agentify
49
49
 
50
- **Framework-agnostic AI agent library for building single and multi-agent systems**
50
+ **Independent AI agent library based on the OpenAI SDK**
51
+
52
+ Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It targets the OpenAI-compatible Chat Completions interface, enabling support for multiple providers through a configurable `base_url` (OpenAI, Azure OpenAI, DeepSeek, Gemini, etc.). Agentify offers a streamlined, independent set of primitives for memory, tools, and coordination so you can focus on product logic without being tied to heavy frameworks.
51
53
 
52
- Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It focuses on a small set of composable primitives for LLM integration, memory, tools and coordination, so you can focus on product logic instead of framework details.
53
54
 
54
55
  ## Why Agentify?
55
56
 
@@ -116,7 +117,7 @@ agent = BaseAgent(
116
117
  )
117
118
 
118
119
  # 3. Run a conversation
119
- response = agent.respond(user_input="Hello! How can you help me?")
120
+ response = agent.run(user_input="Hello! How can you help me?")
120
121
  ```
121
122
 
122
123
  ## Composable Flows
@@ -133,6 +134,15 @@ Because all flows share the same `run()` interface, you can build Teams made of
133
134
  Agentify supports both **strict workflows** (fixed, pre-defined Pipelines and Hierarchies) and **dynamic agentic flows**, where a supervisor/router agent decides at runtime which agent, Team or Pipeline to call next.
134
135
 
135
136
 
137
+ ## Documentation
138
+
139
+ - [Getting Started](docs/getting_started.md) - Installation and first steps
140
+ - [Core Concepts](docs/core_concepts.md) - Agents, memory, and tools
141
+ - [Multi-Agent Systems](docs/multi_agent.md) - Teams, pipelines, and hierarchies
142
+ - [Advanced Features](docs/advanced.md) - Vision, streaming, hooks, and more
143
+ - [API Reference](docs/api_reference.md) - Complete API documentation
144
+
145
+
136
146
  ### More Examples
137
147
 
138
148
  Check out the [examples](examples/) directory for detailed implementations:
@@ -1,8 +1,9 @@
1
1
  # Agentify
2
2
 
3
- **Framework-agnostic AI agent library for building single and multi-agent systems**
3
+ **Independent AI agent library based on the OpenAI SDK**
4
+
5
+ Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It targets the OpenAI-compatible Chat Completions interface, enabling support for multiple providers through a configurable `base_url` (OpenAI, Azure OpenAI, DeepSeek, Gemini, etc.). Agentify offers a streamlined, independent set of primitives for memory, tools, and coordination so you can focus on product logic without being tied to heavy frameworks.
4
6
 
5
- Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It focuses on a small set of composable primitives for LLM integration, memory, tools and coordination, so you can focus on product logic instead of framework details.
6
7
 
7
8
  ## Why Agentify?
8
9
 
@@ -69,7 +70,7 @@ agent = BaseAgent(
69
70
  )
70
71
 
71
72
  # 3. Run a conversation
72
- response = agent.respond(user_input="Hello! How can you help me?")
73
+ response = agent.run(user_input="Hello! How can you help me?")
73
74
  ```
74
75
 
75
76
  ## Composable Flows
@@ -86,6 +87,15 @@ Because all flows share the same `run()` interface, you can build Teams made of
86
87
  Agentify supports both **strict workflows** (fixed, pre-defined Pipelines and Hierarchies) and **dynamic agentic flows**, where a supervisor/router agent decides at runtime which agent, Team or Pipeline to call next.
87
88
 
88
89
 
90
+ ## Documentation
91
+
92
+ - [Getting Started](docs/getting_started.md) - Installation and first steps
93
+ - [Core Concepts](docs/core_concepts.md) - Agents, memory, and tools
94
+ - [Multi-Agent Systems](docs/multi_agent.md) - Teams, pipelines, and hierarchies
95
+ - [Advanced Features](docs/advanced.md) - Vision, streaming, hooks, and more
96
+ - [API Reference](docs/api_reference.md) - Complete API documentation
97
+
98
+
89
99
  ### More Examples
90
100
 
91
101
  Check out the [examples](examples/) directory for detailed implementations:
@@ -16,7 +16,7 @@ from agentify.memory.service import MemoryService
16
16
  from agentify.memory.interfaces import MemoryAddress
17
17
  from agentify.memory.policies import MemoryPolicy
18
18
 
19
- __version__ = "0.1.0"
19
+ __version__ = "0.1.3"
20
20
 
21
21
  __all__ = [
22
22
  "BaseAgent",
@@ -5,7 +5,8 @@ import time
5
5
  import uuid
6
6
  import base64
7
7
  from io import BytesIO
8
- from typing import Any, Dict, Generator, List, Optional, Union, Iterator
8
+ import inspect
9
+ from typing import Any, Dict, Generator, List, Optional, Union, Iterator, Callable
9
10
 
10
11
  from PIL import Image
11
12
  from openai import RateLimitError
@@ -21,7 +22,23 @@ logger = logging.getLogger(__name__)
21
22
 
22
23
 
23
24
  class BaseAgent:
24
- """Framework-agnostic AI Agent core class."""
25
+ """AI Agent core class based on chat completions interface.
26
+
27
+ This class provides a unified interface for interacting with various LLM providers
28
+ that are compatible with the OpenAI SDK format.
29
+
30
+ Attributes:
31
+ pre_hooks (List[Callable]): Functions to execute before the agent loop starts.
32
+ Supported arguments for injection:
33
+ - `agent`: The BaseAgent instance.
34
+ - `user_input`: The user's input string.
35
+
36
+ post_hooks (List[Callable]): Functions to execute after the agent loop finishes.
37
+ Supported arguments for injection:
38
+ - `agent`: The BaseAgent instance.
39
+ - `user_input`: The user's input string.
40
+ - `response`: The final accumulated response string.
41
+ """
25
42
 
26
43
  def __init__(
27
44
  self,
@@ -32,11 +49,15 @@ class BaseAgent:
32
49
  client_factory: Optional[LLMClientFactory] = None,
33
50
  tools: Optional[List[Tool]] = None,
34
51
  image_config: Optional[ImageConfig] = None,
52
+ pre_hooks: Optional[List[Callable]] = None,
53
+ post_hooks: Optional[List[Callable]] = None,
35
54
  ) -> None:
36
55
  self.config = config
37
56
  self.memory = memory
38
57
  self.memory_address = memory_address
39
58
  self.image_config = image_config or ImageConfig()
59
+ self.pre_hooks = pre_hooks or []
60
+ self.post_hooks = post_hooks or []
40
61
 
41
62
  # Decouple callbacks from config to avoid mutation of shared config
42
63
  self.callbacks = list(self.config.callbacks) if self.config.callbacks else []
@@ -196,6 +217,28 @@ class BaseAgent:
196
217
  for m in messages[1:]:
197
218
  self.memory.append_history(a, m)
198
219
 
220
+ # Hook Execution
221
+ def _execute_hook(self, hook: Callable, **kwargs: Any) -> None:
222
+ """Execute a hook injecting only the arguments it declares."""
223
+ try:
224
+ sig = inspect.signature(hook)
225
+ # Filter kwargs to only those present in the hook's signature
226
+ # If the hook accepts **kwargs, pass everything
227
+ has_var_keyword = any(
228
+ p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
229
+ )
230
+
231
+ if has_var_keyword:
232
+ hook_kwargs = kwargs
233
+ else:
234
+ hook_kwargs = {
235
+ k: v for k, v in kwargs.items() if k in sig.parameters
236
+ }
237
+
238
+ hook(**hook_kwargs)
239
+ except Exception as e:
240
+ logger.error(f"Error executing hook '{hook.__name__}': {e}", exc_info=True)
241
+
199
242
  # Core Logic
200
243
 
201
244
  def _get_llm_response(
@@ -518,6 +561,9 @@ class BaseAgent:
518
561
  for cb in self.callbacks:
519
562
  cb.on_agent_start(self.config.name, user_input)
520
563
 
564
+ for hook in self.pre_hooks:
565
+ self._execute_hook(hook, agent=self, user_input=user_input)
566
+
521
567
  user_content = self._build_user_content(
522
568
  user_input,
523
569
  image_path=image_path,
@@ -526,7 +572,14 @@ class BaseAgent:
526
572
  if user_content is not None:
527
573
  self.add(role="user", content=user_content, addr=addr)
528
574
 
529
- for _ in range(self.config.max_tool_iter):
575
+ accumulated_response: List[str] = []
576
+
577
+ iteration_count = 0
578
+ while True:
579
+ if self.config.max_tool_iter is not None and iteration_count >= self.config.max_tool_iter:
580
+ break
581
+ iteration_count += 1
582
+
530
583
  response_or_stream = self._get_llm_response(addr=addr)
531
584
 
532
585
  current_turn_content_parts: List[str] = []
@@ -540,6 +593,7 @@ class BaseAgent:
540
593
  content_chunk = next(gen)
541
594
  yield content_chunk
542
595
  current_turn_content_parts.append(content_chunk)
596
+ accumulated_response.append(content_chunk)
543
597
  except StopIteration as e:
544
598
  assembled_tool_calls, full_reasoning_content = e.value
545
599
  else:
@@ -549,6 +603,7 @@ class BaseAgent:
549
603
  if content:
550
604
  yield content
551
605
  current_turn_content_parts.append(content)
606
+ accumulated_response.append(content)
552
607
 
553
608
  # Expand tool calls (fix for some models)
554
609
  assembled_tool_calls = self._expand_tool_calls(assembled_tool_calls)
@@ -602,10 +657,17 @@ class BaseAgent:
602
657
  for cb in self.callbacks:
603
658
  cb.on_agent_finish(self.config.name, warn_msg)
604
659
  yield warn_msg
660
+ accumulated_response.append(warn_msg)
661
+
662
+ full_response = "".join(accumulated_response)
663
+ for hook in self.post_hooks:
664
+ self._execute_hook(
665
+ hook, agent=self, user_input=user_input, response=full_response
666
+ )
605
667
 
606
668
  # Public entrypoint
607
669
 
608
- def respond(
670
+ def run(
609
671
  self,
610
672
  user_input: str,
611
673
  *,
@@ -613,7 +675,17 @@ class BaseAgent:
613
675
  image_path: Optional[str] = None,
614
676
  image_detail_override: Optional[str] = None,
615
677
  ) -> Union[str, Generator[str, None, None]]:
616
- """Main entrypoint to interact with the agent."""
678
+ """Main entrypoint to interact with the agent.
679
+
680
+ Args:
681
+ user_input: The text input from the user.
682
+ addr: The memory address for the conversation.
683
+ image_path: Optional path to an image file.
684
+ image_detail_override: Optional detail level for image processing.
685
+
686
+ Returns:
687
+ The agent's response as a string or a generator if streaming is enabled.
688
+ """
617
689
  a = self._addr_or_raise(addr)
618
690
  response_generator = self._execute_agent_loop(
619
691
  user_input,
@@ -628,6 +700,7 @@ class BaseAgent:
628
700
  parts: List[str] = list(response_generator)
629
701
  return "".join(parts).strip()
630
702
 
703
+
631
704
  # Tool registry management
632
705
 
633
706
  def tool_exists(self, name: str) -> bool:
@@ -21,7 +21,7 @@ class AgentConfig:
21
21
  timeout: int = 60
22
22
  stream: bool = False
23
23
  max_retries: int = 3
24
- max_tool_iter: int = 5
24
+ max_tool_iter: Optional[int] = 10
25
25
  reasoning_effort: Optional[str] = None
26
26
  model_kwargs: Optional[Dict[str, Any]] = None
27
27
  client_config_override: Optional[Dict[str, Any]] = None
@@ -0,0 +1,15 @@
1
+ from agentify.extensions.tools.time import TimeTool
2
+ from agentify.extensions.tools.calculator import CalculatorTool
3
+ from agentify.extensions.tools.weather import WeatherTool
4
+ from agentify.extensions.tools.planning import TodoTool
5
+ from agentify.extensions.tools.filesystem import ListDirTool, ReadFileTool, WriteFileTool
6
+
7
+ __all__ = [
8
+ "TimeTool",
9
+ "CalculatorTool",
10
+ "WeatherTool",
11
+ "TodoTool",
12
+ "ListDirTool",
13
+ "ReadFileTool",
14
+ "WriteFileTool",
15
+ ]
@@ -0,0 +1,55 @@
1
+ from agentify.core.tool import Tool
2
+ import ast
3
+ import operator as op
4
+
5
+
6
+ class CalculatorTool(Tool):
7
+ """Tool for evaluating safe mathematical expressions."""
8
+
9
+ def __init__(self):
10
+ self._allowed_ops = {
11
+ ast.Add: op.add,
12
+ ast.Sub: op.sub,
13
+ ast.Mult: op.mul,
14
+ ast.Div: op.truediv,
15
+ ast.Pow: op.pow,
16
+ ast.Mod: op.mod,
17
+ ast.UAdd: op.pos,
18
+ ast.USub: op.neg,
19
+ }
20
+
21
+ schema = {
22
+ "name": "calculate_expression",
23
+ "description": "Evalúa una expresión matemática segura y devuelve el resultado.",
24
+ "parameters": {
25
+ "type": "object",
26
+ "properties": {
27
+ "expression": {
28
+ "type": "string",
29
+ "description": "Expresión matemática a calcular, por ejemplo '2 + 2 * (3 - 1)'.",
30
+ }
31
+ },
32
+ "required": ["expression"],
33
+ },
34
+ }
35
+ super().__init__(schema, self._calculate_expression)
36
+
37
+ def _eval_node(self, node):
38
+ if isinstance(node, ast.Num):
39
+ return node.n
40
+ if isinstance(node, ast.BinOp):
41
+ left = self._eval_node(node.left)
42
+ right = self._eval_node(node.right)
43
+ return self._allowed_ops[type(node.op)](left, right)
44
+ if isinstance(node, ast.UnaryOp):
45
+ operand = self._eval_node(node.operand)
46
+ return self._allowed_ops[type(node.op)](operand)
47
+ raise ValueError(f"Operador no permitido: {node}")
48
+
49
+ def _calculate_expression(self, expression: str):
50
+ try:
51
+ tree = ast.parse(expression, mode="eval").body
52
+ result = self._eval_node(tree)
53
+ return {"result": result}
54
+ except Exception as e:
55
+ return {"error": f"Expresión inválida: {e}"}
@@ -0,0 +1,126 @@
1
+ import os
2
+ from typing import Any, Dict, List, Optional
3
+ from agentify.core.tool import Tool
4
+
5
+ class BaseFilesystemTool(Tool):
6
+ """Base class for filesystem tools with sandbox security."""
7
+
8
+ def __init__(self, schema: Dict[str, Any], func: Any, sandbox_dir: Optional[str] = None):
9
+ super().__init__(schema, func)
10
+ # If no sandbox provided, default to current working directory or a safe temp dir could be better
11
+ # but for this agent library, let's default to CWD but allow override.
12
+ self.sandbox_dir = os.path.abspath(sandbox_dir or os.getcwd())
13
+
14
+ def _validate_path(self, file_path: str) -> str:
15
+ """Ensure path is within sandbox."""
16
+ # Handle absolute paths by checking if they start with sandbox
17
+ abs_path = os.path.abspath(os.path.join(self.sandbox_dir, file_path))
18
+
19
+ if not abs_path.startswith(self.sandbox_dir):
20
+ raise ValueError(f"Access denied: Path '{file_path}' is outside sandbox directory '{self.sandbox_dir}'")
21
+
22
+ return abs_path
23
+
24
+
25
+ class ListDirTool(BaseFilesystemTool):
26
+ def __init__(self, sandbox_dir: Optional[str] = None):
27
+ schema = {
28
+ "name": "list_files",
29
+ "description": "List files and directories in a given path.",
30
+ "parameters": {
31
+ "type": "object",
32
+ "properties": {
33
+ "directory_path": {
34
+ "type": "string",
35
+ "description": "Relative path to list contents of. Defaults to root of sandbox.",
36
+ }
37
+ },
38
+ },
39
+ }
40
+ super().__init__(schema, self._list_dir, sandbox_dir)
41
+
42
+ def _list_dir(self, directory_path: str = ".") -> str:
43
+ try:
44
+ target_path = self._validate_path(directory_path)
45
+ if not os.path.exists(target_path):
46
+ return f"Error: Directory '{directory_path}' does not exist."
47
+
48
+ items = os.listdir(target_path)
49
+ # Add indicators for directories
50
+ formatted_items = []
51
+ for item in items:
52
+ if os.path.isdir(os.path.join(target_path, item)):
53
+ formatted_items.append(f"{item}/")
54
+ else:
55
+ formatted_items.append(item)
56
+
57
+ return "\n".join(formatted_items) if formatted_items else "(empty directory)"
58
+ except Exception as e:
59
+ return f"Error listing directory: {str(e)}"
60
+
61
+
62
+ class ReadFileTool(BaseFilesystemTool):
63
+ def __init__(self, sandbox_dir: Optional[str] = None):
64
+ schema = {
65
+ "name": "read_file",
66
+ "description": "Read the contents of a file.",
67
+ "parameters": {
68
+ "type": "object",
69
+ "properties": {
70
+ "file_path": {
71
+ "type": "string",
72
+ "description": "Path to the file to read.",
73
+ }
74
+ },
75
+ "required": ["file_path"],
76
+ },
77
+ }
78
+ super().__init__(schema, self._read_file, sandbox_dir)
79
+
80
+ def _read_file(self, file_path: str) -> str:
81
+ try:
82
+ target_path = self._validate_path(file_path)
83
+ if not os.path.exists(target_path):
84
+ return f"Error: File '{file_path}' does not exist."
85
+
86
+ with open(target_path, "r", encoding="utf-8") as f:
87
+ return f.read()
88
+ except Exception as e:
89
+ return f"Error reading file: {str(e)}"
90
+
91
+
92
+ class WriteFileTool(BaseFilesystemTool):
93
+ def __init__(self, sandbox_dir: Optional[str] = None):
94
+ schema = {
95
+ "name": "write_file",
96
+ "description": "Write content to a file. Overwrites if exists.",
97
+ "parameters": {
98
+ "type": "object",
99
+ "properties": {
100
+ "file_path": {
101
+ "type": "string",
102
+ "description": "Path to the file to write.",
103
+ },
104
+ "content": {
105
+ "type": "string",
106
+ "description": "Content to write to the file.",
107
+ }
108
+ },
109
+ "required": ["file_path", "content"],
110
+ },
111
+ }
112
+ super().__init__(schema, self._write_file, sandbox_dir)
113
+
114
+ def _write_file(self, file_path: str, content: str) -> str:
115
+ try:
116
+ target_path = self._validate_path(file_path)
117
+
118
+ # Ensure directory exists
119
+ os.makedirs(os.path.dirname(target_path), exist_ok=True)
120
+
121
+ with open(target_path, "w", encoding="utf-8") as f:
122
+ f.write(content)
123
+
124
+ return f"Successfully wrote to '{file_path}'."
125
+ except Exception as e:
126
+ return f"Error writing file: {str(e)}"
@@ -0,0 +1,76 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from agentify.core.tool import Tool
3
+
4
+ class TodoTool(Tool):
5
+ """A tool for agents to manage their own todo list / plan."""
6
+
7
+ def __init__(self):
8
+ self._todos: List[Dict[str, Any]] = []
9
+ schema = {
10
+ "name": "manage_plan",
11
+ "description": "Manage a todo list to plan and track progress.",
12
+ "parameters": {
13
+ "type": "object",
14
+ "properties": {
15
+ "action": {
16
+ "type": "string",
17
+ "enum": ["add", "complete", "list", "remove"],
18
+ "description": "Action to perform on the plan.",
19
+ },
20
+ "task": {
21
+ "type": "string",
22
+ "description": "Description of the task (required for 'add')",
23
+ },
24
+ "task_id": {
25
+ "type": "integer",
26
+ "description": "ID of the task (required for 'complete' or 'remove')",
27
+ },
28
+ },
29
+ "required": ["action"],
30
+ },
31
+ }
32
+ super().__init__(schema, self._manage_todos)
33
+
34
+ def _manage_todos(
35
+ self,
36
+ action: str,
37
+ task: Optional[str] = None,
38
+ task_id: Optional[int] = None
39
+ ) -> str:
40
+ if action == "add":
41
+ if not task:
42
+ return "Error: 'task' description required for 'add' action."
43
+ new_id = len(self._todos)
44
+ self._todos.append({"id": new_id, "task": task, "status": "pending"})
45
+ return f"Task added: [{new_id}] {task}"
46
+
47
+ elif action == "complete":
48
+ if task_id is None:
49
+ return "Error: 'task_id' required for 'complete' action."
50
+ if 0 <= task_id < len(self._todos):
51
+ self._todos[task_id]["status"] = "completed"
52
+ return f"Task [{task_id}] marked as completed."
53
+ return f"Error: Invalid task_id {task_id}"
54
+
55
+ elif action == "remove":
56
+ if task_id is None:
57
+ return "Error: 'task_id' required for 'remove' action."
58
+ if 0 <= task_id < len(self._todos):
59
+ # Rebuild list without the specified task_id
60
+ self._todos = [t for i, t in enumerate(self._todos) if i != task_id]
61
+ # Re-assign IDs to keep them sequential
62
+ for i, t in enumerate(self._todos):
63
+ t["id"] = i
64
+ return f"Task removed. Remaining tasks re-indexed."
65
+ return f"Error: Invalid task_id {task_id}"
66
+
67
+ elif action == "list":
68
+ if not self._todos:
69
+ return "Plan is empty."
70
+ lines = []
71
+ for t in self._todos:
72
+ status = "[x]" if t["status"] == "completed" else "[ ]"
73
+ lines.append(f"{status} {t['id']}: {t['task']}")
74
+ return "\n".join(lines)
75
+
76
+ return f"Error: Unknown action '{action}'"
@@ -0,0 +1,22 @@
1
+ from agentify.core.tool import Tool
2
+ import datetime
3
+
4
+
5
+ class TimeTool(Tool):
6
+ """Tool for getting current date and time."""
7
+
8
+ def __init__(self):
9
+ schema = {
10
+ "name": "get_current_time",
11
+ "description": "Devuelve la hora y fecha actual en formato ISO 8601.",
12
+ "parameters": {
13
+ "type": "object",
14
+ "properties": {},
15
+ "required": [],
16
+ },
17
+ }
18
+ super().__init__(schema, self._get_current_time)
19
+
20
+ def _get_current_time(self):
21
+ now = datetime.datetime.now().astimezone().isoformat()
22
+ return {"current_time": now}
@@ -0,0 +1,52 @@
1
+ from agentify.core.tool import Tool
2
+ import os
3
+ import requests
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+
9
+ class WeatherTool(Tool):
10
+ """Tool for getting current weather information."""
11
+
12
+ def __init__(self):
13
+ schema = {
14
+ "name": "get_weather",
15
+ "description": "Obtiene el estado del tiempo o clima actual para una ciudad o zona especificada.",
16
+ "parameters": {
17
+ "type": "object",
18
+ "properties": {
19
+ "location": {
20
+ "type": "string",
21
+ "description": "Nombre de la ciudad o zona para consultar el clima.",
22
+ }
23
+ },
24
+ "required": ["location"],
25
+ },
26
+ }
27
+ super().__init__(schema, self._get_weather)
28
+
29
+ def _get_weather(self, location: str):
30
+ api_key = os.getenv("OPENWEATHER_API_KEY")
31
+ if not api_key:
32
+ return {"error": "Variable de entorno OPENWEATHER_API_KEY no configurada."}
33
+ try:
34
+ response = requests.get(
35
+ "https://api.openweathermap.org/data/2.5/weather",
36
+ params={"q": location, "appid": api_key, "units": "metric"},
37
+ )
38
+ data = response.json()
39
+ if response.status_code != 200:
40
+ return {
41
+ "error": data.get("message", "Error desconocido al obtener el clima.")
42
+ }
43
+ weather = {
44
+ "location": data["name"],
45
+ "description": data["weather"][0]["description"],
46
+ "temperature": data["main"]["temp"],
47
+ "humidity": data["main"]["humidity"],
48
+ "wind_speed": data["wind"]["speed"],
49
+ }
50
+ return {"weather": weather}
51
+ except Exception as e:
52
+ return {"error": f"Error al conectar con el servicio de clima: {e}"}
@@ -99,3 +99,13 @@ class ConversationStore(Protocol):
99
99
  class TokenCounter(Protocol):
100
100
  """Callable that returns token count for OpenAI-formatted messages."""
101
101
  def __call__(self, openai_messages: List[Dict[str, Any]]) -> int: ...
102
+
103
+
104
+ class KeyValueStore(Protocol):
105
+ """
106
+ Interface for long-term semantic memory or global state.
107
+ """
108
+ def get(self, key: str) -> Optional[Any]: ...
109
+ def set(self, key: str, value: Any) -> None: ...
110
+ def delete(self, key: str) -> None: ...
111
+ def search(self, query: str, limit: int = 5) -> List[Any]: ...
@@ -0,0 +1,5 @@
1
+ """Memory storage backends."""
2
+ from .in_memory_store import InMemoryStore
3
+ from .redis_store import RedisStore
4
+
5
+ __all__ = ["InMemoryStore", "RedisStore"]
@@ -44,7 +44,7 @@ class HierarchicalTeam:
44
44
  self._register_hierarchy_tools(session_id, user_id)
45
45
 
46
46
  # 3. Run Root
47
- return self.root.respond(user_input=user_input, addr=root_addr)
47
+ return self.root.run(user_input=user_input, addr=root_addr)
48
48
 
49
49
  def _register_hierarchy_tools(self, session_id: str, user_id: str) -> None:
50
50
  """Registers children as tools for their parents based on the current session."""
@@ -1,4 +1,5 @@
1
1
  from typing import List, Union, Generator, Any
2
+
2
3
  from agentify.core.agent import BaseAgent
3
4
  from agentify.memory.interfaces import MemoryAddress
4
5
  from agentify.multi_agent.team import Team
@@ -43,7 +44,7 @@ class SequentialPipeline:
43
44
  step_addr = MemoryAddress(
44
45
  user_id=user_id, conversation_id=session_id, agent_id=step_name
45
46
  )
46
- response = step.respond(user_input=current_input, addr=step_addr)
47
+ response = step.run(user_input=current_input, addr=step_addr)
47
48
 
48
49
  elif hasattr(step, "run"):
49
50
  # Team, SequentialPipeline, HierarchicalTeam
@@ -59,4 +59,4 @@ class Team:
59
59
  self.supervisor.register_tool(tool_wrapper)
60
60
 
61
61
  # 3. Run Supervisor
62
- return self.supervisor.respond(user_input=user_input, addr=supervisor_addr)
62
+ return self.supervisor.run(user_input=user_input, addr=supervisor_addr)
@@ -57,7 +57,7 @@ class AgentTool(Tool):
57
57
  )
58
58
 
59
59
  # Run the agent
60
- response = self.agent.respond(user_input=instructions, addr=child_addr)
60
+ response = self.agent.run(user_input=instructions, addr=child_addr)
61
61
 
62
62
  # Consume generator if needed
63
63
  if hasattr(response, "__iter__") and not isinstance(response, str):
@@ -122,3 +122,88 @@ class FlowTool(Tool):
122
122
  response = "".join(list(response))
123
123
 
124
124
  return {"response": response}
125
+
126
+
127
+ class SpawnAgentTool(Tool):
128
+ """Tool to dynamically spawn a transient sub-agent for a specific task."""
129
+
130
+ def __init__(
131
+ self,
132
+ base_config: Any, # AgentConfig type ideally, but Any to avoid circular imports context
133
+ memory_service: Any, # MemoryService
134
+ parent_addr: MemoryAddress,
135
+ client_factory: Optional[Any] = None,
136
+ ):
137
+ self.base_config = base_config
138
+ self.memory_service = memory_service
139
+ self.parent_addr = parent_addr
140
+ self.client_factory = client_factory
141
+
142
+ schema = {
143
+ "name": "spawn_subagent",
144
+ "description": "Spawn a temporary specialized sub-agent to handle a complex sub-task.",
145
+ "parameters": {
146
+ "type": "object",
147
+ "properties": {
148
+ "role_name": {
149
+ "type": "string",
150
+ "description": "Name of the sub-agent (e.g., 'ResearchAssistant').",
151
+ },
152
+ "instructions": {
153
+ "type": "string",
154
+ "description": "Specific task instructions for the sub-agent.",
155
+ },
156
+ "system_prompt": {
157
+ "type": "string",
158
+ "description": "System prompt defining the sub-agent's persona and constraints.",
159
+ }
160
+ },
161
+ "required": ["role_name", "instructions"],
162
+ },
163
+ }
164
+ super().__init__(schema, self._spawn_and_run)
165
+
166
+ def _spawn_and_run(
167
+ self,
168
+ role_name: str,
169
+ instructions: str,
170
+ system_prompt: Optional[str] = None
171
+ ) -> Dict[str, Any]:
172
+ """Creates and runs a new agent instance."""
173
+ from agentify.core.agent import BaseAgent
174
+ from agentify.core.config import AgentConfig
175
+ import copy
176
+
177
+ # Clone config but override name and system prompt
178
+ # Assuming base_config is an AgentConfig object or similar dataclass
179
+ new_config = copy.deepcopy(self.base_config)
180
+ new_config.name = f"{self.base_config.name}.{role_name}"
181
+ if system_prompt:
182
+ new_config.system_prompt = system_prompt
183
+
184
+ # Create a unique address for this interaction
185
+ child_addr = MemoryAddress(
186
+ user_id=self.parent_addr.user_id,
187
+ conversation_id=f"{self.parent_addr.conversation_id}_{role_name}_{instructions[:10]}", # Unique-ish
188
+ agent_id=new_config.name,
189
+ )
190
+
191
+ # Create the agent
192
+ sub_agent = BaseAgent(
193
+ config=new_config,
194
+ memory=self.memory_service,
195
+ memory_address=child_addr,
196
+ client_factory=self.client_factory
197
+ )
198
+
199
+ response = sub_agent.run(user_input=instructions)
200
+
201
+ # Consume generator if needed
202
+ if hasattr(response, "__iter__") and not isinstance(response, str):
203
+ response = "".join(list(response))
204
+
205
+ return {
206
+ "subagent": role_name,
207
+ "status": "finished",
208
+ "response": response
209
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentify-core
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Framework-agnostic AI agent library for building single and multi-agent systems
5
5
  Author-email: Fabian M <fabian@example.com>
6
6
  License: MIT
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
- Requires-Python: >=3.9
22
+ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: openai>=1.0.0
@@ -47,9 +47,10 @@ Dynamic: license-file
47
47
 
48
48
  # Agentify
49
49
 
50
- **Framework-agnostic AI agent library for building single and multi-agent systems**
50
+ **Independent AI agent library based on the OpenAI SDK**
51
+
52
+ Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It targets the OpenAI-compatible Chat Completions interface, enabling support for multiple providers through a configurable `base_url` (OpenAI, Azure OpenAI, DeepSeek, Gemini, etc.). Agentify offers a streamlined, independent set of primitives for memory, tools, and coordination so you can focus on product logic without being tied to heavy frameworks.
51
53
 
52
- Agentify is a Python library for building and orchestrating AI agents, from simple assistants to complex multi-agent systems. It focuses on a small set of composable primitives for LLM integration, memory, tools and coordination, so you can focus on product logic instead of framework details.
53
54
 
54
55
  ## Why Agentify?
55
56
 
@@ -116,7 +117,7 @@ agent = BaseAgent(
116
117
  )
117
118
 
118
119
  # 3. Run a conversation
119
- response = agent.respond(user_input="Hello! How can you help me?")
120
+ response = agent.run(user_input="Hello! How can you help me?")
120
121
  ```
121
122
 
122
123
  ## Composable Flows
@@ -133,6 +134,15 @@ Because all flows share the same `run()` interface, you can build Teams made of
133
134
  Agentify supports both **strict workflows** (fixed, pre-defined Pipelines and Hierarchies) and **dynamic agentic flows**, where a supervisor/router agent decides at runtime which agent, Team or Pipeline to call next.
134
135
 
135
136
 
137
+ ## Documentation
138
+
139
+ - [Getting Started](docs/getting_started.md) - Installation and first steps
140
+ - [Core Concepts](docs/core_concepts.md) - Agents, memory, and tools
141
+ - [Multi-Agent Systems](docs/multi_agent.md) - Teams, pipelines, and hierarchies
142
+ - [Advanced Features](docs/advanced.md) - Vision, streaming, hooks, and more
143
+ - [API Reference](docs/api_reference.md) - Complete API documentation
144
+
145
+
136
146
  ### More Examples
137
147
 
138
148
  Check out the [examples](examples/) directory for detailed implementations:
@@ -14,6 +14,8 @@ agentify/extensions/prompts/__init__.py
14
14
  agentify/extensions/prompts/assistant.py
15
15
  agentify/extensions/tools/__init__.py
16
16
  agentify/extensions/tools/calculator.py
17
+ agentify/extensions/tools/filesystem.py
18
+ agentify/extensions/tools/planning.py
17
19
  agentify/extensions/tools/time.py
18
20
  agentify/extensions/tools/weather.py
19
21
  agentify/llm/__init__.py
@@ -36,4 +38,6 @@ agentify_core.egg-info/PKG-INFO
36
38
  agentify_core.egg-info/SOURCES.txt
37
39
  agentify_core.egg-info/dependency_links.txt
38
40
  agentify_core.egg-info/requires.txt
39
- agentify_core.egg-info/top_level.txt
41
+ agentify_core.egg-info/top_level.txt
42
+ tests/test_filesystem_tools.py
43
+ tests/test_planning_tool.py
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentify-core"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Framework-agnostic AI agent library for building single and multi-agent systems"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  license = {text = "MIT"}
12
12
  authors = [
13
13
  {name = "Fabian M", email = "fabian@example.com"}
@@ -83,10 +83,10 @@ agentify = ["py.typed"]
83
83
 
84
84
  [tool.black]
85
85
  line-length = 100
86
- target-version = ['py39', 'py310', 'py311']
86
+ target-version = ['py310', 'py311']
87
87
 
88
88
  [tool.mypy]
89
- python_version = "3.9"
89
+ python_version = "3.10"
90
90
  warn_return_any = true
91
91
  warn_unused_configs = true
92
92
  disallow_untyped_defs = false
@@ -0,0 +1,62 @@
1
+ import os
2
+ import tempfile
3
+ import pytest
4
+ from agentify.tools.filesystem import ListDirTool, ReadFileTool, WriteFileTool
5
+
6
+
7
+ class TestFilesystemTools:
8
+ """Test suite for filesystem tools."""
9
+
10
+ def test_list_dir_tool(self):
11
+ """Test ListDirTool enumerates directory contents."""
12
+ with tempfile.TemporaryDirectory() as tmpdir:
13
+ # Create test files
14
+ open(os.path.join(tmpdir, "file1.txt"), "w").close()
15
+ open(os.path.join(tmpdir, "file2.txt"), "w").close()
16
+ os.makedirs(os.path.join(tmpdir, "subdir"))
17
+
18
+ tool = ListDirTool(sandbox_dir=tmpdir)
19
+ result = tool._list_dir(".")
20
+
21
+ assert "file1.txt" in result
22
+ assert "file2.txt" in result
23
+ assert "subdir/" in result
24
+
25
+ def test_read_file_tool(self):
26
+ """Test ReadFileTool reads file contents."""
27
+ with tempfile.TemporaryDirectory() as tmpdir:
28
+ test_file = os.path.join(tmpdir, "test.txt")
29
+ test_content = "Hello, Deep Agent!"
30
+
31
+ with open(test_file, "w") as f:
32
+ f.write(test_content)
33
+
34
+ tool = ReadFileTool(sandbox_dir=tmpdir)
35
+ result = tool._read_file("test.txt")
36
+
37
+ assert result == test_content
38
+
39
+ def test_write_file_tool(self):
40
+ """Test WriteFileTool creates and writes files."""
41
+ with tempfile.TemporaryDirectory() as tmpdir:
42
+ tool = WriteFileTool(sandbox_dir=tmpdir)
43
+ result = tool._write_file("output.txt", "Test content")
44
+
45
+ assert "Successfully wrote" in result
46
+
47
+ # Verify file exists and has correct content
48
+ with open(os.path.join(tmpdir, "output.txt"), "r") as f:
49
+ assert f.read() == "Test content"
50
+
51
+ def test_sandbox_security(self):
52
+ """Test that sandbox prevents access outside its boundaries."""
53
+ with tempfile.TemporaryDirectory() as tmpdir:
54
+ tool = ReadFileTool(sandbox_dir=tmpdir)
55
+
56
+ # Try to read outside sandbox
57
+ result = tool._read_file("../../etc/passwd")
58
+ assert "Access denied" in result or "Error" in result
59
+
60
+
61
+ if __name__ == "__main__":
62
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,55 @@
1
+ import pytest
2
+ from agentify.tools.planning import TodoTool
3
+
4
+
5
+ class TestPlanningTool:
6
+ """Test suite for planning tool."""
7
+
8
+ def test_add_task(self):
9
+ """Test adding tasks to the plan."""
10
+ tool = TodoTool()
11
+ result = tool._manage_todos(action="add", task="Complete the demo")
12
+
13
+ assert "Task added" in result
14
+ assert "[0]" in result
15
+ assert "Complete the demo" in result
16
+
17
+ def test_list_tasks(self):
18
+ """Test listing all tasks."""
19
+ tool = TodoTool()
20
+ tool._manage_todos(action="add", task="Task 1")
21
+ tool._manage_todos(action="add", task="Task 2")
22
+
23
+ result = tool._manage_todos(action="list")
24
+
25
+ assert "Task 1" in result
26
+ assert "Task 2" in result
27
+ assert "[ ]" in result # Pending status
28
+
29
+ def test_complete_task(self):
30
+ """Test marking a task as complete."""
31
+ tool = TodoTool()
32
+ tool._manage_todos(action="add", task="Do something")
33
+
34
+ result = tool._manage_todos(action="complete", task_id=0)
35
+ assert "marked as completed" in result
36
+
37
+ list_result = tool._manage_todos(action="list")
38
+ assert "[x]" in list_result # Completed status
39
+
40
+ def test_remove_task(self):
41
+ """Test removing a task."""
42
+ tool = TodoTool()
43
+ tool._manage_todos(action="add", task="Task to remove")
44
+ tool._manage_todos(action="add", task="Task to keep")
45
+
46
+ result = tool._manage_todos(action="remove", task_id=0)
47
+ assert "removed" in result
48
+
49
+ list_result = tool._manage_todos(action="list")
50
+ assert "Task to remove" not in list_result
51
+ assert "Task to keep" in list_result
52
+
53
+
54
+ if __name__ == "__main__":
55
+ pytest.main([__file__, "-v"])
@@ -1,9 +0,0 @@
1
- from agentify.extensions.tools.time import get_current_time_tool
2
- from agentify.extensions.tools.calculator import calculate_expression_tool
3
- from agentify.extensions.tools.weather import get_weather_tool
4
-
5
- __all__ = [
6
- "get_current_time_tool",
7
- "calculate_expression_tool",
8
- "get_weather_tool",
9
- ]
@@ -1,56 +0,0 @@
1
- from agentify.core.tool import Tool
2
- import ast
3
- import operator as op
4
-
5
-
6
- calculate_expression_schema = {
7
- "name": "calculate_expression",
8
- "description": "Evalúa una expresión matemática segura y devuelve el resultado.",
9
- "parameters": {
10
- "type": "object",
11
- "properties": {
12
- "expression": {
13
- "type": "string",
14
- "description": "Expresión matemática a calcular, por ejemplo '2 + 2 * (3 - 1)'.",
15
- }
16
- },
17
- "required": ["expression"],
18
- },
19
- }
20
-
21
-
22
- _allowed_ops = {
23
- ast.Add: op.add,
24
- ast.Sub: op.sub,
25
- ast.Mult: op.mul,
26
- ast.Div: op.truediv,
27
- ast.Pow: op.pow,
28
- ast.Mod: op.mod,
29
- ast.UAdd: op.pos,
30
- ast.USub: op.neg,
31
- }
32
-
33
-
34
- def _eval_node(node):
35
- if isinstance(node, ast.Num):
36
- return node.n
37
- if isinstance(node, ast.BinOp):
38
- left = _eval_node(node.left)
39
- right = _eval_node(node.right)
40
- return _allowed_ops[type(node.op)](left, right)
41
- if isinstance(node, ast.UnaryOp):
42
- operand = _eval_node(node.operand)
43
- return _allowed_ops[type(node.op)](operand)
44
- raise ValueError(f"Operador no permitido: {node}")
45
-
46
-
47
- def calculate_expression(expression: str):
48
- try:
49
- tree = ast.parse(expression, mode="eval").body
50
- result = _eval_node(tree)
51
- return {"result": result}
52
- except Exception as e:
53
- return {"error": f"Expresión inválida: {e}"}
54
-
55
-
56
- calculate_expression_tool = Tool(calculate_expression_schema, calculate_expression)
@@ -1,21 +0,0 @@
1
- from agentify.core.tool import Tool
2
- import datetime
3
-
4
-
5
- get_current_time_schema = {
6
- "name": "get_current_time",
7
- "description": "Devuelve la hora y fecha actual en formato ISO 8601.",
8
- "parameters": {
9
- "type": "object",
10
- "properties": {},
11
- "required": [],
12
- },
13
- }
14
-
15
-
16
- def get_current_time():
17
- now = datetime.datetime.now().astimezone().isoformat()
18
- return {"current_time": now}
19
-
20
-
21
- get_current_time_tool = Tool(get_current_time_schema, get_current_time)
@@ -1,51 +0,0 @@
1
- from agentify.core.tool import Tool
2
- import os
3
- import requests
4
- from dotenv import load_dotenv
5
-
6
- load_dotenv()
7
-
8
-
9
- get_weather_schema = {
10
- "name": "get_weather",
11
- "description": "Obtiene el estado del tiempo o clima actual para una ciudad o zona especificada.",
12
- "parameters": {
13
- "type": "object",
14
- "properties": {
15
- "location": {
16
- "type": "string",
17
- "description": "Nombre de la ciudad o zona para consultar el clima.",
18
- }
19
- },
20
- "required": ["location"],
21
- },
22
- }
23
-
24
-
25
- def get_weather(location: str):
26
- api_key = os.getenv("OPENWEATHER_API_KEY")
27
- if not api_key:
28
- return {"error": "Variable de entorno OPENWEATHER_API_KEY no configurada."}
29
- try:
30
- response = requests.get(
31
- "https://api.openweathermap.org/data/2.5/weather",
32
- params={"q": location, "appid": api_key, "units": "metric"},
33
- )
34
- data = response.json()
35
- if response.status_code != 200:
36
- return {
37
- "error": data.get("message", "Error desconocido al obtener el clima.")
38
- }
39
- weather = {
40
- "location": data["name"],
41
- "description": data["weather"][0]["description"],
42
- "temperature": data["main"]["temp"],
43
- "humidity": data["main"]["humidity"],
44
- "wind_speed": data["wind"]["speed"],
45
- }
46
- return {"weather": weather}
47
- except Exception as e:
48
- return {"error": f"Error al conectar con el servicio de clima: {e}"}
49
-
50
-
51
- get_weather_tool = Tool(get_weather_schema, get_weather)
@@ -1,6 +0,0 @@
1
- """Memory storage backends."""
2
-
3
- from agentify.memory.stores.in_memory_store import InMemoryStore
4
- from agentify.memory.stores.redis_store import RedisStore
5
-
6
- __all__ = ["InMemoryStore", "RedisStore"]
File without changes
File without changes
File without changes