gofannon 0.25.10.1__py3-none-any.whl → 0.25.12__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/base/__init__.py +27 -26
- gofannon/base/bedrock.py +666 -0
- gofannon/base/langchain.py +22 -10
- gofannon/base/smol_agents.py +6 -4
- gofannon/github/pr_review_tool.py +104 -0
- {gofannon-0.25.10.1.dist-info → gofannon-0.25.12.dist-info}/METADATA +67 -14
- {gofannon-0.25.10.1.dist-info → gofannon-0.25.12.dist-info}/RECORD +9 -7
- {gofannon-0.25.10.1.dist-info → gofannon-0.25.12.dist-info}/LICENSE +0 -0
- {gofannon-0.25.10.1.dist-info → gofannon-0.25.12.dist-info}/WHEEL +0 -0
gofannon/base/__init__.py
CHANGED
@@ -9,6 +9,8 @@ from ..config import ToolConfig
|
|
9
9
|
|
10
10
|
from .smol_agents import SmolAgentsMixin
|
11
11
|
from .langchain import LangchainMixin
|
12
|
+
from .bedrock import BedrockMixin
|
13
|
+
|
12
14
|
|
13
15
|
@dataclass
|
14
16
|
class ToolResult:
|
@@ -17,6 +19,7 @@ class ToolResult:
|
|
17
19
|
error: str = None
|
18
20
|
retryable: bool = False
|
19
21
|
|
22
|
+
|
20
23
|
class WorkflowContext:
|
21
24
|
def __init__(self, firebase_config=None):
|
22
25
|
self.data = {}
|
@@ -33,34 +36,37 @@ class WorkflowContext:
|
|
33
36
|
|
34
37
|
def _save_local(self, name):
|
35
38
|
path = self.local_storage / f"{name}.json"
|
36
|
-
with open(path,
|
37
|
-
json.dump({
|
38
|
-
'data': self.data,
|
39
|
-
'execution_log': self.execution_log
|
40
|
-
}, f)
|
39
|
+
with open(path, "w") as f:
|
40
|
+
json.dump({"data": self.data, "execution_log": self.execution_log}, f)
|
41
41
|
|
42
42
|
def _save_to_firebase(self, name):
|
43
43
|
from firebase_admin import firestore
|
44
|
+
|
44
45
|
db = firestore.client()
|
45
|
-
doc_ref = db.collection(
|
46
|
-
doc_ref.set(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
doc_ref = db.collection("checkpoints").document(name)
|
47
|
+
doc_ref.set(
|
48
|
+
{
|
49
|
+
"data": self.data,
|
50
|
+
"execution_log": self.execution_log,
|
51
|
+
"timestamp": firestore.SERVER_TIMESTAMP,
|
52
|
+
}
|
53
|
+
)
|
51
54
|
|
52
55
|
def log_execution(self, tool_name, duration, input_data, output_data):
|
53
56
|
entry = {
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
"tool": tool_name,
|
58
|
+
"duration": duration,
|
59
|
+
"input": input_data,
|
60
|
+
"output": output_data,
|
58
61
|
}
|
59
62
|
self.execution_log.append(entry)
|
60
63
|
|
61
|
-
|
64
|
+
|
65
|
+
class BaseTool(SmolAgentsMixin, LangchainMixin, BedrockMixin, ABC):
|
62
66
|
def __init__(self, **kwargs):
|
63
|
-
self.logger = logging.getLogger(
|
67
|
+
self.logger = logging.getLogger(
|
68
|
+
f"{self.__class__.__module__}.{self.__class__.__name__}"
|
69
|
+
)
|
64
70
|
self._load_config()
|
65
71
|
self._configure(**kwargs)
|
66
72
|
self.logger.debug("Initialized %s tool", self.__class__.__name__)
|
@@ -73,7 +79,7 @@ class BaseTool(SmolAgentsMixin, LangchainMixin, ABC):
|
|
73
79
|
|
74
80
|
def _load_config(self):
|
75
81
|
"""Auto-load config based on tool type"""
|
76
|
-
if hasattr(self,
|
82
|
+
if hasattr(self, "API_SERVICE"):
|
77
83
|
self.api_key = ToolConfig.get(f"{self.API_SERVICE}_api_key")
|
78
84
|
|
79
85
|
@property
|
@@ -83,7 +89,7 @@ class BaseTool(SmolAgentsMixin, LangchainMixin, ABC):
|
|
83
89
|
|
84
90
|
@property
|
85
91
|
def output_schema(self):
|
86
|
-
return self.definition.get(
|
92
|
+
return self.definition.get("function", {}).get("parameters", {})
|
87
93
|
|
88
94
|
@abstractmethod
|
89
95
|
def fn(self, *args, **kwargs):
|
@@ -99,14 +105,9 @@ class BaseTool(SmolAgentsMixin, LangchainMixin, ABC):
|
|
99
105
|
tool_name=self.__class__.__name__,
|
100
106
|
duration=duration,
|
101
107
|
input_data=kwargs,
|
102
|
-
output_data=result
|
108
|
+
output_data=result,
|
103
109
|
)
|
104
110
|
|
105
111
|
return ToolResult(success=True, output=result)
|
106
112
|
except Exception as e:
|
107
|
-
return ToolResult(
|
108
|
-
success=False,
|
109
|
-
output=None,
|
110
|
-
error=str(e),
|
111
|
-
retryable=True
|
112
|
-
)
|
113
|
+
return ToolResult(success=False, output=None, error=str(e), retryable=True)
|
gofannon/base/bedrock.py
ADDED
@@ -0,0 +1,666 @@
|
|
1
|
+
from typing import Type, Callable
|
2
|
+
import subprocess
|
3
|
+
import shutil
|
4
|
+
import zipfile
|
5
|
+
import json
|
6
|
+
import time
|
7
|
+
|
8
|
+
try:
|
9
|
+
import boto3
|
10
|
+
from botocore.exceptions import ClientError
|
11
|
+
from botocore.client import BaseClient
|
12
|
+
|
13
|
+
_HAS_BOTO3 = True
|
14
|
+
except ImportError:
|
15
|
+
_HAS_BOTO3 = False
|
16
|
+
|
17
|
+
try:
|
18
|
+
from jsonschema import validate
|
19
|
+
import jsonschema.exceptions
|
20
|
+
|
21
|
+
_HAS_JSONSCHEMA = True
|
22
|
+
except ImportError:
|
23
|
+
_HAS_JSONSCHEMA = False
|
24
|
+
|
25
|
+
|
26
|
+
class BedrockMixin:
|
27
|
+
|
28
|
+
def export_to_bedrock(self, agent_app_config: dict = None) -> dict:
|
29
|
+
"""
|
30
|
+
Export tool as Bedrock Agent tool configuration
|
31
|
+
"""
|
32
|
+
if not _HAS_BOTO3:
|
33
|
+
error = "boto3 not installed. Install with `pip install boto3`"
|
34
|
+
self.logger.error(error)
|
35
|
+
raise RuntimeError(error)
|
36
|
+
|
37
|
+
if not _HAS_JSONSCHEMA:
|
38
|
+
error = "jasonschema not installed. Install with `pip install jsonschema`"
|
39
|
+
self.logger.error(error)
|
40
|
+
raise RuntimeError(error)
|
41
|
+
|
42
|
+
valid_agent_app_config_schema = {
|
43
|
+
"type": "object",
|
44
|
+
"properties": {
|
45
|
+
"app_id": {"type": "string"},
|
46
|
+
"agent_session_timeout": {"type": "integer"},
|
47
|
+
"instruction": {"type": "string"},
|
48
|
+
"agent_description": {"type": "string"},
|
49
|
+
"target_model": {"type": "string"},
|
50
|
+
"python_runtime_version": {"type": "string"},
|
51
|
+
"temp_build_root": {"type": "string"},
|
52
|
+
},
|
53
|
+
"required": [
|
54
|
+
"app_id",
|
55
|
+
"agent_session_timeout",
|
56
|
+
"instruction",
|
57
|
+
"agent_description",
|
58
|
+
"target_model",
|
59
|
+
"python_runtime_version",
|
60
|
+
"temp_build_root",
|
61
|
+
],
|
62
|
+
}
|
63
|
+
try:
|
64
|
+
validate(agent_app_config, valid_agent_app_config_schema)
|
65
|
+
except jsonschema.exceptions.ValidationError as err:
|
66
|
+
error = f"JSON validation failure on input parameters: {err}"
|
67
|
+
self.logger.error(error)
|
68
|
+
raise RuntimeError(error)
|
69
|
+
|
70
|
+
self.bedrock_agent_client = boto3.client("bedrock-agent")
|
71
|
+
self.app_id = agent_app_config["app_id"]
|
72
|
+
self.agent_name = agent_app_config["agent_name"]
|
73
|
+
self.agent_session_timeout = agent_app_config["agent_session_timeout"]
|
74
|
+
self.agent_instruction = agent_app_config["instruction"]
|
75
|
+
self.agent_description = agent_app_config["agent_description"]
|
76
|
+
self.agent_target_model = agent_app_config["target_model"]
|
77
|
+
self.python_runtime_version = agent_app_config["python_runtime_version"]
|
78
|
+
self.temp_build_root = agent_app_config["temp_build_root"]
|
79
|
+
try:
|
80
|
+
self.aws_account_id = (
|
81
|
+
boto3.client("sts").get_caller_identity().get("Account")
|
82
|
+
)
|
83
|
+
except ClientError as e:
|
84
|
+
error = f"Error getting AWS account number. Client error: {e}"
|
85
|
+
self.logger.error(error)
|
86
|
+
raise RuntimeError(error)
|
87
|
+
except Exception as e:
|
88
|
+
error = f"Error getting AWS account number. Unexpected error: {e}"
|
89
|
+
self.logger.error(error)
|
90
|
+
raise RuntimeError(error)
|
91
|
+
|
92
|
+
self.openapi_schema_dict = self._generate_openapi_schema()
|
93
|
+
|
94
|
+
self.logger.info(
|
95
|
+
f"Starting build of app {self.app_id}...", self.__class__.__name__
|
96
|
+
)
|
97
|
+
# Creates:
|
98
|
+
# the lambda - a .zip archive that contains all the dependencies and the lambda source.
|
99
|
+
# This lambda will have a resource-based policy attached giving it
|
100
|
+
# permission to be invoked by the agent attached.
|
101
|
+
# an IAM role - the lambda execution role with managed attached policy
|
102
|
+
# AWSLambdaBasicExecutionRole allowing the lambda to use CloudWatch
|
103
|
+
|
104
|
+
self.logger.info("\tCreating lambda...", self.__class__.__name__)
|
105
|
+
|
106
|
+
self.lambda_arn = self._create_bedrock_lambda()
|
107
|
+
|
108
|
+
# Creates:
|
109
|
+
# the Bedrock agent - The bedrock agent itself
|
110
|
+
# an IAM role - The agent execution role
|
111
|
+
# An IAM policy - a policy to attach to the role allowing the agent to invoke the
|
112
|
+
# - foundational model
|
113
|
+
self.logger.info("\tCreating agent...", self.__class__.__name__)
|
114
|
+
self.agent_id = self._create_bedrock_agent()
|
115
|
+
|
116
|
+
# Creates:
|
117
|
+
# the Bedrock agent action group - Defined with an API schema
|
118
|
+
self.logger.info("\tCreating agent action group...", self.__class__.__name__)
|
119
|
+
action_group = self._create_agent_action_group()
|
120
|
+
|
121
|
+
# Finally, prepare the agent
|
122
|
+
self.logger.info("\tPreparing agent...", self.__class__.__name__)
|
123
|
+
try:
|
124
|
+
response = self.bedrock_agent_client.prepare_agent(agentId=self.agent_id)
|
125
|
+
except ClientError as e:
|
126
|
+
error = f"Error preparing agent. Client error: {e}"
|
127
|
+
self.logger.error(error)
|
128
|
+
raise RuntimeError(error)
|
129
|
+
except Exception as e:
|
130
|
+
error = f"Error preparing agent. Unexpected error: {e}"
|
131
|
+
self.logger.error(error)
|
132
|
+
raise RuntimeError(error)
|
133
|
+
|
134
|
+
output_manifest = {
|
135
|
+
"lambdaARN": self.lambda_arn,
|
136
|
+
"lambdaRoleName": self.lambda_role_name,
|
137
|
+
"agentId": self.agent_id,
|
138
|
+
"agentRoleName": self.agent_role_name,
|
139
|
+
"agentPolicyARN": self.agent_role_policy_arn,
|
140
|
+
"agentActionGroup": self.agent_action_group_id,
|
141
|
+
}
|
142
|
+
self.logger.info("Done!", self.__class__.__name__)
|
143
|
+
return output_manifest
|
144
|
+
|
145
|
+
def _generate_openapi_schema(self) -> dict:
|
146
|
+
"""Convert Gofannon definition to OpenAPI schema"""
|
147
|
+
params = self.definition["function"]["parameters"]
|
148
|
+
if not "properties" in params:
|
149
|
+
params["properties"] = {}
|
150
|
+
openapi_schema = {
|
151
|
+
"openapi": "3.0.0",
|
152
|
+
"info": {"title": self.name, "version": "1.0.0"},
|
153
|
+
"paths": {
|
154
|
+
f"/{self.name}": {
|
155
|
+
"get": {
|
156
|
+
"description": self.definition["function"]["description"],
|
157
|
+
"requestBody": {
|
158
|
+
"content": {
|
159
|
+
"application/json": {
|
160
|
+
"schema": {
|
161
|
+
"type": "object",
|
162
|
+
"properties": {
|
163
|
+
param: {
|
164
|
+
"type": props["type"],
|
165
|
+
"description": props["description"],
|
166
|
+
}
|
167
|
+
for param, props in params[
|
168
|
+
"properties"
|
169
|
+
].items()
|
170
|
+
},
|
171
|
+
"required": params.get("required", []),
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
},
|
176
|
+
"responses": {
|
177
|
+
"200": {
|
178
|
+
"description": "Successful operation",
|
179
|
+
"content": {
|
180
|
+
"application/json": {
|
181
|
+
"schema": {
|
182
|
+
"type": "object",
|
183
|
+
"properties": {
|
184
|
+
"result": {"type": "string"}
|
185
|
+
},
|
186
|
+
}
|
187
|
+
}
|
188
|
+
},
|
189
|
+
}
|
190
|
+
},
|
191
|
+
}
|
192
|
+
}
|
193
|
+
},
|
194
|
+
}
|
195
|
+
|
196
|
+
return openapi_schema
|
197
|
+
|
198
|
+
def _create_bedrock_lambda(self) -> str:
|
199
|
+
"""Create Lambda function for Bedrock integration"""
|
200
|
+
|
201
|
+
lambda_client = boto3.client("lambda")
|
202
|
+
self.lambda_role_arn = self._create_bedrock_lambda_role()
|
203
|
+
|
204
|
+
# Build the lambda .zip deployment package here. Package consits of:
|
205
|
+
# 1. All the Python dependencies, including Gofannon itself
|
206
|
+
# 2. The lambda source code.
|
207
|
+
# bash code as comments at the bottom of this file.
|
208
|
+
# See: https://docs.aws.amazon.com/lambda/latest/dg/python-package.html
|
209
|
+
|
210
|
+
# TODO: verify the path here
|
211
|
+
command = f"""../../scripts/build_lambda.sh {self.temp_build_root} {self.python_runtime_version.replace("python","")} {self.app_id}"""
|
212
|
+
try:
|
213
|
+
process = subprocess.Popen(
|
214
|
+
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
215
|
+
)
|
216
|
+
stdout, stderr = process.communicate()
|
217
|
+
|
218
|
+
if process.returncode == 0:
|
219
|
+
self.built_archive = stdout.decode("utf-8").replace("\n", "")
|
220
|
+
else:
|
221
|
+
raise RuntimeError(stderr.decode("utf-8"))
|
222
|
+
|
223
|
+
except Exception as e:
|
224
|
+
error = f"Error creating .zip archive. {e}"
|
225
|
+
self.logger.error(error)
|
226
|
+
raise RuntimeError(error)
|
227
|
+
|
228
|
+
# TODO: Skeleton code in the function directory or here?
|
229
|
+
lambda_src_string = self._get_lambda_source(
|
230
|
+
module_name=self.__class__.__module__, class_name=self.__class__.__name__
|
231
|
+
)
|
232
|
+
|
233
|
+
try:
|
234
|
+
with open(f"{self.temp_build_root}/lambda_function.py", "w") as text_file:
|
235
|
+
text_file.write(lambda_src_string)
|
236
|
+
except Exception as e:
|
237
|
+
error = f"Error writing lambda Python source file: {e}"
|
238
|
+
self.logger.error(error)
|
239
|
+
raise RuntimeError(error)
|
240
|
+
|
241
|
+
try:
|
242
|
+
with zipfile.ZipFile(
|
243
|
+
self.built_archive,
|
244
|
+
"a",
|
245
|
+
compression=zipfile.ZIP_DEFLATED,
|
246
|
+
) as zipf:
|
247
|
+
zipf.write(
|
248
|
+
f"{self.temp_build_root}/lambda_function.py", "lambda_function.py"
|
249
|
+
)
|
250
|
+
except Exception as e:
|
251
|
+
error = f"Error inserting lambda Python source file into zip: {e}"
|
252
|
+
self.logger.error(error)
|
253
|
+
raise RuntimeError(error)
|
254
|
+
|
255
|
+
with open(self.built_archive, "rb") as f:
|
256
|
+
zipped_code = f.read()
|
257
|
+
|
258
|
+
try:
|
259
|
+
response = lambda_client.create_function(
|
260
|
+
FunctionName=f"{self.app_id}_{self.name}",
|
261
|
+
Runtime=self.python_runtime_version,
|
262
|
+
Role=self.lambda_role_arn,
|
263
|
+
Handler="lambda_function.lambda_handler",
|
264
|
+
Code={"ZipFile": zipped_code},
|
265
|
+
Description=f"{self.app_id} tool: {self.name}",
|
266
|
+
Timeout=30,
|
267
|
+
MemorySize=256,
|
268
|
+
)
|
269
|
+
except ClientError as e:
|
270
|
+
error = f"Error creating lambda. Client error: {e}"
|
271
|
+
self.logger.error(error)
|
272
|
+
raise RuntimeError(error)
|
273
|
+
|
274
|
+
except Exception as e:
|
275
|
+
error = f"Error creating lambda. Unexpected error: {e}"
|
276
|
+
self.logger.error(error)
|
277
|
+
raise RuntimeError(error)
|
278
|
+
|
279
|
+
# Clean up the .zip archive build
|
280
|
+
try:
|
281
|
+
shutil.rmtree(self.temp_build_root)
|
282
|
+
except OSError as e:
|
283
|
+
error = (
|
284
|
+
f"Error deleting temporary build directory {self.temp_build_root}: {e}"
|
285
|
+
)
|
286
|
+
self.logger.error(error)
|
287
|
+
pass
|
288
|
+
|
289
|
+
# Add the resourced-base policy allowing the lambda to be invoked
|
290
|
+
# by the agent.
|
291
|
+
try:
|
292
|
+
add_permission_response = lambda_client.add_permission(
|
293
|
+
StatementId=f"{self.app_id}-bedrock-invoke",
|
294
|
+
FunctionName=response["FunctionArn"],
|
295
|
+
Action="lambda:InvokeFunction",
|
296
|
+
Principal="bedrock.amazonaws.com",
|
297
|
+
# TODO: fix this. Remove wildcard
|
298
|
+
SourceArn=f"arn:aws:bedrock:us-east-1:{self.aws_account_id}:agent/*",
|
299
|
+
)
|
300
|
+
except ClientError as e:
|
301
|
+
error = f"Error attaching resource policy to lambda execution role. Client error: {e}"
|
302
|
+
self.logger.error(error)
|
303
|
+
raise RuntimeError(error)
|
304
|
+
except Exception as e:
|
305
|
+
error = f"Error attaching resource policy to lambda execution role. Unexpected error: {e}"
|
306
|
+
self.logger.error(error)
|
307
|
+
raise RuntimeError(error)
|
308
|
+
|
309
|
+
return response["FunctionArn"]
|
310
|
+
|
311
|
+
def _create_bedrock_lambda_role(self) -> str:
|
312
|
+
"""Get or create IAM role for Bedrock integration"""
|
313
|
+
iam = boto3.client("iam")
|
314
|
+
|
315
|
+
self.lambda_role_name = f"{self.app_id}_bedrock_lambda_execution_role"
|
316
|
+
self.assume_role = self._get_assumed_role(service="lambda.amazonaws.com")
|
317
|
+
|
318
|
+
try:
|
319
|
+
iam.create_role(
|
320
|
+
RoleName=self.lambda_role_name,
|
321
|
+
AssumeRolePolicyDocument=json.dumps(self.assume_role),
|
322
|
+
)
|
323
|
+
except ClientError as e:
|
324
|
+
error = f"Error creating lambda execution role. Client error: {e}"
|
325
|
+
self.logger.error(error)
|
326
|
+
raise RuntimeError(error)
|
327
|
+
except Exception as e:
|
328
|
+
error = f"Error creating lambda execution role. Unexpected error: {e}"
|
329
|
+
self.logger.error(error)
|
330
|
+
raise RuntimeError(error)
|
331
|
+
|
332
|
+
try:
|
333
|
+
iam.attach_role_policy(
|
334
|
+
RoleName=self.lambda_role_name,
|
335
|
+
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
|
336
|
+
)
|
337
|
+
except ClientError as e:
|
338
|
+
error = f"Error attaching managed policy to lambda execution role. Client error: {e}"
|
339
|
+
self.logger.error(error)
|
340
|
+
raise RuntimeError(error)
|
341
|
+
|
342
|
+
except Exception as e:
|
343
|
+
error = f"Error attaching managed policy to lambda execution role. Unexpected error: {e}"
|
344
|
+
self.logger.error(error)
|
345
|
+
raise RuntimeError(error)
|
346
|
+
|
347
|
+
return iam.get_role(RoleName=self.lambda_role_name)["Role"]["Arn"]
|
348
|
+
|
349
|
+
def _create_bedrock_agent(self) -> str:
|
350
|
+
"""Create Bedrock agent"""
|
351
|
+
|
352
|
+
self.agent_role_arn = self._create_agent_role()
|
353
|
+
|
354
|
+
args = {
|
355
|
+
"agentName": f"{self.app_id}_{self.agent_name}Agent",
|
356
|
+
"agentResourceRoleArn": self.agent_role_arn,
|
357
|
+
"foundationModel": self.agent_target_model,
|
358
|
+
"idleSessionTTLInSeconds": self.agent_session_timeout,
|
359
|
+
"instruction": self.agent_instruction,
|
360
|
+
"description": self.agent_description,
|
361
|
+
}
|
362
|
+
|
363
|
+
try:
|
364
|
+
response = self.bedrock_agent_client.create_agent(**args)
|
365
|
+
except ClientError as e:
|
366
|
+
error = f"Error creating agent. Client error: {e}"
|
367
|
+
self.logger.error(error)
|
368
|
+
raise RuntimeError(error)
|
369
|
+
|
370
|
+
except Exception as e:
|
371
|
+
error = f"Error creating agent. Unexpected error: {e}"
|
372
|
+
self.logger.error(error)
|
373
|
+
raise RuntimeError(error)
|
374
|
+
|
375
|
+
return response["agent"]["agentId"]
|
376
|
+
|
377
|
+
def _create_agent_action_group(self):
|
378
|
+
"""Create Bedrock action group and attach to Bedrock agent"""
|
379
|
+
|
380
|
+
args = {
|
381
|
+
"agentId": self.agent_id,
|
382
|
+
"actionGroupExecutor": {
|
383
|
+
"lambda": self.lambda_arn,
|
384
|
+
},
|
385
|
+
"actionGroupName": f"{self.app_id}_{self.agent_name}_ag",
|
386
|
+
"agentVersion": "DRAFT",
|
387
|
+
"apiSchema": {"payload": json.dumps(self.openapi_schema_dict)},
|
388
|
+
"description": self.agent_description,
|
389
|
+
}
|
390
|
+
|
391
|
+
# Wait until agent status transitions to CREATING before creating the action group.
|
392
|
+
counter = 0
|
393
|
+
agent_details = self.bedrock_agent_client.get_agent(agentId=self.agent_id)
|
394
|
+
agent_status = agent_details["agent"]["agentStatus"]
|
395
|
+
while (agent_status == "CREATING") and (counter <= 9):
|
396
|
+
counter = counter + 1
|
397
|
+
time.sleep(1)
|
398
|
+
agent_details = self.bedrock_agent_client.get_agent(agentId=self.agent_id)
|
399
|
+
agent_status = agent_details["agent"]["agentStatus"]
|
400
|
+
|
401
|
+
if counter == 10:
|
402
|
+
error = f"Error creating agent timeout. Agent created, but status never transitioned out of CREATING"
|
403
|
+
self.logger.error(error)
|
404
|
+
raise RuntimeError(error)
|
405
|
+
|
406
|
+
try:
|
407
|
+
agent_action_group = self.bedrock_agent_client.create_agent_action_group(
|
408
|
+
**args
|
409
|
+
)
|
410
|
+
self.agent_action_group_id = agent_action_group["agentActionGroup"][
|
411
|
+
"actionGroupId"
|
412
|
+
]
|
413
|
+
except ClientError as e:
|
414
|
+
error = f"Error creating agent action group. Client error: {e}"
|
415
|
+
self.logger.error(error)
|
416
|
+
raise RuntimeError(error)
|
417
|
+
except Exception as e:
|
418
|
+
error = f"Error creating agent action group. Unexpected error: {e}"
|
419
|
+
self.logger.error(error)
|
420
|
+
raise RuntimeError(error)
|
421
|
+
|
422
|
+
return agent_action_group
|
423
|
+
|
424
|
+
def _create_agent_role(self) -> str:
|
425
|
+
"""Create IAM agent role for Bedrock integration"""
|
426
|
+
|
427
|
+
iam = boto3.client("iam")
|
428
|
+
|
429
|
+
self.agent_role_name = f"{self.app_id}_bedrock_agent_execution_role"
|
430
|
+
self.agent_policy_name = f"{self.app_id}_bedrock_agent_allow_model"
|
431
|
+
|
432
|
+
assume_role = self._get_assumed_role(service="bedrock.amazonaws.com")
|
433
|
+
|
434
|
+
try:
|
435
|
+
iam.create_role(
|
436
|
+
RoleName=self.agent_role_name,
|
437
|
+
AssumeRolePolicyDocument=json.dumps(assume_role),
|
438
|
+
)
|
439
|
+
except ClientError as e:
|
440
|
+
error = f"Error creating agent role. Client error: {e}"
|
441
|
+
self.logger.error(error)
|
442
|
+
raise RuntimeError(error)
|
443
|
+
except Exception as e:
|
444
|
+
error = f"Error creating agent role. Unexpected error: {e}"
|
445
|
+
self.logger.error(error)
|
446
|
+
raise RuntimeError(error)
|
447
|
+
|
448
|
+
managed_policy_txt = self._get_managed_policy(
|
449
|
+
resource=f"foundation-model/{self.agent_target_model}"
|
450
|
+
)
|
451
|
+
|
452
|
+
try:
|
453
|
+
managed_policy = iam.create_policy(
|
454
|
+
PolicyName=self.agent_policy_name,
|
455
|
+
PolicyDocument=json.dumps(managed_policy_txt),
|
456
|
+
)
|
457
|
+
except ClientError as e:
|
458
|
+
error = f"Error creating agent policy. Client error: {e}"
|
459
|
+
self.logger.error(error)
|
460
|
+
raise RuntimeError(error)
|
461
|
+
except Exception as e:
|
462
|
+
error = f"Error creating agent policy. Unexpected error: {e}"
|
463
|
+
self.logger.error(error)
|
464
|
+
raise RuntimeError(error)
|
465
|
+
|
466
|
+
self.agent_role_policy_arn = (
|
467
|
+
f"arn:aws:iam::{self.aws_account_id}:policy/{self.agent_policy_name}"
|
468
|
+
)
|
469
|
+
try:
|
470
|
+
iam.attach_role_policy(
|
471
|
+
RoleName=self.agent_role_name,
|
472
|
+
PolicyArn=self.agent_role_policy_arn,
|
473
|
+
)
|
474
|
+
except ClientError as e:
|
475
|
+
error = f"Error creating attaching policy to agent role. Client error: {e}"
|
476
|
+
self.logger.error(error)
|
477
|
+
raise RuntimeError(error)
|
478
|
+
except Exception as e:
|
479
|
+
error = (
|
480
|
+
f"Error creating attaching policy to agent role. Unexpected error: {e}"
|
481
|
+
)
|
482
|
+
self.logger.error(error)
|
483
|
+
raise RuntimeError(error)
|
484
|
+
|
485
|
+
return iam.get_role(RoleName=self.agent_role_name)["Role"]["Arn"]
|
486
|
+
|
487
|
+
def _get_assumed_role(self, service: str = None) -> str:
|
488
|
+
"""Skeleton code for creating a role"""
|
489
|
+
role = {
|
490
|
+
"Version": "2012-10-17",
|
491
|
+
"Statement": [
|
492
|
+
{
|
493
|
+
"Effect": "Allow",
|
494
|
+
"Principal": {"Service": f"{service}"},
|
495
|
+
"Action": "sts:AssumeRole",
|
496
|
+
}
|
497
|
+
],
|
498
|
+
}
|
499
|
+
return role
|
500
|
+
|
501
|
+
def _get_managed_policy(self, resource: str = None) -> str:
|
502
|
+
"""Skeleton code for creating a policy"""
|
503
|
+
managed_policy_txt = {
|
504
|
+
"Version": "2012-10-17",
|
505
|
+
"Statement": [
|
506
|
+
{
|
507
|
+
"Action": "bedrock:InvokeModel",
|
508
|
+
"Resource": f"arn:aws:bedrock:us-east-1::{resource}",
|
509
|
+
"Effect": "Allow",
|
510
|
+
}
|
511
|
+
],
|
512
|
+
}
|
513
|
+
|
514
|
+
return managed_policy_txt
|
515
|
+
|
516
|
+
def _get_lambda_source(
|
517
|
+
self, module_name: str = None, class_name: str = None
|
518
|
+
) -> str:
|
519
|
+
"""Bedrock wrapped lambda used by the agent"""
|
520
|
+
|
521
|
+
# funky spacing below is required...
|
522
|
+
lambda_src = f"""
|
523
|
+
import json
|
524
|
+
from {module_name} import {class_name}
|
525
|
+
|
526
|
+
|
527
|
+
def lambda_handler(event, context):
|
528
|
+
tool = {class_name}()
|
529
|
+
result = tool.fn()
|
530
|
+
action_response = {{
|
531
|
+
"actionGroup": event["actionGroup"],
|
532
|
+
"apiPath": event["apiPath"],
|
533
|
+
"httpMethod": event["httpMethod"],
|
534
|
+
"httpStatusCode": 200,
|
535
|
+
"responseBody": {{"application/json": {{"body": json.dumps(result)}}}},
|
536
|
+
}}
|
537
|
+
api_response = {{"messageVersion": "1.0", "response": action_response}}
|
538
|
+
|
539
|
+
return api_response"""
|
540
|
+
return lambda_src
|
541
|
+
|
542
|
+
def delete_app(self, bedrock_config: dict = None) -> bool:
|
543
|
+
"""Tear down the boto3 stack created by .export_to_bedrock()"""
|
544
|
+
|
545
|
+
valid_bedrock_config_schema = {
|
546
|
+
"type": "object",
|
547
|
+
"properties": {
|
548
|
+
"lambdaARN": {"type": "string"},
|
549
|
+
"lambdaRoleName": {"type": "string"},
|
550
|
+
"agentId": {"type": "string"},
|
551
|
+
"agentRoleName": {"type": "string"},
|
552
|
+
"agentPolicyARN": {"type": "string"},
|
553
|
+
"agentActionGroup": {"type": "string"},
|
554
|
+
},
|
555
|
+
"required": [
|
556
|
+
"lambdaARN",
|
557
|
+
"lambdaRoleName",
|
558
|
+
"agentId",
|
559
|
+
"agentRoleName",
|
560
|
+
"agentPolicyARN",
|
561
|
+
"agentActionGroup",
|
562
|
+
],
|
563
|
+
}
|
564
|
+
try:
|
565
|
+
validate(bedrock_config, valid_bedrock_config_schema)
|
566
|
+
except jsonschema.exceptions.ValidationError as err:
|
567
|
+
error = f"JSON validation failure on input bedrock_config: {err}"
|
568
|
+
self.logger.error(error)
|
569
|
+
raise RuntimeError(error)
|
570
|
+
|
571
|
+
self.lambda_client = boto3.client("lambda")
|
572
|
+
self.iam_client = boto3.client("iam")
|
573
|
+
self.bedrock_agent_client = boto3.client("bedrock-agent")
|
574
|
+
|
575
|
+
# Order is important here...
|
576
|
+
try:
|
577
|
+
detach_agent_policy = self.iam_client.detach_role_policy(
|
578
|
+
RoleName=bedrock_config["agentRoleName"],
|
579
|
+
PolicyArn=bedrock_config["agentPolicyARN"],
|
580
|
+
)
|
581
|
+
except ClientError as e:
|
582
|
+
error = f"Error detaching agent policy. Client error: {e}"
|
583
|
+
self.logger.error(error)
|
584
|
+
pass
|
585
|
+
except Exception as e:
|
586
|
+
error = f"Error detaching agent policy. Unexpected error: {e}"
|
587
|
+
self.logger.error(error)
|
588
|
+
pass
|
589
|
+
try:
|
590
|
+
delete_agent_role = self.iam_client.delete_role(
|
591
|
+
RoleName=bedrock_config["agentRoleName"]
|
592
|
+
)
|
593
|
+
except ClientError as e:
|
594
|
+
error = f"Error deleting agent role. Client error: {e}"
|
595
|
+
self.logger.error(error)
|
596
|
+
pass
|
597
|
+
except Exception as e:
|
598
|
+
error = f"Error deleting agent role. Unexpected error: {e}"
|
599
|
+
self.logger.error(error)
|
600
|
+
pass
|
601
|
+
|
602
|
+
try:
|
603
|
+
delete_agent_policy = self.iam_client.delete_policy(
|
604
|
+
PolicyArn=bedrock_config["agentPolicyARN"]
|
605
|
+
)
|
606
|
+
except ClientError as e:
|
607
|
+
error = f"Error deleting agent policy. Client error: {e}"
|
608
|
+
self.logger.error(error)
|
609
|
+
pass
|
610
|
+
except Exception as e:
|
611
|
+
error = f"Error deleting agent policy. Unexpected error: {e}"
|
612
|
+
self.logger.error(error)
|
613
|
+
pass
|
614
|
+
|
615
|
+
try:
|
616
|
+
delete_agent = self.bedrock_agent_client.delete_agent(
|
617
|
+
agentId=bedrock_config["agentId"], skipResourceInUseCheck=True
|
618
|
+
)
|
619
|
+
except ClientError as e:
|
620
|
+
error = f"Error deleting agent. Client error: {e}"
|
621
|
+
self.logger.error(error)
|
622
|
+
pass
|
623
|
+
except Exception as e:
|
624
|
+
error = f"Error deleting agent. Unexpected error: {e}"
|
625
|
+
self.logger.error(error)
|
626
|
+
pass
|
627
|
+
|
628
|
+
try:
|
629
|
+
detach_lambda_policy = self.iam_client.detach_role_policy(
|
630
|
+
RoleName=bedrock_config["lambdaRoleName"],
|
631
|
+
PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
|
632
|
+
)
|
633
|
+
except ClientError as e:
|
634
|
+
error = f"Error detaching lambda policy. Client error: {e}"
|
635
|
+
self.logger.error(error)
|
636
|
+
pass
|
637
|
+
except Exception as e:
|
638
|
+
error = f"Error detaching lambda policy. Unexpected error: {e}"
|
639
|
+
self.logger.error(error)
|
640
|
+
pass
|
641
|
+
|
642
|
+
try:
|
643
|
+
delete_lambda_role = self.iam_client.delete_role(
|
644
|
+
RoleName=bedrock_config["lambdaRoleName"]
|
645
|
+
)
|
646
|
+
except ClientError as e:
|
647
|
+
error = f"Error deleting lambda role. Client error: {e}"
|
648
|
+
self.logger.error(error)
|
649
|
+
pass
|
650
|
+
except Exception as e:
|
651
|
+
error = f"Error deleting lambda role. Unexpected error: {e}"
|
652
|
+
self.logger.error(error)
|
653
|
+
pass
|
654
|
+
|
655
|
+
try:
|
656
|
+
delete_lambda = self.lambda_client.delete_function(
|
657
|
+
FunctionName=bedrock_config["lambdaARN"]
|
658
|
+
)
|
659
|
+
except ClientError as e:
|
660
|
+
error = f"Error deleting lambda. Client error: {e}"
|
661
|
+
self.logger.error(error)
|
662
|
+
pass
|
663
|
+
except Exception as e:
|
664
|
+
error = f"Error deleting lambda. Unexpected error: {e}"
|
665
|
+
self.logger.error(error)
|
666
|
+
pass
|
gofannon/base/langchain.py
CHANGED
@@ -3,20 +3,30 @@ from typing import Type, Callable
|
|
3
3
|
try:
|
4
4
|
from langchain.tools import BaseTool as LangchainBaseTool
|
5
5
|
from langchain.pydantic_v1 import BaseModel, Field
|
6
|
+
|
6
7
|
_HAS_LANGCHAIN = True
|
7
8
|
except ImportError:
|
8
9
|
_HAS_LANGCHAIN = False
|
9
10
|
|
11
|
+
|
10
12
|
class LangchainMixin:
|
11
|
-
def import_from_langchain(self, langchain_tool
|
13
|
+
def import_from_langchain(self, langchain_tool):
|
12
14
|
if not _HAS_LANGCHAIN:
|
13
|
-
raise RuntimeError(
|
15
|
+
raise RuntimeError(
|
16
|
+
"langchain is not installed. Install with `pip install langchain-core`"
|
17
|
+
)
|
14
18
|
|
15
19
|
self.name = getattr(langchain_tool, "name", "exported_langchain_tool")
|
16
|
-
self.description = getattr(
|
20
|
+
self.description = getattr(
|
21
|
+
langchain_tool, "description", "No description provided."
|
22
|
+
)
|
17
23
|
|
18
24
|
maybe_args_schema = getattr(langchain_tool, "args_schema", None)
|
19
|
-
if
|
25
|
+
if (
|
26
|
+
maybe_args_schema
|
27
|
+
and hasattr(maybe_args_schema, "schema")
|
28
|
+
and callable(maybe_args_schema.schema)
|
29
|
+
):
|
20
30
|
args_schema = maybe_args_schema.schema()
|
21
31
|
else:
|
22
32
|
args_schema = {}
|
@@ -29,7 +39,7 @@ class LangchainMixin:
|
|
29
39
|
|
30
40
|
self.fn = adapted_fn
|
31
41
|
|
32
|
-
def export_to_langchain(self)
|
42
|
+
def export_to_langchain(self):
|
33
43
|
if not _HAS_LANGCHAIN:
|
34
44
|
raise RuntimeError(
|
35
45
|
"langchain is not installed. Install with `pip install langchain-core`"
|
@@ -43,7 +53,7 @@ class LangchainMixin:
|
|
43
53
|
"integer": int,
|
44
54
|
"boolean": bool,
|
45
55
|
"object": dict,
|
46
|
-
"array": list
|
56
|
+
"array": list,
|
47
57
|
}
|
48
58
|
|
49
59
|
parameters = self.definition.get("function", {}).get("parameters", {})
|
@@ -55,14 +65,16 @@ class LangchainMixin:
|
|
55
65
|
description = param_def.get("description", "")
|
56
66
|
fields[param_name] = (
|
57
67
|
type_map.get(param_type, str),
|
58
|
-
Field(..., description=description)
|
68
|
+
Field(..., description=description),
|
59
69
|
)
|
60
70
|
|
61
|
-
ArgsSchema = create_model(
|
71
|
+
ArgsSchema = create_model("ArgsSchema", **fields)
|
62
72
|
|
63
73
|
class ExportedTool(LangchainBaseTool):
|
64
74
|
name: str = self.definition.get("function", {}).get("name", "")
|
65
|
-
description: str = self.definition.get("function", {}).get(
|
75
|
+
description: str = self.definition.get("function", {}).get(
|
76
|
+
"description", ""
|
77
|
+
)
|
66
78
|
args_schema: Type[BaseModel] = ArgsSchema
|
67
79
|
fn: Callable = self.fn
|
68
80
|
|
@@ -70,4 +82,4 @@ class LangchainMixin:
|
|
70
82
|
return self.fn(*args, **kwargs)
|
71
83
|
|
72
84
|
tool = ExportedTool()
|
73
|
-
return tool
|
85
|
+
return tool
|
gofannon/base/smol_agents.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
try:
|
2
2
|
from smolagents.tools import Tool as SmolTool
|
3
3
|
from smolagents.tools import tool as smol_tool_decorator
|
4
|
+
|
4
5
|
_HAS_SMOLAGENTS = True
|
5
6
|
except ImportError:
|
6
7
|
_HAS_SMOLAGENTS = False
|
7
8
|
|
9
|
+
|
8
10
|
class SmolAgentsMixin:
|
9
|
-
def import_from_smolagents(self, smol_tool
|
11
|
+
def import_from_smolagents(self, smol_tool):
|
10
12
|
if not _HAS_SMOLAGENTS:
|
11
13
|
raise RuntimeError(
|
12
14
|
"smolagents is not installed or could not be imported. "
|
@@ -20,7 +22,7 @@ class SmolAgentsMixin:
|
|
20
22
|
|
21
23
|
self.fn = adapted_fn
|
22
24
|
|
23
|
-
def export_to_smolagents(self)
|
25
|
+
def export_to_smolagents(self):
|
24
26
|
if not _HAS_SMOLAGENTS:
|
25
27
|
raise RuntimeError(
|
26
28
|
"smolagents is not installed or could not be imported. "
|
@@ -33,7 +35,7 @@ class SmolAgentsMixin:
|
|
33
35
|
inputs_definition = {
|
34
36
|
"example_arg": {
|
35
37
|
"type": "string",
|
36
|
-
"description": "Example argument recognized by this tool"
|
38
|
+
"description": "Example argument recognized by this tool",
|
37
39
|
}
|
38
40
|
}
|
39
41
|
output_type = "string"
|
@@ -46,4 +48,4 @@ class SmolAgentsMixin:
|
|
46
48
|
exported_tool.forward = smol_forward
|
47
49
|
exported_tool.is_initialized = True
|
48
50
|
|
49
|
-
return exported_tool
|
51
|
+
return exported_tool
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
import importlib.util
|
5
|
+
from github import Github
|
6
|
+
from openai import OpenAI
|
7
|
+
from gofannon.config import FunctionRegistry
|
8
|
+
from gofannon.base import BaseTool
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
def load_review_checks():
|
13
|
+
"""
|
14
|
+
Dynamically load the review checks module from a configurable file.
|
15
|
+
The path is specified via the environment variable PR_REVIEW_CHECKS_PATH.
|
16
|
+
If not defined, it defaults to ".github/scripts/pr_review_checks.py".
|
17
|
+
Returns a list of check classes (having names ending with 'Check').
|
18
|
+
"""
|
19
|
+
checks_path = os.getenv("PR_REVIEW_CHECKS_PATH", ".github/scripts/pr_review_checks.py")
|
20
|
+
spec = importlib.util.spec_from_file_location("pr_review_checks", checks_path)
|
21
|
+
module = importlib.util.module_from_spec(spec)
|
22
|
+
spec.loader.exec_module(module)
|
23
|
+
# Collect all classes in the module with names ending in 'Check'
|
24
|
+
checks = [cls for name, cls in module.__dict__.items() if name.endswith("Check") and isinstance(cls, type)]
|
25
|
+
return checks
|
26
|
+
|
27
|
+
@FunctionRegistry.register
|
28
|
+
class PRReviewTool(BaseTool):
|
29
|
+
def __init__(self, name="pr_review_tool"):
|
30
|
+
super().__init__()
|
31
|
+
self.name = name
|
32
|
+
self.api_key = os.getenv("OPENAI_API_KEY")
|
33
|
+
self.base_url = os.getenv("OPENAI_BASE_URL")
|
34
|
+
self.model_name = os.getenv("OPENAI_MODEL_NAME")
|
35
|
+
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
|
36
|
+
|
37
|
+
@property
|
38
|
+
def definition(self):
|
39
|
+
return {
|
40
|
+
"type": "function",
|
41
|
+
"function": {
|
42
|
+
"name": self.name,
|
43
|
+
"description": "Perform an automated pull request review using gofannon tools. "
|
44
|
+
"It aggregates configurable checks (e.g. code quality and schema validation).",
|
45
|
+
"parameters": {
|
46
|
+
"type": "object",
|
47
|
+
"properties": {
|
48
|
+
"pr_number": {
|
49
|
+
"type": "integer",
|
50
|
+
"description": "The pull request number."
|
51
|
+
},
|
52
|
+
"repo_name": {
|
53
|
+
"type": "string",
|
54
|
+
"description": "The repository name in the format owner/repo."
|
55
|
+
}
|
56
|
+
},
|
57
|
+
"required": ["pr_number", "repo_name"]
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
def fn(self, pr_number, repo_name):
|
63
|
+
# Connect to GitHub and get pull request details.
|
64
|
+
g = Github(os.getenv("GITHUB_TOKEN"))
|
65
|
+
repo = g.get_repo(repo_name)
|
66
|
+
pr = repo.get_pull(pr_number)
|
67
|
+
all_comments = []
|
68
|
+
check_results = {}
|
69
|
+
|
70
|
+
# Load review check classes dynamically.
|
71
|
+
check_classes = load_review_checks()
|
72
|
+
checks = [check_class(self.client, self.model_name) for check_class in check_classes]
|
73
|
+
|
74
|
+
for check in checks:
|
75
|
+
check_name = check.__class__.__name__
|
76
|
+
check_results[check_name] = []
|
77
|
+
if hasattr(check, 'process_pr_file'):
|
78
|
+
for file in pr.get_files():
|
79
|
+
file_comments, analyzed = check.process_pr_file(file, repo, pr)
|
80
|
+
if analyzed:
|
81
|
+
for comment in file_comments:
|
82
|
+
comment['check_name'] = check_name
|
83
|
+
all_comments.append(comment)
|
84
|
+
check_results[check_name].append(comment)
|
85
|
+
if hasattr(check, 'process_pr'):
|
86
|
+
pr_comments, _ = check.process_pr(pr)
|
87
|
+
for comment in pr_comments:
|
88
|
+
comment['check_name'] = check_name
|
89
|
+
all_comments.append(comment)
|
90
|
+
check_results[check_name].append(comment)
|
91
|
+
|
92
|
+
summary = "## 🤖 Automated PR Review Summary 🤖\n\n"
|
93
|
+
for check_name, comments in check_results.items():
|
94
|
+
summary += f"### 🤖 {check_name} 🤖\n\n"
|
95
|
+
for comment in comments:
|
96
|
+
body = comment.get("body", "")
|
97
|
+
file_path = comment.get("path", "")
|
98
|
+
if file_path and file_path != "GENERAL":
|
99
|
+
body = f"**File:** `{file_path}`\n{body}"
|
100
|
+
summary += f"{body}\n\n"
|
101
|
+
if not all_comments:
|
102
|
+
summary += "\nNo issues found. Code looks good!"
|
103
|
+
|
104
|
+
return summary
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: gofannon
|
3
|
-
Version: 0.25.
|
3
|
+
Version: 0.25.12
|
4
4
|
Summary: A collection of tools for LLMs
|
5
5
|
License: ASFv2
|
6
6
|
Author: Trevor Grant
|
@@ -12,17 +12,20 @@ Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
15
|
+
Provides-Extra: aws
|
15
16
|
Provides-Extra: google
|
16
17
|
Provides-Extra: headless-browser
|
17
18
|
Provides-Extra: langchain
|
18
19
|
Provides-Extra: smolagents
|
19
20
|
Provides-Extra: testing
|
20
21
|
Requires-Dist: GitPython (>=3.1.43,<4.0.0)
|
22
|
+
Requires-Dist: boto3 (>=1.34.97,<2.0.0) ; extra == "aws"
|
21
23
|
Requires-Dist: google-api-python-client (>=2.161.0,<3.0.0) ; extra == "google"
|
22
24
|
Requires-Dist: jsonschema (>=4.23.0,<5.0.0)
|
23
25
|
Requires-Dist: langchain (>=0.3.16,<0.4.0) ; extra == "langchain"
|
24
26
|
Requires-Dist: openai (>=1.60.2,<2.0.0)
|
25
27
|
Requires-Dist: pydantic (>=2.10.6,<3.0.0) ; extra == "langchain"
|
28
|
+
Requires-Dist: pygithub (>=2.6.1,<3.0.0)
|
26
29
|
Requires-Dist: pytest (>=8.3.4,<9.0.0) ; extra == "testing"
|
27
30
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
28
31
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
@@ -31,7 +34,12 @@ Requires-Dist: selenium (>=4.10.0,<5.0.0) ; extra == "headless-browser"
|
|
31
34
|
Requires-Dist: smolagents (>=1.6.0,<2.0.0) ; extra == "smolagents"
|
32
35
|
Description-Content-Type: text/markdown
|
33
36
|
|
34
|
-

|
38
|
+
<!--  -->
|
39
|
+

|
40
|
+

|
41
|
+

|
42
|
+

|
35
43
|
|
36
44
|
# gofannon
|
37
45
|
|
@@ -39,7 +47,30 @@ Description-Content-Type: text/markdown
|
|
39
47
|
|
40
48
|
## Why the name `gofanon` ?
|
41
49
|
|
42
|
-
See [`why_the_name_gofannon.md`](
|
50
|
+
See [`why_the_name_gofannon.md`](https://the-ai-alliance.github.io/gofannon/about/the_name_gofannon/) for the rich story on why we chose to honor this Celtic Diety
|
51
|
+
|
52
|
+
## Our Mission
|
53
|
+
|
54
|
+
We aim to achieve:
|
55
|
+
|
56
|
+
### Cross-Framework Compatibility
|
57
|
+
We solve the "vendor lock-in" problem in AI tooling through:
|
58
|
+
- Standardized interface definitions
|
59
|
+
- Automatic schema translation
|
60
|
+
- Bidirectional conversion tools
|
61
|
+
|
62
|
+
### Open Source Education
|
63
|
+
We make AI development accessible by:
|
64
|
+
- Curated contribution pathways
|
65
|
+
- Interactive documentation
|
66
|
+
- Pair programming sessions
|
67
|
+
- Weekly office hours
|
68
|
+
|
69
|
+
### Encouraging First-Time Contributors
|
70
|
+
We actively support new contributors through:
|
71
|
+
- Beginner-friendly issues
|
72
|
+
- Clear documentation and guides
|
73
|
+
- Supportuve community engagement
|
43
74
|
|
44
75
|
## Features
|
45
76
|
|
@@ -51,20 +82,12 @@ See [`why_the_name_gofannon.md`](./why_the_name_gofannon.md) for the rich story
|
|
51
82
|
|
52
83
|
## Roadmap
|
53
84
|
|
54
|
-
For a detailed overview of planned features and their current status, please refer to the [ROADMAP](
|
85
|
+
For a detailed overview of planned features and their current status, please refer to the [ROADMAP](https://github.com/The-AI-Alliance/gofannon/blob/main/ROADMAP.md).
|
55
86
|
|
56
87
|
## Documentation
|
57
88
|
|
58
|
-
Documentation can be found [here](
|
89
|
+
Documentation can be found [here](https://github.com/The-AI-Alliance/gofannon/tree/main/docs).Each tool comes with its own documentation, which can be found in the docs/ directory. The documentation provides detailed information on how to use each tool, including required parameters and example usage.
|
59
90
|
|
60
|
-
## Contributing
|
61
|
-
|
62
|
-
We welcome contributions from the community! If you'd like to add a new tool or improve an existing one, please check out our [CONTRIBUTING](./CONTRIBUTING.md) guide for detailed instructions on how to get started.
|
63
|
-
|
64
|
-
## Documentation
|
65
|
-
|
66
|
-
Each tool comes with its own documentation, which can be found in the `docs/` directory. The documentation provides detailed information on how to use each tool, including required parameters and example usage.
|
67
|
-
|
68
91
|
## Installation
|
69
92
|
|
70
93
|
To install gofannon, simply clone the repository and install the required dependencies:
|
@@ -79,11 +102,41 @@ or
|
|
79
102
|
|
80
103
|
```
|
81
104
|
pip install git+https://github.com/The-AI-Alliance/gofannon.git
|
105
|
+
# OR
|
106
|
+
pip install gofannon
|
107
|
+
```
|
108
|
+
|
109
|
+
## Communication Channels
|
110
|
+
- **Discord** [Join our Discord server](https://discord.gg/2MMCVs76Sr)for eal-time collaboration and support
|
111
|
+
- **GitHub Discussions**: Explore our [GitHub organization](https://github.com/The-AI-Alliance/agents-wg/discussions/) for all related projects
|
112
|
+
- **Community Calls**: [Join our bi-weekly video meetings](https://calendar.app.google/c4eKW4zrNiXaue926)
|
113
|
+
|
114
|
+
## Usage Example
|
115
|
+
```bash
|
116
|
+
from gofannon.base import BaseTool
|
117
|
+
|
118
|
+
class NewTool(BaseTool):
|
119
|
+
def __init__(self):
|
120
|
+
super().__init__()
|
121
|
+
|
122
|
+
@property
|
123
|
+
def definition(self):
|
124
|
+
return {
|
125
|
+
# Define your tool metadata and configuration
|
126
|
+
}
|
127
|
+
|
128
|
+
def fn(self, *args, **kwargs):
|
129
|
+
# Define your tool functionality
|
130
|
+
pass
|
82
131
|
```
|
83
132
|
|
84
133
|
## License
|
85
134
|
|
86
|
-
This project is licensed under the ASFv2 License. See the [LICENSE](
|
135
|
+
This project is licensed under the ASFv2 License. See the [LICENSE](https://github.com/The-AI-Alliance/gofannon/blob/main/LICENSE) file for more details.
|
136
|
+
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
We welcome contributions from the community! If you'd like to add a new tool or improve an existing one, please check out our [CONTRIBUTING](https://github.com/The-AI-Alliance/gofannon/blob/main/CONTRIBUTING.md) guide for detailed instructions on how to get started.
|
87
140
|
|
88
141
|
## Support
|
89
142
|
|
@@ -2,9 +2,10 @@ gofannon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
gofannon/arxiv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
gofannon/arxiv/get_article.py,sha256=SRGTXFXdXdXTIOLZKWUTXxZEYEqZFWFJEV2nTsU5qqU,1167
|
4
4
|
gofannon/arxiv/search.py,sha256=37Zx1y2vAX1xYIKaAxzBGXE3qPHUZdAD2XR0H1Acs-4,4225
|
5
|
-
gofannon/base/__init__.py,sha256=
|
6
|
-
gofannon/base/
|
7
|
-
gofannon/base/
|
5
|
+
gofannon/base/__init__.py,sha256=8-uJsYAbLD-2e4clX73QZ8eip9S2PJlQ9JMRNWFHxSI,3387
|
6
|
+
gofannon/base/bedrock.py,sha256=Z2c36R8jaIusgpmegbYVz2eR7lDBc0IhTtwiqUGOcT4,25646
|
7
|
+
gofannon/base/langchain.py,sha256=gAmDT_khB28-p8KMf4lT4Buy2w4zrdP1jG4hOituQ54,2714
|
8
|
+
gofannon/base/smol_agents.py,sha256=rrdsXiOF-LZ1eY08PCN3r8bg0YEaN1b4vPlSaxii4LI,1654
|
8
9
|
gofannon/basic_math/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
9
10
|
gofannon/basic_math/addition.py,sha256=zA-xtUBNnGnQVqTJkBvjZwX0gnGZ6mrUGhrlTbsH1ts,1148
|
10
11
|
gofannon/basic_math/division.py,sha256=ZO8ZzWNL9zeFdXTEdPWDpnbDrWMXVeucSu105RbQmWU,1229
|
@@ -18,6 +19,7 @@ gofannon/github/commit_file.py,sha256=jdQGQHbrZx4521XgTbx5N0Ss8fDyl7hvp9sjDW15v9
|
|
18
19
|
gofannon/github/commit_files.py,sha256=0WP4nx4RYpO_Vb45Sa65afuzSMf4de-Wt6awpSOusyM,4395
|
19
20
|
gofannon/github/create_issue.py,sha256=WXq0klzPvGZnZyZRmriGOkHl7E_Tp08vO1v2ou7TR8w,2560
|
20
21
|
gofannon/github/get_repo_contents.py,sha256=9k6M2BqGlNsSGVjyfW7nxZpk1TFuhyPoZvURkv1PEyo,3637
|
22
|
+
gofannon/github/pr_review_tool.py,sha256=srBbfgqBWy-J4wdAM0kLJfQr8LJ6uaA4vkDErqhyMxI,4336
|
21
23
|
gofannon/github/read_issue.py,sha256=JrnBAlZxknhHm3aLC0uB9u0bSvoQNfK3OKmxYlr8jgQ,2308
|
22
24
|
gofannon/github/search.py,sha256=yuX_dZ6f8ogUnskIRMUgF8wuN7JepqRtTDFrbmbwrrs,2183
|
23
25
|
gofannon/google_search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -35,7 +37,7 @@ gofannon/reasoning/base.py,sha256=D-4JHJqUlqgwMNOkKU0BHYA4GEWwNgPlLxKYHX0FVyg,14
|
|
35
37
|
gofannon/reasoning/hierarchical_cot.py,sha256=e8ZgMbyJQ0wCBEmv7QJqFv7l3XxTMwDYupGxJ7EF6t8,11516
|
36
38
|
gofannon/reasoning/sequential_cot.py,sha256=m9c8GnyTtmI-JntCuhkoFfULAabVOxsYgTRUd3MjzfY,3166
|
37
39
|
gofannon/reasoning/tree_of_thought.py,sha256=TRhRJQNsFVauCLw4TOvQCDcX1nGmp_wSg9H67GJn1hs,10574
|
38
|
-
gofannon-0.25.
|
39
|
-
gofannon-0.25.
|
40
|
-
gofannon-0.25.
|
41
|
-
gofannon-0.25.
|
40
|
+
gofannon-0.25.12.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
41
|
+
gofannon-0.25.12.dist-info/METADATA,sha256=KqFu7dMGSTz5Xg9ESdM20VRNZS3jdWwfRWj9NkV2gvA,6041
|
42
|
+
gofannon-0.25.12.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
43
|
+
gofannon-0.25.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|