fyodorov-llm-agents 0.2.146__py3-none-any.whl → 0.2.148__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.
- fyodorov_llm_agents/agents/agent.py +44 -17
- fyodorov_llm_agents/tools/{mcp_tool.py → mcp_tool_model.py} +40 -1
- fyodorov_llm_agents/tools/mcp_tool_service.py +133 -0
- {fyodorov_llm_agents-0.2.146.dist-info → fyodorov_llm_agents-0.2.148.dist-info}/METADATA +1 -1
- fyodorov_llm_agents-0.2.148.dist-info/RECORD +9 -0
- {fyodorov_llm_agents-0.2.146.dist-info → fyodorov_llm_agents-0.2.148.dist-info}/WHEEL +1 -1
- fyodorov_llm_agents-0.2.146.dist-info/RECORD +0 -8
- {fyodorov_llm_agents-0.2.146.dist-info → fyodorov_llm_agents-0.2.148.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,10 @@
|
|
1
1
|
import os
|
2
2
|
import re
|
3
|
-
import requests
|
4
|
-
import queue
|
5
|
-
import threading
|
6
3
|
import json
|
7
|
-
import yaml
|
8
4
|
from pydantic import BaseModel, HttpUrl
|
9
5
|
from typing import Optional
|
10
|
-
from openai import OpenAI as oai
|
11
6
|
import litellm
|
12
|
-
from fyodorov_llm_agents.tools.
|
7
|
+
from fyodorov_llm_agents.tools.mcp_tool_service import MCPTool as ToolService
|
13
8
|
from datetime import datetime
|
14
9
|
|
15
10
|
MAX_NAME_LENGTH = 80
|
@@ -78,7 +73,7 @@ class Agent(BaseModel):
|
|
78
73
|
# 'rag': self.rag,
|
79
74
|
# }
|
80
75
|
|
81
|
-
def
|
76
|
+
def call_with_fn_calling_old(self, input: str = "", history = []) -> dict:
|
82
77
|
litellm.set_verbose = True
|
83
78
|
model = self.model
|
84
79
|
# Set environmental variable
|
@@ -106,7 +101,41 @@ class Agent(BaseModel):
|
|
106
101
|
{ "content": input, "role": "user"},
|
107
102
|
]
|
108
103
|
print(f"Tools: {self.tools}")
|
109
|
-
|
104
|
+
print(f"calling litellm with model {model}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
|
105
|
+
response = litellm.completion(model=model, messages=messages, max_retries=0, base_url=base_url)
|
106
|
+
print(f"Response: {response}")
|
107
|
+
answer = response.choices[0].message.content
|
108
|
+
print(f"Answer: {answer}")
|
109
|
+
return {
|
110
|
+
"answer": answer,
|
111
|
+
|
112
|
+
}
|
113
|
+
|
114
|
+
def call_with_fn_calling(self, input: str = "", history = []) -> dict:
|
115
|
+
litellm.set_verbose = True
|
116
|
+
model = self.model
|
117
|
+
# Set environmental variable
|
118
|
+
if self.api_key.startswith('sk-'):
|
119
|
+
os.environ["OPENAI_API_KEY"] = self.api_key
|
120
|
+
self.api_url = "https://api.openai.com/v1"
|
121
|
+
elif self.api_key and self.api_key != '':
|
122
|
+
model = 'mistral/'+self.model
|
123
|
+
os.environ["MISTRAL_API_KEY"] = self.api_key
|
124
|
+
self.api_url = "https://api.mistral.ai/v1"
|
125
|
+
else:
|
126
|
+
print("Provider Ollama")
|
127
|
+
model = 'ollama/'+self.model
|
128
|
+
if self.api_url is None:
|
129
|
+
self.api_url = "https://api.ollama.ai/v1"
|
130
|
+
|
131
|
+
base_url = str(self.api_url.rstrip('/'))
|
132
|
+
messages: list[dict] = [
|
133
|
+
{"content": self.prompt, "role": "system"},
|
134
|
+
*history,
|
135
|
+
{ "content": input, "role": "user"},
|
136
|
+
]
|
137
|
+
print(f"Tools: {self.tools}")
|
138
|
+
tools = [ToolService.get_by_name_and_user_id(tool, self.user_id) for tool in self.tools]
|
110
139
|
if tools and litellm.supports_function_calling(model=model):
|
111
140
|
print(f"calling litellm with model {model}, tools: {tools}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
|
112
141
|
response = litellm.completion(model=model, messages=messages, max_retries=0, tools=tools, tool_choice="auto", base_url=base_url)
|
@@ -114,14 +143,14 @@ class Agent(BaseModel):
|
|
114
143
|
print(f"calling litellm with model {model}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
|
115
144
|
response = litellm.completion(model=model, messages=messages, max_retries=0, base_url=base_url)
|
116
145
|
print(f"Response: {response}")
|
117
|
-
|
118
|
-
if hasattr(response, 'tool_calls'):
|
119
|
-
|
120
|
-
if tool_calls:
|
121
|
-
for tool_call in tool_calls:
|
146
|
+
answer = response.choices[0].message
|
147
|
+
if hasattr(response, 'tool_calls') and response.tool_calls:
|
148
|
+
for tool_call in response.tool_calls:
|
122
149
|
print(f"Calling function {tool_call.function.name}")
|
123
150
|
function_args = json.loads(tool_call.function.arguments)
|
124
|
-
|
151
|
+
tool = tools.get(tool_call.function.name)
|
152
|
+
ToolService.get
|
153
|
+
function_response = self.call_api(
|
125
154
|
url=function_args["url"],
|
126
155
|
method=function_args["method"],
|
127
156
|
body=function_args["body"],
|
@@ -137,9 +166,7 @@ class Agent(BaseModel):
|
|
137
166
|
response = litellm.completion(
|
138
167
|
model=model,
|
139
168
|
messages=messages,
|
140
|
-
)
|
141
|
-
print("\nSecond LLM response:\n", response)
|
142
|
-
answer = response.choices[0].message.content
|
169
|
+
)
|
143
170
|
print(f"Answer: {answer}")
|
144
171
|
return {
|
145
172
|
"answer": answer,
|
@@ -1,8 +1,10 @@
|
|
1
|
-
from pydantic import BaseModel,
|
1
|
+
from pydantic import BaseModel, Field
|
2
2
|
from typing import Optional, Dict, Any, Literal
|
3
3
|
import re
|
4
4
|
from datetime import datetime
|
5
5
|
import yaml
|
6
|
+
import requests
|
7
|
+
import json
|
6
8
|
|
7
9
|
APIUrlTypes = Literal['openapi']
|
8
10
|
|
@@ -71,6 +73,43 @@ class MCPTool(BaseModel):
|
|
71
73
|
tool.validate()
|
72
74
|
return tool
|
73
75
|
|
76
|
+
def call(self, args: dict) -> str:
|
77
|
+
if not self.api_url:
|
78
|
+
raise ValueError("MCP tool is missing an `api_url`")
|
79
|
+
|
80
|
+
headers = {
|
81
|
+
"Content-Type": "application/json",
|
82
|
+
"User-Agent": "Fyodorov-Agent/1.0",
|
83
|
+
}
|
84
|
+
|
85
|
+
# Handle authentication
|
86
|
+
if self.auth_method == "bearer":
|
87
|
+
token = self.auth_info.get("token")
|
88
|
+
if not token:
|
89
|
+
raise ValueError("Bearer token required but not provided in `auth_info`")
|
90
|
+
headers["Authorization"] = f"Bearer {token}"
|
91
|
+
elif self.auth_method == "basic":
|
92
|
+
user = self.auth_info.get("username")
|
93
|
+
pwd = self.auth_info.get("password")
|
94
|
+
if not user or not pwd:
|
95
|
+
raise ValueError("Basic auth requires `username` and `password` in `auth_info`")
|
96
|
+
auth = (user, pwd)
|
97
|
+
else:
|
98
|
+
auth = None # anonymous access
|
99
|
+
|
100
|
+
try:
|
101
|
+
print(f"Calling MCP tool at {self.api_url} with args: {args}")
|
102
|
+
response = requests.post(self.api_url, json=args, headers=headers, auth=auth)
|
103
|
+
response.raise_for_status()
|
104
|
+
content_type = response.headers.get("Content-Type", "")
|
105
|
+
if "application/json" in content_type:
|
106
|
+
return json.dumps(response.json(), indent=2)
|
107
|
+
return response.text
|
108
|
+
except requests.RequestException as e:
|
109
|
+
print(f"Error calling MCP tool: {e}")
|
110
|
+
return f"Error calling tool: {e}"
|
111
|
+
|
112
|
+
|
74
113
|
def to_dict(self) -> dict:
|
75
114
|
"""
|
76
115
|
Convert this Pydantic model to a plain dict (e.g., for inserting into Supabase).
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from fyodorov_utils.config.supabase import get_supabase
|
3
|
+
from fyodorov_llm_agents.tools.mcp_tool import MCPTool as ToolModel
|
4
|
+
|
5
|
+
class MCPTool():
|
6
|
+
@staticmethod
|
7
|
+
def create_in_db(access_token: str, tool: ToolModel, user_id: str) -> str:
|
8
|
+
try:
|
9
|
+
supabase = get_supabase(access_token)
|
10
|
+
tool_dict = tool.to_dict()
|
11
|
+
tool_dict['user_id'] = user_id
|
12
|
+
del tool_dict['id']
|
13
|
+
del tool_dict['created_at']
|
14
|
+
del tool_dict['updated_at']
|
15
|
+
print('creating tool in db', tool_dict)
|
16
|
+
result = supabase.table('mcp_tools').insert(tool_dict).execute()
|
17
|
+
tool_id = result.data[0]['id']
|
18
|
+
return tool_id
|
19
|
+
except Exception as e:
|
20
|
+
print('Error creating tool', str(e))
|
21
|
+
raise e
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def update_in_db(access_token: str, id: str, tool: ToolModel) -> dict:
|
25
|
+
if not id:
|
26
|
+
raise ValueError('Tool ID is required')
|
27
|
+
try:
|
28
|
+
supabase = get_supabase(access_token)
|
29
|
+
tool_dict = tool.to_dict()
|
30
|
+
print('updating tool in db', tool_dict)
|
31
|
+
result = supabase.table('mcp_tools').update(tool_dict).eq('id', id).execute()
|
32
|
+
return result.data[0]
|
33
|
+
except Exception as e:
|
34
|
+
print('An error occurred while updating tool:', id, str(e))
|
35
|
+
raise
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def delete_in_db(access_token: str, id: str) -> bool:
|
39
|
+
if not id:
|
40
|
+
raise ValueError('Tool ID is required')
|
41
|
+
try:
|
42
|
+
supabase = get_supabase(access_token)
|
43
|
+
result = supabase.table('mcp_tools').delete().eq('id', id).execute()
|
44
|
+
print('Deleted tool', result)
|
45
|
+
return True
|
46
|
+
except Exception as e:
|
47
|
+
print('Error deleting tool', str(e))
|
48
|
+
raise e
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def get_in_db(access_token: str, id: str) -> ToolModel:
|
52
|
+
if not id:
|
53
|
+
raise ValueError('Tool ID is required')
|
54
|
+
try:
|
55
|
+
supabase = get_supabase(access_token)
|
56
|
+
result = supabase.table('mcp_tools').select('*').eq('id', id).limit(1).execute()
|
57
|
+
tool_dict = result.data[0]
|
58
|
+
tool = ToolModel(**tool_dict)
|
59
|
+
return tool
|
60
|
+
except Exception as e:
|
61
|
+
print('Error fetching tool', str(e))
|
62
|
+
raise e
|
63
|
+
|
64
|
+
@staticmethod
|
65
|
+
def get_by_user_and_name_in_db(access_token: str, user_id: str, name: str) -> ToolModel:
|
66
|
+
try:
|
67
|
+
supabase = get_supabase(access_token)
|
68
|
+
result = supabase.table('mcp_tools').select('*').eq('user_id', user_id).eq('name', name).limit(1).execute()
|
69
|
+
tool_dict = result.data[0]
|
70
|
+
tool = ToolModel(**tool_dict)
|
71
|
+
return tool
|
72
|
+
except Exception as e:
|
73
|
+
print('Error fetching tool', str(e))
|
74
|
+
raise e
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def get_all_in_db(access_token: str, user_id: str = None, limit: int = 10, created_at_lt: datetime = datetime.now()) -> list[dict]:
|
78
|
+
try:
|
79
|
+
supabase = get_supabase(access_token)
|
80
|
+
print('getting tools from db for user', user_id)
|
81
|
+
tools = []
|
82
|
+
result = supabase.from_('mcp_tools') \
|
83
|
+
.select("*") \
|
84
|
+
.limit(limit) \
|
85
|
+
.lt('created_at', created_at_lt) \
|
86
|
+
.order('created_at', desc=True) \
|
87
|
+
.execute()
|
88
|
+
for tool in result.data:
|
89
|
+
tool["id"] = str(tool["id"])
|
90
|
+
tool["user_id"] = str(tool["user_id"])
|
91
|
+
tool["created_at"] = str(tool["created_at"])
|
92
|
+
tool["updated_at"] = str(tool["updated_at"])
|
93
|
+
if tool and (tool['public'] == True or (user_id and 'user_id' in tool and tool['user_id'] == user_id)):
|
94
|
+
print('tool is public or belongs to user', tool)
|
95
|
+
tools.append(tool)
|
96
|
+
print('got tools from db', len(tools))
|
97
|
+
return tools
|
98
|
+
except Exception as e:
|
99
|
+
print('Error fetching tools', str(e))
|
100
|
+
raise e
|
101
|
+
|
102
|
+
@staticmethod
|
103
|
+
def get_tool_agents(access_token: str, id: str) -> list[int]:
|
104
|
+
if not id:
|
105
|
+
raise ValueError('Tool ID is required')
|
106
|
+
try:
|
107
|
+
supabase = get_supabase(access_token)
|
108
|
+
result = supabase.table('agent_mcp_tool').select('*').eq('mcp_tool_id', id).execute()
|
109
|
+
tool_agents = [item['agent_id'] for item in result.data if 'agent_id' in item]
|
110
|
+
return tool_agents
|
111
|
+
except Exception as e:
|
112
|
+
print('Error fetching tool agents', str(e))
|
113
|
+
raise
|
114
|
+
|
115
|
+
@staticmethod
|
116
|
+
def set_tool_agents(access_token: str, id: str, agent_ids: list[int]) -> list[int]:
|
117
|
+
if not id:
|
118
|
+
raise ValueError('Tool ID is required')
|
119
|
+
try:
|
120
|
+
supabase = get_supabase(access_token)
|
121
|
+
for agent_id in agent_ids:
|
122
|
+
# Check if agent is valid and exists in the database
|
123
|
+
agent_result = supabase.table('agents').select('*').eq('id', agent_id).limit(1).execute()
|
124
|
+
if not agent_result.data:
|
125
|
+
print(f"Agent with ID {agent_id} does not exist.")
|
126
|
+
continue
|
127
|
+
# Insert the agent-tool relationship
|
128
|
+
supabase.table('agent_mcp_tool').insert({'mcp_tool_id': id, 'agent_id': agent_id}).execute()
|
129
|
+
print('Inserted tool agents', agent_ids)
|
130
|
+
return agent_ids
|
131
|
+
except Exception as e:
|
132
|
+
print('Error setting tool agents', str(e))
|
133
|
+
raise
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fyodorov_llm_agents
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.148
|
4
4
|
Summary: LLM agents for the Fyodorov AI suite
|
5
5
|
Author-email: Daniel Ransom <02masseur.alibis@icloud.com>
|
6
6
|
Project-URL: Homepage, https://github.com/FyodorovAI/fyodorov-llm-agents
|
@@ -0,0 +1,9 @@
|
|
1
|
+
fyodorov_llm_agents/agents/agent.py,sha256=NbjiyydsrNUdEQYZ_rR1YpAZ-oqRZqxKEwStWxOkxpE,8010
|
2
|
+
fyodorov_llm_agents/agents/openai.py,sha256=FA5RS7yn3JwvFA8PXju60XSYC_2oUZFNgBUzeIYtGv0,1154
|
3
|
+
fyodorov_llm_agents/tools/mcp_tool_model.py,sha256=i7oFZReVGZsHN2K8T7mCAGF4t92wMMIR9mCkRHUgKsI,4413
|
4
|
+
fyodorov_llm_agents/tools/mcp_tool_service.py,sha256=MWf-wKu0iEUd7K_iXEClAqr2kB9gTJPlXoVnTva4DSQ,5558
|
5
|
+
fyodorov_llm_agents/tools/tool.py,sha256=HyOk0X_3XE23sa8J-8UZx657tJ0sxwZWMbA4OPxXU6E,7940
|
6
|
+
fyodorov_llm_agents-0.2.148.dist-info/METADATA,sha256=BO7MA-li_5Lt07aUfM2_Wx7rM1Lf-zURXMbDXAnad24,552
|
7
|
+
fyodorov_llm_agents-0.2.148.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
8
|
+
fyodorov_llm_agents-0.2.148.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
|
9
|
+
fyodorov_llm_agents-0.2.148.dist-info/RECORD,,
|
@@ -1,8 +0,0 @@
|
|
1
|
-
fyodorov_llm_agents/agents/agent.py,sha256=Lb9wBH1OnDvzYAOc4LFg4MWoMJwburC_wdxSygEy5KY,6781
|
2
|
-
fyodorov_llm_agents/agents/openai.py,sha256=FA5RS7yn3JwvFA8PXju60XSYC_2oUZFNgBUzeIYtGv0,1154
|
3
|
-
fyodorov_llm_agents/tools/mcp_tool.py,sha256=E-LAx2rYxG4ujBtqzeHDbHLxanlT9qnjdal7aMvhYEA,2899
|
4
|
-
fyodorov_llm_agents/tools/tool.py,sha256=HyOk0X_3XE23sa8J-8UZx657tJ0sxwZWMbA4OPxXU6E,7940
|
5
|
-
fyodorov_llm_agents-0.2.146.dist-info/METADATA,sha256=X2pNnm3HeOABgNvhftOc2wa17nEO8uirCcxwCngPGV4,552
|
6
|
-
fyodorov_llm_agents-0.2.146.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
7
|
-
fyodorov_llm_agents-0.2.146.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
|
8
|
-
fyodorov_llm_agents-0.2.146.dist-info/RECORD,,
|
{fyodorov_llm_agents-0.2.146.dist-info → fyodorov_llm_agents-0.2.148.dist-info}/top_level.txt
RENAMED
File without changes
|