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.
@@ -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.mcp_tool import MCPTool as Tool
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 call_with_fn_calling(self, input: str = "", history = []) -> dict:
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
- tools = [tool.get_function() for tool in self.tools]
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
- 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:
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
- function_response = self.call_api(
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
- ) # 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
169
+ )
143
170
  print(f"Answer: {answer}")
144
171
  return {
145
172
  "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.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,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,,