fyodorov-llm-agents 0.2.145__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
@@ -23,7 +19,6 @@ class Agent(BaseModel):
23
19
  api_url: HttpUrl | None = None
24
20
  tools: list[str] = []
25
21
  rag: list[dict] = []
26
- chat_history: Optional[list[dict]] = []
27
22
  model: str | None = None
28
23
  modelid: str | None = None
29
24
  name: str = "My Agent"
@@ -79,7 +74,7 @@ class Agent(BaseModel):
79
74
  # 'rag': self.rag,
80
75
  # }
81
76
 
82
- def call_with_fn_calling(self, input: str = "", history = []) -> dict:
77
+ def call_with_fn_calling_old(self, input: str = "", history = []) -> dict:
83
78
  litellm.set_verbose = True
84
79
  model = self.model
85
80
  # Set environmental variable
@@ -107,7 +102,41 @@ class Agent(BaseModel):
107
102
  { "content": input, "role": "user"},
108
103
  ]
109
104
  print(f"Tools: {self.tools}")
110
- 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]
111
140
  if tools and litellm.supports_function_calling(model=model):
112
141
  print(f"calling litellm with model {model}, tools: {tools}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
113
142
  response = litellm.completion(model=model, messages=messages, max_retries=0, tools=tools, tool_choice="auto", base_url=base_url)
@@ -115,14 +144,14 @@ class Agent(BaseModel):
115
144
  print(f"calling litellm with model {model}, messages: {messages}, max_retries: 0, history: {history}, base_url: {base_url}")
116
145
  response = litellm.completion(model=model, messages=messages, max_retries=0, base_url=base_url)
117
146
  print(f"Response: {response}")
118
- tool_calls = []
119
- if hasattr(response, 'tool_calls'):
120
- tool_calls = response.tool_calls if response.tool_calls else []
121
- if tool_calls:
122
- 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:
123
150
  print(f"Calling function {tool_call.function.name}")
124
151
  function_args = json.loads(tool_call.function.arguments)
125
- function_response = self.call_api(
152
+ tool = tools.get(tool_call.function.name)
153
+ ToolService.get
154
+ function_response = self.call_api(
126
155
  url=function_args["url"],
127
156
  method=function_args["method"],
128
157
  body=function_args["body"],
@@ -138,9 +167,7 @@ class Agent(BaseModel):
138
167
  response = litellm.completion(
139
168
  model=model,
140
169
  messages=messages,
141
- ) # get a new response from the model where it can see the function response
142
- print("\nSecond LLM response:\n", response)
143
- answer = response.choices[0].message.content
170
+ )
144
171
  print(f"Answer: {answer}")
145
172
  return {
146
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.145
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=OJsNYAdmH0nsCMP7RVnYkjx8_QYPA0VaBxc1kj9aY9M,6825
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.145.dist-info/METADATA,sha256=09_rBHpBPqvWtRX5IIz68v8aIXBBu0L3LcA9vjTxZ40,552
6
- fyodorov_llm_agents-0.2.145.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
7
- fyodorov_llm_agents-0.2.145.dist-info/top_level.txt,sha256=4QOslsBp8Gh7ng25DceA7fHp4KguTIdAxwURz97gH-g,20
8
- fyodorov_llm_agents-0.2.145.dist-info/RECORD,,