augagent 0.1.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.
- augagent/__init__.py +90 -0
- augagent/agent.py +420 -0
- augagent/models.py +291 -0
- augagent/task.py +156 -0
- augagent/team.py +163 -0
- augagent/telemetry.py +50 -0
- augagent/tools.py +262 -0
- augagent-0.1.0.dist-info/METADATA +139 -0
- augagent-0.1.0.dist-info/RECORD +11 -0
- augagent-0.1.0.dist-info/WHEEL +5 -0
- augagent-0.1.0.dist-info/top_level.txt +1 -0
augagent/__init__.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""AugAgent — A multi-agent framework with intuitive DX, powered by Pydantic.
|
|
2
|
+
|
|
3
|
+
Quick Start::
|
|
4
|
+
|
|
5
|
+
from augagent import AugAgent, AugTask, AugTeam, aug_tool, LLMConfig
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
@aug_tool
|
|
9
|
+
def greet(name: str = Field(description="Person to greet")) -> str:
|
|
10
|
+
\"\"\"Greet someone by name.\"\"\"
|
|
11
|
+
return f"Hello, {name}!"
|
|
12
|
+
|
|
13
|
+
assistant = AugAgent(
|
|
14
|
+
name="Assistant",
|
|
15
|
+
role="Friendly Helper",
|
|
16
|
+
goal="Help users with their requests",
|
|
17
|
+
llm_config=LLMConfig(model="gpt-4o"),
|
|
18
|
+
tools=[greet],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
task = AugTask(
|
|
22
|
+
description="Greet the user warmly",
|
|
23
|
+
expected_output="A friendly greeting message",
|
|
24
|
+
agent=assistant,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
team = AugTeam(agents=[assistant], tasks=[task], verbose=True)
|
|
28
|
+
result = team.kickoff()
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from augagent.agent import Agent, AugAgent
|
|
32
|
+
from augagent.models import (
|
|
33
|
+
AgentConfig,
|
|
34
|
+
ChatCompletion,
|
|
35
|
+
ChatMessage,
|
|
36
|
+
ChatToolCall,
|
|
37
|
+
FunctionCall,
|
|
38
|
+
LLMConfig,
|
|
39
|
+
Message,
|
|
40
|
+
Role,
|
|
41
|
+
TaskResult,
|
|
42
|
+
TaskStatus,
|
|
43
|
+
TokenUsage,
|
|
44
|
+
ToolCall,
|
|
45
|
+
ToolResponse,
|
|
46
|
+
)
|
|
47
|
+
from augagent.task import AugTask, Task
|
|
48
|
+
from augagent.team import AugTeam, Process, Team
|
|
49
|
+
from augagent.telemetry import AgentLogger, get_logger
|
|
50
|
+
from augagent.tools import AugTool, Tool, aug_tool, tool
|
|
51
|
+
|
|
52
|
+
__version__ = "0.1.0"
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Core orchestration
|
|
56
|
+
"AugAgent",
|
|
57
|
+
"AugTask",
|
|
58
|
+
"AugTeam",
|
|
59
|
+
"Process",
|
|
60
|
+
# Tools
|
|
61
|
+
"AugTool",
|
|
62
|
+
"aug_tool",
|
|
63
|
+
# Backward compatibility aliases
|
|
64
|
+
"Agent",
|
|
65
|
+
"Task",
|
|
66
|
+
"Team",
|
|
67
|
+
"Tool",
|
|
68
|
+
"tool",
|
|
69
|
+
# Models — LLM configuration
|
|
70
|
+
"LLMConfig",
|
|
71
|
+
# Models — chat completion response
|
|
72
|
+
"ChatCompletion",
|
|
73
|
+
"ChatMessage",
|
|
74
|
+
"ChatToolCall",
|
|
75
|
+
"FunctionCall",
|
|
76
|
+
"TokenUsage",
|
|
77
|
+
# Models — internal
|
|
78
|
+
"AgentConfig",
|
|
79
|
+
"Message",
|
|
80
|
+
"Role",
|
|
81
|
+
"TaskResult",
|
|
82
|
+
"TaskStatus",
|
|
83
|
+
"ToolCall",
|
|
84
|
+
"ToolResponse",
|
|
85
|
+
# Telemetry
|
|
86
|
+
"AgentLogger",
|
|
87
|
+
"get_logger",
|
|
88
|
+
# Metadata
|
|
89
|
+
"__version__",
|
|
90
|
+
]
|
augagent/agent.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""AugAgent — the core autonomous entity in augagent.
|
|
2
|
+
|
|
3
|
+
:class:`AugAgent` encapsulates a persona (role, goal, backstory), an
|
|
4
|
+
:class:`~augagent.models.LLMConfig`, and a set of
|
|
5
|
+
:class:`~augagent.tools.AugTool` instances. It implements a **ReAct**
|
|
6
|
+
(Reason + Act) loop that interleaves LLM reasoning with tool execution,
|
|
7
|
+
communicating with the LLM provider over HTTP via :mod:`httpx`.
|
|
8
|
+
|
|
9
|
+
Example::
|
|
10
|
+
|
|
11
|
+
from augagent import AugAgent, aug_tool, LLMConfig
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
@aug_tool
|
|
15
|
+
def lookup_db(query: str = Field(description="SQL query")) -> str:
|
|
16
|
+
\"\"\"Run a database lookup.\"\"\"
|
|
17
|
+
return "42"
|
|
18
|
+
|
|
19
|
+
agent = AugAgent(
|
|
20
|
+
name="Analyst",
|
|
21
|
+
role="Data Analyst",
|
|
22
|
+
goal="Answer data questions accurately",
|
|
23
|
+
backstory="You have 10 years of experience with SQL and analytics.",
|
|
24
|
+
llm_config=LLMConfig(model="gpt-4o", temperature=0.2),
|
|
25
|
+
tools=[lookup_db],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
result = await agent.execute("What is the total revenue for Q3?")
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import json
|
|
35
|
+
import time
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
import httpx
|
|
39
|
+
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
|
40
|
+
|
|
41
|
+
from augagent.models import (
|
|
42
|
+
AgentConfig,
|
|
43
|
+
ChatCompletion,
|
|
44
|
+
ChatMessage,
|
|
45
|
+
ChatToolCall,
|
|
46
|
+
LLMConfig,
|
|
47
|
+
TaskResult,
|
|
48
|
+
TaskStatus,
|
|
49
|
+
)
|
|
50
|
+
from augagent.telemetry import get_logger
|
|
51
|
+
from augagent.tools import AugTool
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AugAgent(BaseModel):
|
|
55
|
+
"""A single autonomous agent with a built-in ReAct loop.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
name:
|
|
60
|
+
Unique, human-readable identifier for this agent.
|
|
61
|
+
role:
|
|
62
|
+
The agent's persona (e.g. ``"Senior Python Developer"``).
|
|
63
|
+
goal:
|
|
64
|
+
One-sentence objective guiding behaviour.
|
|
65
|
+
backstory:
|
|
66
|
+
Rich context that shapes reasoning style and domain knowledge.
|
|
67
|
+
llm_config:
|
|
68
|
+
Connection and generation settings for the LLM provider.
|
|
69
|
+
tools:
|
|
70
|
+
:class:`~augagent.tools.AugTool` instances the agent may invoke.
|
|
71
|
+
max_iterations:
|
|
72
|
+
Safety cap on ReAct loop iterations (Reason → Act → Observe).
|
|
73
|
+
verbose:
|
|
74
|
+
Enable detailed step-by-step logging.
|
|
75
|
+
allow_delegation:
|
|
76
|
+
Whether this agent may delegate sub-tasks to teammates.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
80
|
+
|
|
81
|
+
# ── identity ──────────────────────────────────────────────────────────
|
|
82
|
+
name: str = Field(..., min_length=1, max_length=128)
|
|
83
|
+
role: str
|
|
84
|
+
goal: str
|
|
85
|
+
backstory: str = ""
|
|
86
|
+
|
|
87
|
+
# ── LLM configuration ────────────────────────────────────────────────
|
|
88
|
+
llm_config: LLMConfig = Field(default_factory=LLMConfig)
|
|
89
|
+
|
|
90
|
+
# ── capabilities ─────────────────────────────────────────────────────
|
|
91
|
+
tools: list[AugTool] = Field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
# ── behaviour ────────────────────────────────────────────────────────
|
|
94
|
+
max_iterations: int = Field(default=25, ge=1)
|
|
95
|
+
verbose: bool = False
|
|
96
|
+
allow_delegation: bool = False
|
|
97
|
+
|
|
98
|
+
# ── internal state (private, excluded from serialisation) ─────────────
|
|
99
|
+
_message_history: list[dict[str, Any]] = PrivateAttr(default_factory=list)
|
|
100
|
+
|
|
101
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
102
|
+
# PUBLIC API
|
|
103
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
104
|
+
|
|
105
|
+
async def execute(self, prompt: str) -> TaskResult:
|
|
106
|
+
"""Run the agent's ReAct loop on the given prompt."""
|
|
107
|
+
logger = get_logger()
|
|
108
|
+
start = time.time()
|
|
109
|
+
|
|
110
|
+
if self.verbose:
|
|
111
|
+
logger.log_info(f"[{self.name}] starting execution with {self.llm_config.model}")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
output, usage, iterations = await self._react_loop(prompt, logger)
|
|
115
|
+
elapsed = time.time() - start
|
|
116
|
+
|
|
117
|
+
result = TaskResult(
|
|
118
|
+
task_id="",
|
|
119
|
+
agent_name=self.name,
|
|
120
|
+
status=TaskStatus.COMPLETED,
|
|
121
|
+
output=output,
|
|
122
|
+
token_usage=usage,
|
|
123
|
+
elapsed_seconds=round(elapsed, 3),
|
|
124
|
+
iterations=iterations,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if self.verbose:
|
|
128
|
+
logger.log_info(
|
|
129
|
+
f"[{self.name}] completed in {elapsed:.2f}s "
|
|
130
|
+
f"({iterations} iterations, {usage.get('total_tokens', 0)} tokens)"
|
|
131
|
+
)
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
elapsed = time.time() - start
|
|
136
|
+
logger.log_error(f"[{self.name}] execution failed: {exc}")
|
|
137
|
+
|
|
138
|
+
return TaskResult(
|
|
139
|
+
task_id="",
|
|
140
|
+
agent_name=self.name,
|
|
141
|
+
status=TaskStatus.FAILED,
|
|
142
|
+
output=f"Agent execution failed: {exc}",
|
|
143
|
+
elapsed_seconds=round(elapsed, 3),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
147
|
+
# REACT LOOP (Reason → Act → Observe)
|
|
148
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
149
|
+
|
|
150
|
+
async def _react_loop(
|
|
151
|
+
self,
|
|
152
|
+
prompt: str,
|
|
153
|
+
logger: Any,
|
|
154
|
+
) -> tuple[str, dict[str, int], int]:
|
|
155
|
+
"""Core ReAct loop."""
|
|
156
|
+
messages: list[dict[str, Any]] = [
|
|
157
|
+
{"role": "system", "content": self._build_system_prompt()},
|
|
158
|
+
{"role": "user", "content": prompt},
|
|
159
|
+
]
|
|
160
|
+
self._message_history = list(messages)
|
|
161
|
+
|
|
162
|
+
total_usage: dict[str, int] = {
|
|
163
|
+
"prompt_tokens": 0,
|
|
164
|
+
"completion_tokens": 0,
|
|
165
|
+
"total_tokens": 0,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for iteration in range(1, self.max_iterations + 1):
|
|
169
|
+
if self.verbose:
|
|
170
|
+
logger.log_info(f"[{self.name}] --- iteration {iteration} / {self.max_iterations} ---")
|
|
171
|
+
|
|
172
|
+
# ── REASON: call the LLM ─────────────────────────────────────
|
|
173
|
+
completion = await self._call_llm(messages, logger)
|
|
174
|
+
|
|
175
|
+
if completion.usage:
|
|
176
|
+
total_usage["prompt_tokens"] += completion.usage.prompt_tokens
|
|
177
|
+
total_usage["completion_tokens"] += completion.usage.completion_tokens
|
|
178
|
+
total_usage["total_tokens"] += completion.usage.total_tokens
|
|
179
|
+
|
|
180
|
+
if not completion.choices:
|
|
181
|
+
raise RuntimeError("LLM returned an empty choices array.")
|
|
182
|
+
|
|
183
|
+
assistant_msg = completion.choices[0].message
|
|
184
|
+
messages.append(self._chat_message_to_dict(assistant_msg))
|
|
185
|
+
|
|
186
|
+
# ── Final answer (no tool calls) ─────────────────────────────
|
|
187
|
+
if not assistant_msg.tool_calls:
|
|
188
|
+
final_output = assistant_msg.content or ""
|
|
189
|
+
if self.verbose:
|
|
190
|
+
logger.log_info(f"[{self.name}] final answer ({len(final_output)} chars)")
|
|
191
|
+
self._message_history = list(messages)
|
|
192
|
+
return final_output, total_usage, iteration
|
|
193
|
+
|
|
194
|
+
# ── ACT: execute each tool call ──────────────────────────────
|
|
195
|
+
for tc in assistant_msg.tool_calls:
|
|
196
|
+
logger.log_tool_execution(
|
|
197
|
+
agent_name=self.name,
|
|
198
|
+
tool_name=tc.function.name,
|
|
199
|
+
args=tc.function.arguments[:200]
|
|
200
|
+
)
|
|
201
|
+
tool_output = await self._execute_tool_call(tc, logger)
|
|
202
|
+
messages.append({
|
|
203
|
+
"role": "tool",
|
|
204
|
+
"tool_call_id": tc.id,
|
|
205
|
+
"content": tool_output,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
self._message_history = list(messages)
|
|
209
|
+
|
|
210
|
+
# ── Max iterations exhausted ─────────────────────────────────────
|
|
211
|
+
logger.log_error(f"[{self.name}] reached max iterations ({self.max_iterations}).")
|
|
212
|
+
|
|
213
|
+
last_content = ""
|
|
214
|
+
for msg in reversed(messages):
|
|
215
|
+
if msg.get("role") == "assistant" and msg.get("content"):
|
|
216
|
+
last_content = msg["content"]
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
last_content or f"[Max iterations ({self.max_iterations}) reached without a final answer]",
|
|
221
|
+
total_usage,
|
|
222
|
+
self.max_iterations,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
226
|
+
# LLM COMMUNICATION (httpx)
|
|
227
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
228
|
+
|
|
229
|
+
async def _call_llm(
|
|
230
|
+
self,
|
|
231
|
+
messages: list[dict[str, Any]],
|
|
232
|
+
logger: Any,
|
|
233
|
+
) -> ChatCompletion:
|
|
234
|
+
"""POST to ``/chat/completions`` with retry + exponential backoff."""
|
|
235
|
+
cfg = self.llm_config
|
|
236
|
+
url = f"{cfg.base_url.rstrip('/')}/chat/completions"
|
|
237
|
+
api_key = cfg.resolve_api_key()
|
|
238
|
+
|
|
239
|
+
headers: dict[str, str] = {
|
|
240
|
+
"Authorization": f"Bearer {api_key}",
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
**cfg.extra_headers,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
payload: dict[str, Any] = {
|
|
246
|
+
"model": cfg.model,
|
|
247
|
+
"messages": messages,
|
|
248
|
+
"temperature": cfg.temperature,
|
|
249
|
+
"max_tokens": cfg.max_tokens,
|
|
250
|
+
"top_p": cfg.top_p,
|
|
251
|
+
}
|
|
252
|
+
if cfg.frequency_penalty != 0.0:
|
|
253
|
+
payload["frequency_penalty"] = cfg.frequency_penalty
|
|
254
|
+
if cfg.presence_penalty != 0.0:
|
|
255
|
+
payload["presence_penalty"] = cfg.presence_penalty
|
|
256
|
+
if cfg.stop:
|
|
257
|
+
payload["stop"] = cfg.stop
|
|
258
|
+
|
|
259
|
+
if self.tools:
|
|
260
|
+
payload["tools"] = [t.to_openai_schema() for t in self.tools]
|
|
261
|
+
|
|
262
|
+
last_exc: Exception | None = None
|
|
263
|
+
|
|
264
|
+
for attempt in range(cfg.max_retries + 1):
|
|
265
|
+
try:
|
|
266
|
+
async with httpx.AsyncClient(
|
|
267
|
+
timeout=httpx.Timeout(cfg.timeout),
|
|
268
|
+
) as client:
|
|
269
|
+
resp = await client.post(url, headers=headers, json=payload)
|
|
270
|
+
|
|
271
|
+
if resp.status_code == 429:
|
|
272
|
+
retry_after = float(resp.headers.get("retry-after", str(2 ** attempt)))
|
|
273
|
+
logger.log_error(f"Rate-limited (429). Retrying in {retry_after:.1f}s")
|
|
274
|
+
await asyncio.sleep(retry_after)
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
resp.raise_for_status()
|
|
278
|
+
return ChatCompletion.model_validate(resp.json())
|
|
279
|
+
|
|
280
|
+
except httpx.TimeoutException as exc:
|
|
281
|
+
last_exc = exc
|
|
282
|
+
logger.log_error(f"Timeout [attempt {attempt + 1}/{cfg.max_retries + 1}]: {exc}")
|
|
283
|
+
except httpx.HTTPStatusError as exc:
|
|
284
|
+
if exc.response.status_code in {500, 502, 503}:
|
|
285
|
+
last_exc = exc
|
|
286
|
+
logger.log_error(f"Server error {exc.response.status_code} [attempt {attempt + 1}/{cfg.max_retries + 1}]")
|
|
287
|
+
else:
|
|
288
|
+
raise
|
|
289
|
+
|
|
290
|
+
if attempt < cfg.max_retries:
|
|
291
|
+
backoff = min(2 ** attempt, 30)
|
|
292
|
+
await asyncio.sleep(backoff)
|
|
293
|
+
|
|
294
|
+
raise RuntimeError(f"LLM request failed after {cfg.max_retries + 1} attempts: {last_exc}")
|
|
295
|
+
|
|
296
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
297
|
+
# TOOL EXECUTION
|
|
298
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
299
|
+
|
|
300
|
+
async def _execute_tool_call(self, tool_call: ChatToolCall, logger: Any) -> str:
|
|
301
|
+
"""Dispatch a single tool call to the matching :class:`AugTool`."""
|
|
302
|
+
tool_map = {t.name: t for t in self.tools}
|
|
303
|
+
tool_impl = tool_map.get(tool_call.function.name)
|
|
304
|
+
|
|
305
|
+
if tool_impl is None:
|
|
306
|
+
error = f"Tool '{tool_call.function.name}' not found."
|
|
307
|
+
logger.log_error(error)
|
|
308
|
+
return json.dumps({"error": error})
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
arguments = json.loads(tool_call.function.arguments)
|
|
312
|
+
except json.JSONDecodeError as exc:
|
|
313
|
+
error = f"Failed to parse tool arguments: {exc}"
|
|
314
|
+
logger.log_error(error)
|
|
315
|
+
return json.dumps({"error": error})
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
result = await tool_impl.run(**arguments)
|
|
319
|
+
return result
|
|
320
|
+
except Exception as exc:
|
|
321
|
+
error = f"Tool error ({type(exc).__name__}): {exc}"
|
|
322
|
+
logger.log_error(f"[{tool_call.function.name}] {error}")
|
|
323
|
+
return json.dumps({"error": error})
|
|
324
|
+
|
|
325
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
326
|
+
# PROMPT CONSTRUCTION
|
|
327
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
328
|
+
|
|
329
|
+
def _build_system_prompt(self) -> str:
|
|
330
|
+
"""Assemble the system prompt from the agent's persona fields."""
|
|
331
|
+
parts: list[str] = [
|
|
332
|
+
f"You are {self.name}, a {self.role}.",
|
|
333
|
+
f"\nYour goal: {self.goal}",
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
if self.backstory:
|
|
337
|
+
parts.append(f"\nBackstory: {self.backstory}")
|
|
338
|
+
|
|
339
|
+
if self.tools:
|
|
340
|
+
tool_lines = "\n".join(f" • {t.name} — {t.description}" for t in self.tools)
|
|
341
|
+
parts.append(f"\nYou have access to the following tools:\n{tool_lines}")
|
|
342
|
+
parts.append(
|
|
343
|
+
"\nUse tools when they would help accomplish your goal. "
|
|
344
|
+
"When you have gathered enough information to provide a "
|
|
345
|
+
"final answer, respond directly without making tool calls."
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return "\n".join(parts)
|
|
349
|
+
|
|
350
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
351
|
+
# SERIALISATION HELPERS
|
|
352
|
+
# ══════════════════════════════════════════════════════════════════════
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def _chat_message_to_dict(msg: ChatMessage) -> dict[str, Any]:
|
|
356
|
+
"""Convert a parsed :class:`ChatMessage` back to an API-ready dict."""
|
|
357
|
+
d: dict[str, Any] = {"role": msg.role}
|
|
358
|
+
if msg.content is not None:
|
|
359
|
+
d["content"] = msg.content
|
|
360
|
+
if msg.tool_calls:
|
|
361
|
+
d["tool_calls"] = [
|
|
362
|
+
{
|
|
363
|
+
"id": tc.id,
|
|
364
|
+
"type": tc.type,
|
|
365
|
+
"function": {
|
|
366
|
+
"name": tc.function.name,
|
|
367
|
+
"arguments": tc.function.arguments,
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
for tc in msg.tool_calls
|
|
371
|
+
]
|
|
372
|
+
return d
|
|
373
|
+
|
|
374
|
+
def to_config(self) -> AgentConfig:
|
|
375
|
+
"""Export the agent's declarative settings as an :class:`AgentConfig`."""
|
|
376
|
+
return AgentConfig(
|
|
377
|
+
name=self.name,
|
|
378
|
+
role=self.role,
|
|
379
|
+
goal=self.goal,
|
|
380
|
+
backstory=self.backstory,
|
|
381
|
+
llm_config=self.llm_config,
|
|
382
|
+
max_iterations=self.max_iterations,
|
|
383
|
+
allow_delegation=self.allow_delegation,
|
|
384
|
+
verbose=self.verbose,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
@classmethod
|
|
388
|
+
def from_config(
|
|
389
|
+
cls,
|
|
390
|
+
config: AgentConfig,
|
|
391
|
+
tools: list[AugTool] | None = None,
|
|
392
|
+
) -> AugAgent:
|
|
393
|
+
"""Construct an ``AugAgent`` from a serialised :class:`AgentConfig`."""
|
|
394
|
+
return cls(
|
|
395
|
+
name=config.name,
|
|
396
|
+
role=config.role,
|
|
397
|
+
goal=config.goal,
|
|
398
|
+
backstory=config.backstory,
|
|
399
|
+
llm_config=config.llm_config,
|
|
400
|
+
max_iterations=config.max_iterations,
|
|
401
|
+
allow_delegation=config.allow_delegation,
|
|
402
|
+
verbose=config.verbose,
|
|
403
|
+
tools=tools or [],
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def message_history(self) -> list[dict[str, Any]]:
|
|
408
|
+
"""Read-only view of the conversation history from the last execution."""
|
|
409
|
+
return list(self._message_history)
|
|
410
|
+
|
|
411
|
+
def __repr__(self) -> str:
|
|
412
|
+
return (
|
|
413
|
+
f"AugAgent(name={self.name!r}, role={self.role!r}, "
|
|
414
|
+
f"model={self.llm_config.model!r}, "
|
|
415
|
+
f"tools={[t.name for t in self.tools]})"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Backward-compatible alias
|
|
420
|
+
Agent = AugAgent
|