gofannon 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.
gofannon/__init__.py ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ from.search import Search
2
+ from.get_article import GetArticle
@@ -0,0 +1,41 @@
1
+ from..base import BaseTool
2
+ import requests
3
+ from ..config import FunctionRegistry
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ @FunctionRegistry.register
9
+ class GetArticle(BaseTool):
10
+ def __init__(self, name="get_article"):
11
+ super().__init__()
12
+ self.name = name
13
+
14
+ @property
15
+ def definition(self):
16
+ return {
17
+ "type": "function",
18
+ "function": {
19
+ "name": self.name,
20
+ "description": "Get a specific article from arXiv",
21
+ "parameters": {
22
+ "type": "object",
23
+ "properties": {
24
+ "id": {
25
+ "type": "string",
26
+ "description": "The ID of the article"
27
+ }
28
+ },
29
+ "required": ["id"]
30
+ }
31
+ }
32
+ }
33
+
34
+ def fn(self, id):
35
+ logger.debug("Fetching Article '%s' from ArXiv", id)
36
+ base_url = "http://export.arxiv.org/api/query"
37
+ params = {
38
+ "id_list": id
39
+ }
40
+ response = requests.get(base_url, params=params)
41
+ return response.text
@@ -0,0 +1,115 @@
1
+
2
+ from..base import BaseTool
3
+ import requests
4
+ from ..config import FunctionRegistry
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ @FunctionRegistry.register
10
+ class Search(BaseTool):
11
+ def __init__(self, name="search"):
12
+ super().__init__()
13
+ self.name = name
14
+
15
+ @property
16
+ def definition(self):
17
+ return {
18
+ "type": "function",
19
+ "function": {
20
+ "name": self.name,
21
+ "description": "Search for articles on arXiv",
22
+ "parameters": {
23
+ "type": "object",
24
+ "properties": {
25
+ "query": {
26
+ "type": "string",
27
+ "description": "The search query"
28
+ },
29
+ "start": {
30
+ "type": "integer",
31
+ "description": "The start index of the search results"
32
+ },
33
+ "max_results": {
34
+ "type": "integer",
35
+ "description": "The maximum number of search results to return"
36
+ },
37
+ "submittedDateFrom": {
38
+ "type": "string",
39
+ "description": "The start submission date in the format YYYYMMDD"
40
+ },
41
+ "submittedDateTo": {
42
+ "type": "string",
43
+ "description": "The end submission date in the format YYYYMMDD"
44
+ },
45
+ "ti": {
46
+ "type": "string",
47
+ "description": "Search in title"
48
+ },
49
+ "au": {
50
+ "type": "string",
51
+ "description": "Search in author"
52
+ },
53
+ "abs": {
54
+ "type": "string",
55
+ "description": "Search in abstract"
56
+ },
57
+ "co": {
58
+ "type": "string",
59
+ "description": "Search in comment"
60
+ },
61
+ "jr": {
62
+ "type": "string",
63
+ "description": "Search in journal reference"
64
+ },
65
+ "cat": {
66
+ "type": "string",
67
+ "description": "Search in subject category"
68
+ }
69
+ },
70
+ "required": ["query"]
71
+ }
72
+ }
73
+ }
74
+
75
+ def _format_date(self, date):
76
+ if len(date) == 8:
77
+ return f"{date}0000"
78
+ return date
79
+
80
+ def fn(self, query, start=0, max_results=10, submittedDateFrom=None, submittedDateTo=None, ti=None, au=None, abs=None, co=None, jr=None, cat=None):
81
+ logger.debug("Querying ArXiv for '%s'", query)
82
+ base_url = "http://export.arxiv.org/api/query"
83
+ params = {
84
+ "search_query": query,
85
+ "start": start,
86
+ "max_results": max_results
87
+ }
88
+
89
+ if submittedDateFrom and submittedDateTo:
90
+ params["search_query"] += f" AND submittedDate:[{self._format_date(submittedDateFrom)}0000 TO {self._format_date(submittedDateTo)}0000]"
91
+ elif submittedDateFrom:
92
+ params["search_query"] += f" AND submittedDate:[{self._format_date(submittedDateFrom)}0000 TO *]"
93
+ elif submittedDateTo:
94
+ params["search_query"] += f" AND submittedDate:[* TO {self._format_date(submittedDateTo)}0000]"
95
+
96
+ if ti:
97
+ params["search_query"] += f" AND ti:{ti}"
98
+
99
+ if au:
100
+ params["search_query"] += f" AND au:{au}"
101
+
102
+ if abs:
103
+ params["search_query"] += f" AND abs:{abs}"
104
+
105
+ if co:
106
+ params["search_query"] += f" AND co:{co}"
107
+
108
+ if jr:
109
+ params["search_query"] += f" AND jr:{jr}"
110
+
111
+ if cat:
112
+ params["search_query"] += f" AND cat:{cat}"
113
+
114
+ response = requests.get(base_url, params=params)
115
+ return response.text
@@ -0,0 +1,251 @@
1
+ import time
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from typing import Dict, Any, Callable
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+ from ..config import ToolConfig
9
+
10
+ from typing import Any, Dict
11
+
12
+
13
+ try:
14
+ from smolagents.tools import Tool as SmolTool
15
+ from smolagents.tools import tool as smol_tool_decorator
16
+ _HAS_SMOLAGENTS = True
17
+ except ImportError:
18
+ _HAS_SMOLAGENTS = False
19
+
20
+ try:
21
+ from langchain.tools import BaseTool as LangchainBaseTool
22
+ from langchain.pydantic_v1 import BaseModel, Field
23
+ from typing import Type, Optional
24
+ _HAS_LANGCHAIN = True
25
+ except ImportError:
26
+ _HAS_LANGCHAIN = False
27
+
28
+ @dataclass
29
+ class ToolResult:
30
+ success: bool
31
+ output: Any
32
+ error: str = None
33
+ retryable: bool = False
34
+
35
+ class WorkflowContext:
36
+ def __init__(self, firebase_config=None):
37
+ self.data = {}
38
+ self.execution_log = []
39
+ self.firebase_config = firebase_config
40
+ self.local_storage = Path.home() / ".llama" / "checkpoints"
41
+ self.local_storage.mkdir(parents=True, exist_ok=True)
42
+
43
+ def save_checkpoint(self, name="checkpoint"):
44
+ if self.firebase_config:
45
+ self._save_to_firebase(name)
46
+ else:
47
+ self._save_local(name)
48
+
49
+ def _save_local(self, name):
50
+ path = self.local_storage / f"{name}.json"
51
+ with open(path, 'w') as f:
52
+ json.dump({
53
+ 'data': self.data,
54
+ 'execution_log': self.execution_log
55
+ }, f)
56
+
57
+ def _save_to_firebase(self, name):
58
+ from firebase_admin import firestore
59
+ db = firestore.client()
60
+ doc_ref = db.collection('checkpoints').document(name)
61
+ doc_ref.set({
62
+ 'data': self.data,
63
+ 'execution_log': self.execution_log,
64
+ 'timestamp': firestore.SERVER_TIMESTAMP
65
+ })
66
+
67
+ def log_execution(self, tool_name, duration, input_data, output_data):
68
+ entry = {
69
+ 'tool': tool_name,
70
+ 'duration': duration,
71
+ 'input': input_data,
72
+ 'output': output_data
73
+ }
74
+ self.execution_log.append(entry)
75
+
76
+ class BaseTool(ABC):
77
+ def __init__(self, **kwargs):
78
+ self.logger = logging.getLogger(f"{self.__class__.__module__}.{self.__class__.__name__}")
79
+ self._load_config()
80
+ self._configure(**kwargs)
81
+ self.logger.debug("Initialized %s tool", self.__class__.__name__)
82
+
83
+ def _configure(self, **kwargs):
84
+ """Set instance-specific configurations"""
85
+ for key, value in kwargs.items():
86
+ if hasattr(self, key):
87
+ setattr(self, key, value)
88
+
89
+ def _load_config(self):
90
+ """Auto-load config based on tool type"""
91
+ if hasattr(self, 'API_SERVICE'):
92
+ self.api_key = ToolConfig.get(f"{self.API_SERVICE}_api_key")
93
+
94
+ @property
95
+ @abstractmethod
96
+ def definition(self):
97
+ pass
98
+
99
+ @property
100
+ def output_schema(self):
101
+ return self.definition.get('function', {}).get('parameters', {})
102
+
103
+ @abstractmethod
104
+ def fn(self, *args, **kwargs):
105
+ pass
106
+
107
+ def execute(self, context: WorkflowContext, **kwargs) -> ToolResult:
108
+ try:
109
+ start_time = time.time()
110
+ result = self.fn(**kwargs)
111
+ duration = time.time() - start_time
112
+
113
+ context.log_execution(
114
+ tool_name=self.__class__.__name__,
115
+ duration=duration,
116
+ input_data=kwargs,
117
+ output_data=result
118
+ )
119
+
120
+ return ToolResult(success=True, output=result)
121
+ except Exception as e:
122
+ return ToolResult(
123
+ success=False,
124
+ output=None,
125
+ error=str(e),
126
+ retryable=True
127
+ )
128
+
129
+ def import_from_smolagents(self, smol_tool: "SmolTool"):
130
+ """
131
+ Takes a smolagents Tool instance and adapts it into this Tool.
132
+ """
133
+ if not _HAS_SMOLAGENTS:
134
+ raise RuntimeError(
135
+ "smolagents is not installed or could not be imported. "
136
+ "Install it or check your environment."
137
+ )
138
+ self.name = smol_tool.name[0]
139
+ self.description = smol_tool.description #getattr(smol_tool, "description", "No description provided.")
140
+
141
+
142
+ def adapted_fn(*args, **kwargs):
143
+ return smol_tool.forward(*args, **kwargs)
144
+
145
+
146
+ self.fn = adapted_fn
147
+
148
+ def export_to_smolagents(self) -> "SmolTool":
149
+ """
150
+ Export this Tool as a smolagents Tool instance.
151
+ This sets up a smolagents-style forward method that calls self.fn.
152
+ """
153
+ if not _HAS_SMOLAGENTS:
154
+ raise RuntimeError(
155
+ "smolagents is not installed or could not be imported. "
156
+ "Install it or check your environment."
157
+ )
158
+
159
+ # Provide a standard forward function that calls self.fn
160
+ def smol_forward(*args, **kwargs):
161
+ return self.fn(*args, **kwargs)
162
+
163
+
164
+ inputs_definition = {
165
+ "example_arg": {
166
+ "type": "string",
167
+ "description": "Example argument recognized by this tool"
168
+ }
169
+ }
170
+ output_type = "string"
171
+
172
+ # Construct a new smolagents Tool with the minimal fields
173
+ exported_tool = SmolTool()
174
+ exported_tool.name = getattr(self, "name", "exported_base_tool")
175
+ exported_tool.description = getattr(self, "description", "Exported from Tool")
176
+ exported_tool.inputs = inputs_definition
177
+ exported_tool.output_type = output_type
178
+ exported_tool.forward = smol_forward
179
+ exported_tool.is_initialized = True
180
+
181
+ return exported_tool
182
+
183
+ def import_from_langchain(self, langchain_tool: "LangchainBaseTool"):
184
+ if not _HAS_LANGCHAIN:
185
+ raise RuntimeError("langchain is not installed. Install with `pip install langchain-core`")
186
+
187
+ self.name = getattr(langchain_tool, "name", "exported_langchain_tool")
188
+ self.description = getattr(langchain_tool, "description", "No description provided.")
189
+
190
+ maybe_args_schema = getattr(langchain_tool, "args_schema", None)
191
+ if maybe_args_schema and hasattr(maybe_args_schema, "schema") and callable(maybe_args_schema.schema):
192
+ args_schema = maybe_args_schema.schema()
193
+ else:
194
+ args_schema = {}
195
+
196
+ # Store parameters to avoid modifying the definition property directly
197
+ self._parameters = args_schema.get("properties", {})
198
+ self._required = args_schema.get("required", [])
199
+
200
+ # Adapt the LangChain tool's execution method
201
+ def adapted_fn(*args, **kwargs):
202
+ return langchain_tool._run(*args, **kwargs)
203
+
204
+ self.fn = adapted_fn
205
+
206
+ def export_to_langchain(self) -> "LangchainBaseTool":
207
+ if not _HAS_LANGCHAIN:
208
+ raise RuntimeError(
209
+ "langchain is not installed. Install with `pip install langchain-core`"
210
+ )
211
+
212
+ from pydantic import create_model
213
+
214
+ # Create type mapping from JSON schema types to Python types
215
+ type_map = {
216
+ "number": float,
217
+ "string": str,
218
+ "integer": int,
219
+ "boolean": bool,
220
+ "object": dict,
221
+ "array": list
222
+ }
223
+
224
+ parameters = self.definition.get("function", {}).get("parameters", {})
225
+ param_properties = parameters.get("properties", {})
226
+
227
+ # Dynamically create ArgsSchema using pydantic.create_model
228
+ fields = {}
229
+ for param_name, param_def in param_properties.items():
230
+ param_type = param_def.get("type", "string")
231
+ description = param_def.get("description", "")
232
+ fields[param_name] = (
233
+ type_map.get(param_type, str),
234
+ Field(..., description=description)
235
+ )
236
+
237
+ ArgsSchema = create_model('ArgsSchema', **fields)
238
+
239
+ # Create tool subclass with our functionality
240
+ class ExportedTool(LangchainBaseTool):
241
+ name: str = self.definition.get("function", {}).get("name", "")
242
+ description: str = self.definition.get("function", {}).get("description", "")
243
+ args_schema: Type[BaseModel] = ArgsSchema
244
+ fn: Callable = self.fn
245
+
246
+ def _run(self, *args, **kwargs):
247
+ return self.fn(*args, **kwargs)
248
+
249
+ # Instantiate and return the tool
250
+ tool = ExportedTool()
251
+ return tool
@@ -0,0 +1,5 @@
1
+ from.exponents import Exponents
2
+ from.multiplication import Multiplication
3
+ from.division import Division
4
+ from.addition import Addition
5
+ from.subtraction import Subtraction
@@ -0,0 +1,39 @@
1
+ from..base import BaseTool
2
+ from ..config import FunctionRegistry
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ @FunctionRegistry.register
8
+ class Addition(BaseTool):
9
+ def __init__(self, name="addition"):
10
+ super().__init__()
11
+ self.name = name
12
+
13
+ @property
14
+ def definition(self):
15
+ return {
16
+ "type": "function",
17
+ "function": {
18
+ "name": self.name,
19
+ "description": "Calculate the sum of two numbers",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "num1": {
24
+ "type": "number",
25
+ "description": "The first number"
26
+ },
27
+ "num2": {
28
+ "type": "number",
29
+ "description": "The second number"
30
+ }
31
+ },
32
+ "required": ["num1", "num2"]
33
+ }
34
+ }
35
+ }
36
+
37
+ def fn(self, num1, num2):
38
+ logger.debug(f"Adding {num1} and {num2}")
39
+ return num1 + num2
@@ -0,0 +1,41 @@
1
+ from..base import BaseTool
2
+ from ..config import FunctionRegistry
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ @FunctionRegistry.register
8
+ class Division(BaseTool):
9
+ def __init__(self, name="division"):
10
+ super().__init__()
11
+ self.name = name
12
+
13
+ @property
14
+ def definition(self):
15
+ return {
16
+ "type": "function",
17
+ "function": {
18
+ "name": self.name,
19
+ "description": "Calculate the quotient of two numbers",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "num1": {
24
+ "type": "number",
25
+ "description": "The dividend"
26
+ },
27
+ "num2": {
28
+ "type": "number",
29
+ "description": "The divisor"
30
+ }
31
+ },
32
+ "required": ["num1", "num2"]
33
+ }
34
+ }
35
+ }
36
+
37
+ def fn(self, num1, num2):
38
+ logger.debug(f"Dividing {num1} by {num2}")
39
+ if num2 == 0:
40
+ raise ZeroDivisionError("Cannot divide by zero")
41
+ return num1 / num2
@@ -0,0 +1,39 @@
1
+ from..base import BaseTool
2
+ from ..config import FunctionRegistry
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ @FunctionRegistry.register
8
+ class Exponents(BaseTool):
9
+ def __init__(self, name="exponents"):
10
+ super().__init__()
11
+ self.name = name
12
+
13
+ @property
14
+ def definition(self):
15
+ return {
16
+ "type": "function",
17
+ "function": {
18
+ "name": self.name,
19
+ "description": "Calculate the result of a number raised to a power",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "base": {
24
+ "type": "number",
25
+ "description": "The base number"
26
+ },
27
+ "power": {
28
+ "type": "number",
29
+ "description": "The power to which the base is raised"
30
+ }
31
+ },
32
+ "required": ["base", "power"]
33
+ }
34
+ }
35
+ }
36
+
37
+ def fn(self, base, power):
38
+ logger.debug(f"Raising {base} to the {power}th power")
39
+ return base ** power
@@ -0,0 +1,39 @@
1
+ from..base import BaseTool
2
+ from ..config import FunctionRegistry
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ @FunctionRegistry.register
8
+ class Multiplication(BaseTool):
9
+ def __init__(self, name="multiplication"):
10
+ super().__init__()
11
+ self.name = name
12
+
13
+ @property
14
+ def definition(self):
15
+ return {
16
+ "type": "function",
17
+ "function": {
18
+ "name": self.name,
19
+ "description": "Calculate the product of two numbers",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "num1": {
24
+ "type": "number",
25
+ "description": "The first number"
26
+ },
27
+ "num2": {
28
+ "type": "number",
29
+ "description": "The second number"
30
+ }
31
+ },
32
+ "required": ["num1", "num2"]
33
+ }
34
+ }
35
+ }
36
+
37
+ def fn(self, num1, num2):
38
+ logger.debug(f"Multiplying {num1} by {num2}")
39
+ return num1 * num2
@@ -0,0 +1,39 @@
1
+ from..base import BaseTool
2
+ from ..config import FunctionRegistry
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ @FunctionRegistry.register
8
+ class Subtraction(BaseTool):
9
+ def __init__(self, name="subtraction"):
10
+ super().__init__()
11
+ self.name = name
12
+
13
+ @property
14
+ def definition(self):
15
+ return {
16
+ "type": "function",
17
+ "function": {
18
+ "name": self.name,
19
+ "description": "Calculate the difference between two numbers",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "num1": {
24
+ "type": "number",
25
+ "description": "The minuend"
26
+ },
27
+ "num2": {
28
+ "type": "number",
29
+ "description": "The subtrahend"
30
+ }
31
+ },
32
+ "required": ["num1", "num2"]
33
+ }
34
+ }
35
+ }
36
+
37
+ def fn(self, num1, num2):
38
+ logger.debug(f"Subtracting {num2} from {num1}")
39
+ return num1 - num2
gofannon/cli.py ADDED
@@ -0,0 +1,53 @@
1
+ import argparse
2
+ from .orchestration import ToolChain
3
+ from .base import WorkflowContext
4
+ from orchestration.firebase_wrapper import FirebaseWrapper
5
+ import json
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser(description="Gofannon CLI")
9
+ parser.add_argument('--local', action='store_true', help='Run locally')
10
+ parser.add_argument('--firebase', help='Firebase config path')
11
+ parser.add_argument('--workflow', required=True, help='Workflow config JSON')
12
+ parser.add_argument('--output', help='Output file path')
13
+
14
+ args = parser.parse_args()
15
+
16
+ # Initialize context
17
+ if args.firebase:
18
+ FirebaseWrapper.initialize(args.firebase)
19
+ context = FirebaseWrapper.get_context('current_workflow')
20
+ else:
21
+ context = WorkflowContext()
22
+
23
+ # Load workflow config
24
+ with open(args.workflow) as f:
25
+ workflow_config = json.load(f)
26
+
27
+ # Import and instantiate tools
28
+ tools = []
29
+ for tool_config in workflow_config['tools']:
30
+ module = __import__(f".{tool_config['module']}", fromlist=[tool_config['class']])
31
+ tool_class = getattr(module, tool_config['class'])
32
+ tool = tool_class(**tool_config.get('params', {}))
33
+ tools.append(tool)
34
+
35
+ # Execute workflow
36
+ chain = ToolChain(tools, context)
37
+ result = chain.execute(workflow_config.get('initial_input', {}))
38
+
39
+ # Handle output
40
+ if args.output:
41
+ with open(args.output, 'w') as f:
42
+ json.dump(result.output, f)
43
+ else:
44
+ print(json.dumps(result.output, indent=2))
45
+
46
+ # Save final state
47
+ if args.firebase:
48
+ FirebaseWrapper.save_context('current_workflow', context)
49
+ else:
50
+ context.save_checkpoint('final')
51
+
52
+ if __name__ == '__main__':
53
+ main()