fyodorov-llm-agents 0.2.146__py3-none-any.whl → 0.2.147__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.
@@ -1,15 +1,11 @@
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
7
  from fyodorov_llm_agents.tools.mcp_tool import MCPTool as Tool
8
+ from fyodorov_utils.services.mcp_tool import MCPTool as ToolService
13
9
  from datetime import datetime
14
10
 
15
11
  MAX_NAME_LENGTH = 80
@@ -78,7 +74,7 @@ class Agent(BaseModel):
78
74
  # 'rag': self.rag,
79
75
  # }
80
76
 
81
- def call_with_fn_calling(self, input: str = "", history = []) -> dict:
77
+ def call_with_fn_calling_old(self, input: str = "", history = []) -> dict:
82
78
  litellm.set_verbose = True
83
79
  model = self.model
84
80
  # Set environmental variable
@@ -106,7 +102,41 @@ class Agent(BaseModel):
106
102
  { "content": input, "role": "user"},
107
103
  ]
108
104
  print(f"Tools: {self.tools}")
109
- tools = [tool.get_function() for tool in self.tools]
105
+ print(f"calling litellm with model {model}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
106
+ response = litellm.completion(model=model, messages=messages, max_retries=0, base_url=base_url)
107
+ print(f"Response: {response}")
108
+ answer = response.choices[0].message.content
109
+ print(f"Answer: {answer}")
110
+ return {
111
+ "answer": answer,
112
+
113
+ }
114
+
115
+ def call_with_fn_calling(self, input: str = "", history = []) -> dict:
116
+ litellm.set_verbose = True
117
+ model = self.model
118
+ # Set environmental variable
119
+ if self.api_key.startswith('sk-'):
120
+ os.environ["OPENAI_API_KEY"] = self.api_key
121
+ self.api_url = "https://api.openai.com/v1"
122
+ elif self.api_key and self.api_key != '':
123
+ model = 'mistral/'+self.model
124
+ os.environ["MISTRAL_API_KEY"] = self.api_key
125
+ self.api_url = "https://api.mistral.ai/v1"
126
+ else:
127
+ print("Provider Ollama")
128
+ model = 'ollama/'+self.model
129
+ if self.api_url is None:
130
+ self.api_url = "https://api.ollama.ai/v1"
131
+
132
+ base_url = str(self.api_url.rstrip('/'))
133
+ messages: list[dict] = [
134
+ {"content": self.prompt, "role": "system"},
135
+ *history,
136
+ { "content": input, "role": "user"},
137
+ ]
138
+ print(f"Tools: {self.tools}")
139
+ tools = [ToolService.get_by_name_and_user_id(tool, self.user_id) for tool in self.tools]
110
140
  if tools and litellm.supports_function_calling(model=model):
111
141
  print(f"calling litellm with model {model}, tools: {tools}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
112
142
  response = litellm.completion(model=model, messages=messages, max_retries=0, tools=tools, tool_choice="auto", base_url=base_url)
@@ -114,14 +144,14 @@ class Agent(BaseModel):
114
144
  print(f"calling litellm with model {model}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
115
145
  response = litellm.completion(model=model, messages=messages, max_retries=0, base_url=base_url)
116
146
  print(f"Response: {response}")
117
- tool_calls = []
118
- if hasattr(response, 'tool_calls'):
119
- tool_calls = response.tool_calls if response.tool_calls else []
120
- if tool_calls:
121
- for tool_call in tool_calls:
147
+ answer = response.choices[0].message
148
+ if hasattr(response, 'tool_calls') and response.tool_calls:
149
+ for tool_call in response.tool_calls:
122
150
  print(f"Calling function {tool_call.function.name}")
123
151
  function_args = json.loads(tool_call.function.arguments)
124
- function_response = self.call_api(
152
+ tool = tools.get(tool_call.function.name)
153
+ ToolService.get
154
+ function_response = self.call_api(
125
155
  url=function_args["url"],
126
156
  method=function_args["method"],
127
157
  body=function_args["body"],
@@ -137,9 +167,7 @@ class Agent(BaseModel):
137
167
  response = litellm.completion(
138
168
  model=model,
139
169
  messages=messages,
140
- ) # get a new response from the model where it can see the function response
141
- print("\nSecond LLM response:\n", response)
142
- answer = response.choices[0].message.content
170
+ )
143
171
  print(f"Answer: {answer}")
144
172
  return {
145
173
  "answer": answer,
@@ -1,8 +1,10 @@
1
- from pydantic import BaseModel, HttpUrl, Field
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.146
3
+ Version: 0.2.147
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=X8CCR34yf4tcdbcPsuFQBL043FjXo2syNpHWA1ou-zY,8063
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.147.dist-info/METADATA,sha256=m9V7cnyWTdnQGIhZvnqjjPh1TT2be81ee9CHGnDLgEM,552
7
+ fyodorov_llm_agents-0.2.147.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
8
+ fyodorov_llm_agents-0.2.147.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
9
+ fyodorov_llm_agents-0.2.147.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.1)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,