aap-core 0.1.3.dev1__tar.gz → 0.2.0__tar.gz
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.
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/PKG-INFO +5 -1
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/pyproject.toml +9 -1
- aap_core-0.2.0/src/aap_core/__init__.py +23 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/agent.py +1 -2
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/chain.py +36 -39
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/orchestration.py +60 -10
- aap_core-0.2.0/src/aap_core/policy.py +1017 -0
- aap_core-0.2.0/src/aap_core/policy_trainer.py +3114 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/prompt_augmenter.py +378 -92
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/retriever.py +11 -28
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/thought.py +1 -2
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/types.py +50 -0
- aap_core-0.1.3.dev1/src/aap_core/__init__.py +0 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/README.md +0 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/guardrail.py +0 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/py.typed +0 -0
- {aap_core-0.1.3.dev1 → aap_core-0.2.0}/src/aap_core/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: aap-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Agent design pattern core framework
|
|
5
5
|
Keywords: agent,ai,pattern,llm
|
|
6
6
|
Author: Ly Hon Quang
|
|
@@ -15,9 +15,13 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
15
15
|
Classifier: Framework :: Pydantic :: 2
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Requires-Dist: a2a-sdk>=0.3.12
|
|
18
|
+
Requires-Dist: gymnasium>=1.2.3
|
|
19
|
+
Requires-Dist: nltk>=3.9.2
|
|
18
20
|
Requires-Dist: pandas>=2.0.0
|
|
19
21
|
Requires-Dist: rouge-score>=0.1.2
|
|
20
22
|
Requires-Dist: sacrebleu>=2.5.1
|
|
23
|
+
Requires-Dist: scipy>=1.15.3
|
|
24
|
+
Requires-Dist: wandb>=0.27.0
|
|
21
25
|
Requires-Python: >=3.10.12
|
|
22
26
|
Project-URL: Repository, https://github.com/quanghona/agent_design_pattern.git
|
|
23
27
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aap_core"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Agent design pattern core framework"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -11,9 +11,13 @@ authors = [
|
|
|
11
11
|
requires-python = ">=3.10.12"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"a2a-sdk>=0.3.12",
|
|
14
|
+
"gymnasium>=1.2.3",
|
|
15
|
+
"nltk>=3.9.2",
|
|
14
16
|
"pandas>=2.0.0",
|
|
15
17
|
"rouge-score>=0.1.2",
|
|
16
18
|
"sacrebleu>=2.5.1",
|
|
19
|
+
"scipy>=1.15.3",
|
|
20
|
+
"wandb>=0.27.0",
|
|
17
21
|
]
|
|
18
22
|
classifiers = [
|
|
19
23
|
"Programming Language :: Python :: 3.10",
|
|
@@ -30,6 +34,10 @@ classifiers = [
|
|
|
30
34
|
Repository = "https://github.com/quanghona/agent_design_pattern.git"
|
|
31
35
|
|
|
32
36
|
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=9.0.3",
|
|
39
|
+
"ruff>=0.14.0",
|
|
40
|
+
]
|
|
33
41
|
lint = [
|
|
34
42
|
"ruff==0.14.0",
|
|
35
43
|
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from aap_core.policy_trainer import (
|
|
2
|
+
BaseExplorationModule,
|
|
3
|
+
BasePolicyTrainer,
|
|
4
|
+
DPOTrainer,
|
|
5
|
+
EpsilonGreedyExploration,
|
|
6
|
+
GRPOTrainer,
|
|
7
|
+
PPOTrainer,
|
|
8
|
+
RandomExploration,
|
|
9
|
+
ReinforcePPTrainer,
|
|
10
|
+
SimpleReplayBuffer,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BaseExplorationModule",
|
|
15
|
+
"BasePolicyTrainer",
|
|
16
|
+
"DPOTrainer",
|
|
17
|
+
"EpsilonGreedyExploration",
|
|
18
|
+
"GRPOTrainer",
|
|
19
|
+
"PPOTrainer",
|
|
20
|
+
"RandomExploration",
|
|
21
|
+
"ReinforcePPTrainer",
|
|
22
|
+
"SimpleReplayBuffer",
|
|
23
|
+
]
|
|
@@ -5,8 +5,7 @@ from typing import Any, Dict, Literal
|
|
|
5
5
|
from a2a.types import AgentCard
|
|
6
6
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
from .types import AgentMessage
|
|
8
|
+
from .types import AgentMessage, BaseLLMChain
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class BaseAgent(abc.ABC, BaseModel):
|
|
@@ -9,28 +9,7 @@ from .prompt_augmenter import (
|
|
|
9
9
|
BasePromptAugmenter,
|
|
10
10
|
IdentityPromptAugmenter,
|
|
11
11
|
)
|
|
12
|
-
from .types import AgentMessage,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class BaseLLMChain(BaseChain):
|
|
16
|
-
"""
|
|
17
|
-
Base class for LLM chains.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
name: str = Field(
|
|
21
|
-
"chain",
|
|
22
|
-
description="The name of the chain. Should be same at agent who hold this chain for easy to operate.",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
@abc.abstractmethod
|
|
26
|
-
def invoke(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
async def ainvoke(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
30
|
-
return self.invoke(message, **kwargs)
|
|
31
|
-
|
|
32
|
-
def __call__(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
33
|
-
return self.invoke(message, **kwargs)
|
|
12
|
+
from .types import AgentMessage, BaseLLMChain, ChainMessage, ChainResponse, TokenUsage
|
|
34
13
|
|
|
35
14
|
|
|
36
15
|
class BaseCausalMultiTurnsChain(BaseLLMChain, Generic[ChainMessage, ChainResponse]):
|
|
@@ -46,6 +25,10 @@ class BaseCausalMultiTurnsChain(BaseLLMChain, Generic[ChainMessage, ChainRespons
|
|
|
46
25
|
default=False, description="store intermediate steps output"
|
|
47
26
|
)
|
|
48
27
|
|
|
28
|
+
max_turns: int = Field(
|
|
29
|
+
default=50, description="maximum number of turns before stopping"
|
|
30
|
+
)
|
|
31
|
+
|
|
49
32
|
def final_response_as_context(self, key: str) -> None:
|
|
50
33
|
self._last_response_as_context = key.replace("context_", "")
|
|
51
34
|
|
|
@@ -56,7 +39,7 @@ class BaseCausalMultiTurnsChain(BaseLLMChain, Generic[ChainMessage, ChainRespons
|
|
|
56
39
|
@abc.abstractmethod
|
|
57
40
|
def _generate_response(
|
|
58
41
|
self, conversation: List[ChainMessage], **kwargs
|
|
59
|
-
) -> Tuple[List[ChainMessage], ChainResponse, bool]:
|
|
42
|
+
) -> Tuple[List[ChainMessage], ChainResponse, bool, TokenUsage]:
|
|
60
43
|
pass
|
|
61
44
|
|
|
62
45
|
@abc.abstractmethod
|
|
@@ -74,14 +57,38 @@ class BaseCausalMultiTurnsChain(BaseLLMChain, Generic[ChainMessage, ChainRespons
|
|
|
74
57
|
pass
|
|
75
58
|
|
|
76
59
|
def invoke(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
77
|
-
|
|
60
|
+
if message.token_usage is None:
|
|
61
|
+
message.token_usage = {
|
|
62
|
+
"steps": [],
|
|
63
|
+
"total": TokenUsage(input_tokens=0, output_tokens=0, total_tokens=0),
|
|
64
|
+
}
|
|
65
|
+
if "steps" not in message.token_usage:
|
|
66
|
+
message.token_usage["steps"] = []
|
|
67
|
+
if "total" not in message.token_usage:
|
|
68
|
+
message.token_usage["total"] = TokenUsage(
|
|
69
|
+
input_tokens=0, output_tokens=0, total_tokens=0
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def add_token(usage: TokenUsage):
|
|
73
|
+
message.token_usage["steps"].append(usage) # type: ignore
|
|
74
|
+
message.token_usage["total"]["input_tokens"] += usage["input_tokens"] # type: ignore
|
|
75
|
+
message.token_usage["total"]["output_tokens"] += usage["output_tokens"] # type: ignore
|
|
76
|
+
message.token_usage["total"]["total_tokens"] += usage["total_tokens"] # type: ignore
|
|
77
|
+
|
|
78
|
+
# Template method — ReAct loop: Thought → Act → Thought → ... → Final
|
|
78
79
|
conversation = self._prepare_conversation(message)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
turn = 0
|
|
81
|
+
while turn < self.max_turns:
|
|
82
|
+
conversation, response, has_tool, usage = self._generate_response(
|
|
83
|
+
conversation, **kwargs
|
|
84
|
+
)
|
|
85
|
+
add_token(usage)
|
|
86
|
+
|
|
87
|
+
if not has_tool:
|
|
88
|
+
break
|
|
89
|
+
|
|
83
90
|
conversation = self._process_tools(conversation, response)
|
|
84
|
-
|
|
91
|
+
turn += 1
|
|
85
92
|
|
|
86
93
|
message = self._append_responses(message, conversation)
|
|
87
94
|
|
|
@@ -132,13 +139,3 @@ class TypicalLLMChain(BaseLLMChain):
|
|
|
132
139
|
message = self.generate(message, **kwargs)
|
|
133
140
|
message = self.output_guardrail(message)
|
|
134
141
|
return message
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
class MetaPromptAugmenter(BasePromptAugmenter):
|
|
138
|
-
"""A prompt augmenter that use an LLM chain to rewrite the prompt."""
|
|
139
|
-
|
|
140
|
-
chain: BaseLLMChain = Field(..., description="LLM chain that rewrite the prompt")
|
|
141
|
-
|
|
142
|
-
def augment(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
143
|
-
message = self.chain.invoke(message, **kwargs)
|
|
144
|
-
return message
|
|
@@ -10,8 +10,8 @@ from sacrebleu import sentence_bleu
|
|
|
10
10
|
|
|
11
11
|
from aap_core.types import AgentResponse
|
|
12
12
|
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
13
|
+
from .types import AgentMessage, BaseLLMChain
|
|
14
|
+
from .agent import BaseAgent
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ReflectionAgent(BaseAgent):
|
|
@@ -35,6 +35,10 @@ class ReflectionAgent(BaseAgent):
|
|
|
35
35
|
...,
|
|
36
36
|
description="LLM chain that perform reflection on the result of the main task",
|
|
37
37
|
)
|
|
38
|
+
task_response_key: str = Field(
|
|
39
|
+
default="context_response",
|
|
40
|
+
description="The key to use for the task response in the message context.",
|
|
41
|
+
)
|
|
38
42
|
|
|
39
43
|
@field_validator("task_response_key")
|
|
40
44
|
@classmethod
|
|
@@ -245,7 +249,7 @@ class ParallelAgent(BaseAgent):
|
|
|
245
249
|
origin=self.card.name,
|
|
246
250
|
responses=[message.responses[-1] for message in responses],
|
|
247
251
|
execution_result="success",
|
|
248
|
-
)
|
|
252
|
+
)
|
|
249
253
|
self.state = "idle"
|
|
250
254
|
return result_message
|
|
251
255
|
|
|
@@ -309,7 +313,7 @@ class CoordinatorAgent(BaseAgent):
|
|
|
309
313
|
description="""The prompt that will be used by the summary chain""",
|
|
310
314
|
)
|
|
311
315
|
summary_steps_key: str = Field(
|
|
312
|
-
"context_results",
|
|
316
|
+
default="context_results",
|
|
313
317
|
description="""The key of which to store the context (e.g. intermediate result) during generation.
|
|
314
318
|
Note that the key need to start with 'context_'""",
|
|
315
319
|
)
|
|
@@ -330,11 +334,57 @@ class CoordinatorAgent(BaseAgent):
|
|
|
330
334
|
return message
|
|
331
335
|
|
|
332
336
|
steps = self.parse_plan(message, self.workers)
|
|
337
|
+
|
|
338
|
+
# Validate parsed plan
|
|
339
|
+
if not steps:
|
|
340
|
+
self.state = "idle"
|
|
341
|
+
message.execution_result = "error"
|
|
342
|
+
message.error_message = (
|
|
343
|
+
"Plan parsing failed: no steps returned from parse_plan"
|
|
344
|
+
)
|
|
345
|
+
return message
|
|
346
|
+
|
|
347
|
+
if not isinstance(steps, Sequence):
|
|
348
|
+
self.state = "idle"
|
|
349
|
+
message.execution_result = "error"
|
|
350
|
+
message.error_message = (
|
|
351
|
+
f"Plan parsing failed: expected Sequence, got {type(steps)}"
|
|
352
|
+
)
|
|
353
|
+
return message
|
|
354
|
+
|
|
355
|
+
for i, step in enumerate(steps):
|
|
356
|
+
if not isinstance(step, tuple) or len(step) != 3:
|
|
357
|
+
self.state = "idle"
|
|
358
|
+
message.execution_result = "error"
|
|
359
|
+
message.error_message = f"Plan parsing failed: step {i} is not a valid tuple of (message, agent, dependencies)"
|
|
360
|
+
return message
|
|
361
|
+
step_msg, step_agent, dependencies = step
|
|
362
|
+
if not isinstance(step_msg, AgentMessage):
|
|
363
|
+
self.state = "idle"
|
|
364
|
+
message.execution_result = "error"
|
|
365
|
+
message.error_message = (
|
|
366
|
+
f"Plan parsing failed: step {i} message is not an AgentMessage"
|
|
367
|
+
)
|
|
368
|
+
return message
|
|
369
|
+
if not isinstance(step_agent, BaseAgent):
|
|
370
|
+
self.state = "idle"
|
|
371
|
+
message.execution_result = "error"
|
|
372
|
+
message.error_message = (
|
|
373
|
+
f"Plan parsing failed: step {i} agent is not a BaseAgent"
|
|
374
|
+
)
|
|
375
|
+
return message
|
|
376
|
+
if not isinstance(dependencies, list):
|
|
377
|
+
self.state = "idle"
|
|
378
|
+
message.execution_result = "error"
|
|
379
|
+
message.error_message = (
|
|
380
|
+
f"Plan parsing failed: step {i} dependencies is not a list"
|
|
381
|
+
)
|
|
382
|
+
return message
|
|
383
|
+
|
|
333
384
|
sub_task_result = []
|
|
334
385
|
# TODO: check step dependency and parallelize steps to speed up
|
|
335
386
|
summary_steps_key = self.summary_steps_key.replace("context_", "")
|
|
336
387
|
for i, (step_msg, step_agent, dependencies) in enumerate(steps):
|
|
337
|
-
# TODO: deal with the dependencies
|
|
338
388
|
step_msg.context = {summary_steps_key: sub_task_result}
|
|
339
389
|
self.state = f"step {i}: worker {step_agent.card.name} running"
|
|
340
390
|
sub_task_result.append(step_agent.execute(step_msg, **kwargs))
|
|
@@ -347,7 +397,7 @@ class CoordinatorAgent(BaseAgent):
|
|
|
347
397
|
query=self.summary_prompt,
|
|
348
398
|
context={summary_steps_key: sub_task_result},
|
|
349
399
|
responses=[],
|
|
350
|
-
)
|
|
400
|
+
)
|
|
351
401
|
summary_message = self.planner_agent.execute(summary_message, **kwargs)
|
|
352
402
|
else:
|
|
353
403
|
return message
|
|
@@ -358,7 +408,7 @@ class CoordinatorAgent(BaseAgent):
|
|
|
358
408
|
query=message.query,
|
|
359
409
|
context={summary_steps_key: sub_task_result},
|
|
360
410
|
responses=[],
|
|
361
|
-
)
|
|
411
|
+
)
|
|
362
412
|
summary_message = self.summary_chain.invoke(summary_message, **kwargs)
|
|
363
413
|
|
|
364
414
|
self.state = "idle"
|
|
@@ -521,7 +571,7 @@ class VotingAgent(BaseAgent):
|
|
|
521
571
|
scorer = rouge_scorer.RougeScorer([self.scorer], use_stemmer=True)
|
|
522
572
|
|
|
523
573
|
def rouge_score(hyp: str, ref: str) -> float:
|
|
524
|
-
return scorer.score(ref, hyp)[
|
|
574
|
+
return list(scorer.score(ref, hyp).values())[0].fmeasure
|
|
525
575
|
|
|
526
576
|
score_func = rouge_score
|
|
527
577
|
else:
|
|
@@ -530,7 +580,7 @@ class VotingAgent(BaseAgent):
|
|
|
530
580
|
scores = {agent.card.name: 0.0 for agent in self.agents}
|
|
531
581
|
message_map = {name: msg for name, msg in message.responses}
|
|
532
582
|
for agent_name, msg in message.responses:
|
|
533
|
-
total_score = 0
|
|
583
|
+
total_score = 0.0
|
|
534
584
|
for other_agent in self.agents:
|
|
535
585
|
if agent_name == other_agent.card.name:
|
|
536
586
|
continue
|
|
@@ -560,7 +610,7 @@ class VotingAgent(BaseAgent):
|
|
|
560
610
|
try:
|
|
561
611
|
voting_message = AgentMessage(
|
|
562
612
|
query=self.voting_prompt, responses=[]
|
|
563
|
-
)
|
|
613
|
+
)
|
|
564
614
|
voting_message = agent.execute(voting_message, **kwargs)
|
|
565
615
|
total_score += self.scorer(voting_message.responses[-1][1])
|
|
566
616
|
except Exception:
|