agentx-dev 2.0__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.
@@ -0,0 +1,216 @@
1
+ from typing import Optional, Union, Dict, List, Iterable, Literal, Any
2
+ from openai import OpenAI
3
+ from openai.types.chat import ChatCompletion, ChatCompletionMessageParam
4
+ from openai.types.chat.completion_create_params import (
5
+ FunctionCall, Function, ResponseFormat
6
+ )
7
+ from openai._types import NOT_GIVEN, NotGiven
8
+ import logging,os
9
+
10
+ logger = logging.getLogger(__name__)
11
+ from abc import ABC, abstractmethod
12
+ class BaseChatModel:
13
+ """
14
+ An abstract base class defining the standard interface for chat models.
15
+
16
+ This class establishes a contract for all model integrations, ensuring they
17
+ can be used interchangeably within the framework. It defines both standard
18
+ invocation and streaming, with asynchronous methods as the core implementation
19
+ and synchronous wrappers provided for convenience.
20
+ """
21
+
22
+ class GPT(BaseChatModel):
23
+ """
24
+ Wrapper class for interacting with OpenAI's Chat Completions API using configurable defaults.
25
+
26
+ Attributes:
27
+ api_key (Optional[str]): API key for OpenAI. Defaults to the 'OPENAI_API_KEY' environment variable.
28
+ client (OpenAI): An instance of the OpenAI client.
29
+ defaults (dict): Default parameters for chat completion requests.
30
+ timeout (float): Timeout in seconds for requests.
31
+ """
32
+ def __init__(
33
+
34
+ self,
35
+ # API config
36
+ api_key: Optional[str] = None,
37
+ organization: Optional[str] = None,
38
+ base_url: Optional[str] = None,
39
+ timeout: float = 60.0,
40
+ max_retries: int = 3,
41
+
42
+ # Default chat params
43
+ model: str = "gpt-4o",
44
+ temperature: float | NotGiven = NOT_GIVEN,
45
+ max_tokens: int | NotGiven = NOT_GIVEN,
46
+ top_p: float | NotGiven = NOT_GIVEN,
47
+ n: int | NotGiven = NOT_GIVEN,
48
+ stream_options: Any | NotGiven = NOT_GIVEN,
49
+ stop: Union[str, List[str]] | NotGiven = NOT_GIVEN,
50
+ presence_penalty: float | NotGiven = NOT_GIVEN,
51
+ frequency_penalty: float | NotGiven = NOT_GIVEN,
52
+ logit_bias: Dict[str, int] | NotGiven = NOT_GIVEN,
53
+ user: str | NotGiven = NOT_GIVEN,
54
+ functions: Iterable[Function] | NotGiven = NOT_GIVEN,
55
+ function_call: FunctionCall | NotGiven = NOT_GIVEN,
56
+ tool_choice: Any | NotGiven = NOT_GIVEN,
57
+ tools: Iterable[Any] | NotGiven = NOT_GIVEN,
58
+ logprobs: bool | NotGiven = NOT_GIVEN,
59
+ top_logprobs: int | NotGiven = NOT_GIVEN,
60
+ response_format: ResponseFormat | NotGiven = NOT_GIVEN,
61
+ seed: int | NotGiven = NOT_GIVEN,
62
+ service_tier: Literal["auto", "default"] | NotGiven = NOT_GIVEN,
63
+ metadata: Dict[str, str] | NotGiven = NOT_GIVEN,
64
+ store: bool | NotGiven = NOT_GIVEN,
65
+ parallel_tool_calls: bool | NotGiven = NOT_GIVEN,
66
+ ):
67
+ """
68
+ Initialize the GPT client with configurable API and model parameters.
69
+
70
+ Args:
71
+ api_key (Optional[str]): Your OpenAI API key.
72
+ organization (Optional[str]): OpenAI organization ID (if applicable).
73
+ base_url (Optional[str]): Base URL for the OpenAI API.
74
+ timeout (float): Request timeout in seconds. Default is 60.0.
75
+ max_retries (int): Maximum number of retries on failed requests.
76
+
77
+ model (str): Default model to use (e.g., "gpt-4o").
78
+ temperature (float | NotGiven): Sampling temperature.
79
+ max_tokens (int | NotGiven): Maximum number of tokens in the response.
80
+ top_p (float | NotGiven): Nucleus sampling parameter.
81
+ n (int | NotGiven): Number of completions to generate.
82
+ stream_options (Any | NotGiven): Streaming config.
83
+ stop (Union[str, List[str]] | NotGiven): Stop sequences.
84
+ presence_penalty (float | NotGiven): Penalty for introducing new topics.
85
+ frequency_penalty (float | NotGiven): Penalty for repeating tokens.
86
+ logit_bias (Dict[str, int] | NotGiven): Biasing the logits for certain tokens.
87
+ user (str | NotGiven): Unique identifier for the end user.
88
+ functions (Iterable[Function] | NotGiven): Callable functions for the model.
89
+ function_call (FunctionCall | NotGiven): Control how functions are called.
90
+ tool_choice (Any | NotGiven): Tool selection strategy.
91
+ tools (Iterable[Any] | NotGiven): List of available tools.
92
+ logprobs (bool | NotGiven): Whether to return log probabilities.
93
+ top_logprobs (int | NotGiven): Number of most likely tokens to return.
94
+ response_format (ResponseFormat | NotGiven): Format of the response.
95
+ seed (int | NotGiven): Seed for reproducibility.
96
+ service_tier (Literal["auto", "default"] | NotGiven): API service tier.
97
+ metadata (Dict[str, str] | NotGiven): Custom metadata.
98
+ store (bool | NotGiven): Whether to store the request.
99
+ parallel_tool_calls (bool | NotGiven): Whether to allow parallel tool calls.
100
+ """
101
+ self.api_key = api_key or os.getenv("OPENAI_API_KEY")
102
+ self.client = OpenAI(
103
+ api_key=self.api_key,
104
+ organization=organization,
105
+ base_url=base_url,
106
+ timeout=timeout,
107
+ max_retries=max_retries
108
+ )
109
+
110
+
111
+ self.defaults = {
112
+ "model": model,
113
+ "temperature": temperature,
114
+ "max_tokens": max_tokens,
115
+ "top_p": top_p,
116
+ "n": n,
117
+ "stream_options": stream_options,
118
+ "stop": stop,
119
+ "presence_penalty": presence_penalty,
120
+ "frequency_penalty": frequency_penalty,
121
+ "logit_bias": logit_bias,
122
+ "user": user,
123
+ "functions": functions,
124
+ "function_call": function_call,
125
+ "tool_choice": tool_choice,
126
+ "tools": tools,
127
+ "logprobs": logprobs,
128
+ "top_logprobs": top_logprobs,
129
+ "response_format": response_format,
130
+ "seed": seed,
131
+ "service_tier": service_tier,
132
+ "metadata": metadata,
133
+ "store": store,
134
+ "parallel_tool_calls": parallel_tool_calls,
135
+ }
136
+
137
+ self.timeout = timeout # Keep around for later usage
138
+
139
+ def Initialize(
140
+ self,
141
+ messages: Iterable[ChatCompletionMessageParam],
142
+ stream: Optional[bool] | NotGiven = NOT_GIVEN,
143
+ extra_headers: Optional[Dict[str, str]] = None,
144
+ extra_query: Optional[Dict[str, Any]] = None,
145
+ extra_body: Optional[Dict[str, Any]] = None,
146
+ timeout: Optional[float] = None,
147
+
148
+ ) :
149
+ """
150
+ Create a chat completion using OpenAI API.
151
+
152
+ Args:
153
+ messages (Iterable[ChatCompletionMessageParam]): The list of messages for the conversation.
154
+ stream (Optional[bool] | NotGiven): Whether to stream the response.
155
+ extra_headers (Optional[Dict[str, str]]): Additional request headers.
156
+ extra_query (Optional[Dict[str, Any]]): Additional query parameters.
157
+ extra_body (Optional[Dict[str, Any]]): Additional body parameters.
158
+ timeout (Optional[float]): Override the default timeout.
159
+
160
+ Returns:
161
+ ChatCompletion: The OpenAI ChatCompletion response object.
162
+ """
163
+ try:
164
+
165
+
166
+ logger.debug(f"Calling OpenAI chat.completions.create ")
167
+
168
+ return self.extract_content(self.client.chat.completions.create(
169
+ messages=messages,
170
+ **self.defaults,
171
+ extra_headers=extra_headers,
172
+ extra_query=extra_query,
173
+ extra_body=extra_body,
174
+ timeout=timeout or self.timeout,
175
+ ))
176
+ except Exception as e:
177
+ logger.error(f"Error during chat completion: {str(e)}")
178
+ raise
179
+
180
+ def create_stream(self, messages: Iterable[ChatCompletionMessageParam], **kwargs):
181
+ """
182
+ Create a streamed chat completion.
183
+
184
+ Args:
185
+ messages (Iterable[ChatCompletionMessageParam]): List of chat messages.
186
+ **kwargs: Additional keyword arguments passed to `create`.
187
+
188
+ Returns:
189
+ ChatCompletion: The streamed completion response.
190
+ """
191
+ self.defaults["stream"] = True
192
+ return self.create(messages=messages, **kwargs)
193
+
194
+ def update_defaults(self, **kwargs):
195
+ for key, value in kwargs.items():
196
+ if key in self.defaults:
197
+ self.defaults[key] = value
198
+ logger.info(f"Updated default parameter {key} = {value}")
199
+
200
+ def extract_content(self, completion: ChatCompletion) -> str:
201
+ """
202
+ Extract content from a chat completion response
203
+
204
+ Args:
205
+ completion: ChatCompletion object
206
+
207
+ Returns:
208
+ The content string from the first choice
209
+ """
210
+
211
+ if completion.choices and len(completion.choices) > 0:
212
+ return completion.choices[0].message.content or ""
213
+ return ""
214
+
215
+
216
+
@@ -0,0 +1,338 @@
1
+ from AgentXL.Agents import AgentFormattor,AgentCompletion,AgentPrompt
2
+ from AgentXL.ChatModel import BaseChatModel
3
+ from AgentXL.Agents.Agent import StandardParser,ToolCall
4
+ from AgentXL.Tools import StandardTool,StructuredTool,logger
5
+ from typing import Dict, Callable, List, Type, Optional
6
+ from pydantic import BaseModel,Field
7
+
8
+ import json,uuid
9
+
10
+
11
+
12
+
13
+ class AgentRunner:
14
+ """
15
+ The main engine that orchestrates the agent's execution loop.
16
+
17
+ This class connects all the components of the framework: the LLM, the tools,
18
+ and the prompt. It manages the agent's state, including its conversational
19
+ history and internal scratchpad, and runs the primary "reason-act" cycle.
20
+
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ model:BaseChatModel,
26
+ Agent: AgentFormattor | AgentPrompt | str,
27
+ tools: List[StandardTool|StructuredTool], # A list of StandardTool and/or StructuredTool instances
28
+ max_iterations: int | None = None,
29
+ ):
30
+ """
31
+ Initializes the AgentRunner.
32
+
33
+ Args:
34
+ llm_model (BaseChatModel): The identifier for the LLM model to be used.
35
+ Agent: The agent's prompt template or instances of (AgentFormattor, AgentPrompt or string ).
36
+ tools (List): A list of tool instances available to the agent.
37
+ max_iterations (Optional[int]): The maximum number of tool-calling cycles.
38
+
39
+ Raises:
40
+ TypeError: If any item in the `tools` list is not an instance of
41
+ StandardTool or StructuredTool.
42
+ """
43
+ self.Query = ""
44
+ self.Agent = Agent
45
+ self.tools = tools
46
+ self.max_iterations = max_iterations if max_iterations else 4
47
+ self.model = model
48
+
49
+
50
+ # This dictionary is useful for dispatching any tool type, so we'll build it first
51
+ # Dictionaries for quick lookup
52
+ self.func: Dict[str, Callable] = {}
53
+ self.args: Dict[str, Dict] = {}
54
+
55
+
56
+ # History and intermediate steps
57
+ self.history: List[Dict[str, str]] = []
58
+ self.agent_scratchpad = []
59
+ self.Steps = ''
60
+
61
+ # --- Tool Registration with Type Checking---
62
+ # Iterate through the provided tools and register them for execution.
63
+ for tool_instance in self.tools:
64
+ # Use isinstance() for robust type checking.
65
+ if isinstance(tool_instance, StandardTool):
66
+ # It's a StandardTool, register its function.
67
+ self.func[tool_instance.name] = tool_instance.func
68
+
69
+ elif isinstance(tool_instance, StructuredTool):
70
+ # It's a StructuredTool, register its function and schema.
71
+ self.args[tool_instance.name] = {
72
+ "func": tool_instance.func,
73
+ "args_schema": tool_instance.args_schema
74
+ }
75
+ tool_instance.description=tool_instance.description + str(tool_instance.args_schema.__signature__.parameters)
76
+ else:
77
+ # If it's neither, raise an error with a helpful message.
78
+ tool_type = type(tool_instance).__name__
79
+ raise TypeError(
80
+ f"Unsupported tool type found in 'tools' list. "
81
+ f"Expected an instance of 'StandardTool' or 'StructuredTool', "
82
+ f"but got an object of type '{tool_type}'."
83
+ )
84
+
85
+ # --- Prompt Formatting ---
86
+
87
+ # This block robust implementation would check the type of 'Agent'.
88
+ self.holder=str(uuid.uuid4())
89
+
90
+ self.tool_info = {
91
+ 'tools': '\n'.join([f"- {t.name} : {t.description }" for t in self.tools]),
92
+ 'tool_names': ', '.join([t.name for t in self.tools]),
93
+ 'user_input' : self.holder
94
+ }
95
+ if isinstance(Agent, str) and '{tools}' in Agent and '{tool_names}' in Agent and '{user_input}' in Agent:
96
+
97
+ # str.format_map requires a dictionary-like object.
98
+ self.format_prompt: str = Agent.format_map(self.tool_info)
99
+ self.Agent=AgentFormattor(prompt=Agent,Agent=StandardParser)
100
+ self.parser=self.Agent.Agent
101
+ # Handle the case where an AgentPrompt object is passed
102
+ elif isinstance(Agent, AgentFormattor) and '{tools}' in Agent.prompt and '{tool_names}' in Agent.prompt and '{user_input}' in Agent.prompt:
103
+
104
+ logger.debug(f"Formatting prompt from AgentFormattor: {self.Agent.prompt}")
105
+ # str.format_map requires a dictionary-like object.
106
+ self.format_prompt: str = Agent.prompt.format_map(self.tool_info)
107
+ self.parser=self.Agent.Agent
108
+
109
+
110
+ elif isinstance(Agent, AgentPrompt) and '{tools}' in Agent.prompt and '{tool_names}' in Agent.prompt and '{user_input}' in Agent.prompt:
111
+
112
+ # str.format_map requires a dictionary-like object.
113
+ self.format_prompt: str = Agent.format(self.tools,user_hold=self.holder)
114
+ self.Agent=AgentFormattor(prompt=Agent.prompt,Agent=StandardParser)
115
+ self.parser=self.Agent.Agent
116
+
117
+
118
+ else:
119
+ # This check is important for ensuring the prompt can be built correctly.
120
+ raise ValueError("The 'Agent' object must be a template string containing '{tools}','{tool_names}',{user_input}, or an AgentFormattor instance.")
121
+ def cleaning(words:str):
122
+ import re
123
+ text=""
124
+ if isinstance(words,list):
125
+ for serches in words:
126
+ text+=re.sub(r"'^[a-zA-Z0-9_-]+$'.", '_',serches)
127
+ words=text
128
+ return words
129
+
130
+ def Tool_Runner(self, tool_name: str, args_str: str) -> str:
131
+ """
132
+ Executes a specified tool with the given arguments.
133
+
134
+ Args:
135
+ tool_name (str): The name of the tool to run.
136
+ args_str (str): A string of arguments for the tool. For structured tools,
137
+ this is expected to be a JSON string.
138
+
139
+ Returns:
140
+ str: The result of the tool execution or an error message.
141
+ """
142
+ # Check if it's a structured tool first.
143
+ if tool_name in self.args:
144
+ schema = self.args[tool_name].get('args_schema')
145
+ func = self.args[tool_name].get('func')
146
+ if schema and func:
147
+ try:
148
+ # Parse the JSON string from the LLM into a dictionary.
149
+ parsed_args = args_str
150
+ result=func(**parsed_args)
151
+ if isinstance(args_str,str):
152
+ parsed_args = json.loads(args_str)
153
+ # Validate and structure the arguments using the Pydantic model.
154
+
155
+
156
+ validated_args = schema(**parsed_args)
157
+ result = func(**validated_args.model_dump())
158
+ # Use .model_dump() to get a dictionary to pass to the function.
159
+ # # Change: Replaced print with logger.info for tracking agent actions.
160
+ logger.info(f"ACTION: {tool_name}, INPUT: {parsed_args}")
161
+
162
+ # Execute the tool's function with the validated arguments.
163
+
164
+
165
+ # # Change: Replaced print with logger.info for tracking tool results.
166
+ logger.info(f"RESULT: {result}")
167
+ return result
168
+ except Exception as e:
169
+ # # Change: Use logger.error to explicitly log exceptions before returning an error message.
170
+ logger.error(f"Error executing structured tool '{tool_name}': {e}", exc_info=True)
171
+ return f"Error executing structured tool '{tool_name}': {e}"
172
+ else:
173
+ # # Change: Log a misconfiguration as an error.
174
+ logger.error(f"Misconfigured structured tool '{tool_name}'.")
175
+ return f"Error: Misconfigured structured tool '{tool_name}'."
176
+
177
+ # Fallback to check for a standard tool.
178
+ elif tool_name in self.func:
179
+ try:
180
+ # # Change: Replaced print with logger.info.
181
+ logger.info(f"ACTION: {tool_name}, INPUT: {args_str}")
182
+ function = self.func[tool_name]
183
+ if not args_str:
184
+ result = function()
185
+ logger.info(f"RESULT: {result}")
186
+ return result
187
+ result = function(args_str) # Standard tools take the raw string argument.
188
+ # # Change: Replaced print with logger.info.
189
+ logger.info(f"RESULT: {result}")
190
+ return result
191
+ except Exception as e:
192
+ # # Change: Use logger.error to log exceptions.
193
+ logger.error(f"Error executing standard tool '{tool_name}': {e}", exc_info=True)
194
+ return f"Error executing standard tool '{tool_name}': {e}"
195
+ else:
196
+ # # Change: Use logger.warning for a non-critical but important issue.
197
+ logger.warning(f"Tool '{tool_name}' not found. Available tools: {list(self.func.keys()) + list(self.args.keys())}")
198
+ return f"Error: Tool '{tool_name}' not found. Please double-check the tool name."
199
+
200
+ def Initialize(self, user_input: str)-> AgentCompletion:
201
+
202
+
203
+ """
204
+ The main agent execution loop. (Currently a placeholder)
205
+
206
+ Args:
207
+ user_input (str): Query to run the Agent with.
208
+
209
+ Returns:
210
+ str: The result of the Agent execution, Agent tool execution or an error message.
211
+ """
212
+ self.Query = user_input
213
+ # Uses the highly efficient .replace() method to swap the prompt_holder with the actual user_input.
214
+ if self.holder in self.format_prompt:
215
+ self.format_prompt = self.format_prompt.replace(self.holder,self.Query)
216
+
217
+
218
+ if not any(entry["role"] == "system" for entry in self.history):
219
+ self.history.append({"role": "system", "content": self.format_prompt})
220
+
221
+ self.history.append({"role":"user","content":self.Query})
222
+
223
+ # logger.info to announce the start of the process.
224
+ logger.info(">>>>> Entering AgentRunner Mode <<<<<")
225
+ if not isinstance(self.model, BaseChatModel):
226
+ raise TypeError(
227
+ f"The 'model' object must be an instance of a class that inherits "
228
+ f"from BaseChatModel, but got type {type(self.model).__name__}."
229
+ )
230
+
231
+ # --- AGENT LOOP LOGIC (PLACEHOLDER) ---
232
+
233
+ count = 1
234
+ tool_calls: List[ToolCall] = []
235
+ steps: List[str] = []
236
+
237
+ count = 1
238
+ final_answer = None
239
+
240
+ while count <= self.max_iterations:
241
+
242
+ response = self.model.Initialize(messages=self.history)
243
+ self.history.append({"role": "assistant", "content": response})
244
+
245
+ model = self.Agent.Agent.from_json(response)
246
+
247
+ if not isinstance(model, self.Agent.Agent):
248
+ self.history.append({"role": "assistant", "content": model})
249
+
250
+ final_answer = model
251
+ break
252
+
253
+ step_description = f"Step {count}: {model.action} with {model.action_input}"
254
+ steps.append(step_description)
255
+
256
+ self.history.append({
257
+ "role": "assistant",
258
+ "content": json.dumps({
259
+ "action": model.action,
260
+ "action_input": model.action_input
261
+ })
262
+ })
263
+
264
+ if model.action == "Final_Answer":
265
+
266
+ final_answer = model.action_input
267
+ break
268
+ print(f"Action : {model.action} \n Action_input : {model.action_input}")
269
+
270
+ print(f"\x1B[3;33m🛠️ Invoking tool: '{model.action}' with args: {model.action_input}\x1B[0m")
271
+
272
+ tool_response = self.Tool_Runner(model.action, model.action_input)
273
+ print(f"\x1B[32m🛠️ Tool Response : {str(tool_response)}\x1B[0m")
274
+
275
+ if str(tool_response).startswith("Error:"):
276
+ self.history.append({
277
+ "role": "function",
278
+ "name": 'Tool_call_error',
279
+ "content": str(tool_response)
280
+ })
281
+ else:
282
+
283
+ self.history.append({
284
+ "role": "function",
285
+ "name": model.action,
286
+ "content": str(tool_response)
287
+ })
288
+
289
+ tool_calls.append(ToolCall(
290
+ name=model.action,
291
+ args={"Input":model.action_input},
292
+ result=str(tool_response)
293
+ ))
294
+
295
+ count += 1
296
+ print(f"\x1B[32m✅ Final Answer: {final_answer}\x1B[0m")
297
+ return AgentCompletion.from_agent(
298
+ model_name=self.model.__class__.__name__,
299
+ query=self.Query,
300
+ content=final_answer or "No final answer returned.",
301
+ tool_calls=tool_calls,
302
+ steps=steps,
303
+ history= None
304
+ )
305
+
306
+
307
+ # 1. Build the prompt using self.Agent.format(tools=self.tools, user_input=self.Query)
308
+ # 2. Call the LLM with the prompt.
309
+ # 3. Parse the LLM output to get `tool_name` and `args`.
310
+ # 4. Call self.Tool_Runner(tool_name, args)
311
+ # 5. Update history/scratchpad and loop.
312
+
313
+
314
+ # --- END OF PLACEHOLDER ---
315
+
316
+ def __repr__(self) -> str:
317
+ """Provides a developer-friendly representation of the runner's state."""
318
+ return (
319
+ f"<{self.__class__.__name__}("
320
+ f"Agent={self.Agent.__class__.__name__}, "
321
+ f"Tools={len(self.tools)}, "
322
+ f"ToolNames={[tool.name for tool in self.tools]}, "
323
+ f"MaxIterations={self.max_iterations}, "
324
+ f"Model='{self.model}', "
325
+ f"HistoryLen={len(self.history)})>"
326
+ )
327
+
328
+ def __str__(self) -> str:
329
+ """Provides a user-friendly summary of the agent runner."""
330
+ return (
331
+ f"Agent Runner Summary:\n"
332
+ f" Agent Type : {self.Agent.__class__.__name__}\n"
333
+ f" Tools Used : {[tool.name for tool in self.tools]}\n"
334
+ f" Max Iterations : {self.max_iterations}\n"
335
+ f" Model : {self.model}\n"
336
+ f" Query : {self.Query}\n"
337
+ f" History Entries: {len(self.history)}"
338
+ )
@@ -0,0 +1,12 @@
1
+ from .AgentRun import AgentRunner
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ __all__=[
11
+ 'AgentRunner'
12
+ ]