aap-core 0.1.2.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.2.dev1 → aap_core-0.2.0}/PKG-INFO +7 -1
- aap_core-0.2.0/README.md +1 -0
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/pyproject.toml +10 -1
- aap_core-0.2.0/src/aap_core/__init__.py +23 -0
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/agent.py +1 -2
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/chain.py +42 -49
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/orchestration.py +60 -9
- 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.2.0/src/aap_core/prompt_augmenter.py +1221 -0
- aap_core-0.2.0/src/aap_core/retriever.py +213 -0
- aap_core-0.2.0/src/aap_core/thought.py +41 -0
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/types.py +50 -0
- aap_core-0.1.2.dev1/README.md +0 -0
- aap_core-0.1.2.dev1/src/aap_core/__init__.py +0 -0
- aap_core-0.1.2.dev1/src/aap_core/prompt_enhancer.py +0 -209
- aap_core-0.1.2.dev1/src/aap_core/thought.py +0 -14
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/guardrail.py +0 -0
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/py.typed +0 -0
- {aap_core-0.1.2.dev1 → aap_core-0.2.0}/src/aap_core/utils.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
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
|
+
Keywords: agent,ai,pattern,llm
|
|
5
6
|
Author: Ly Hon Quang
|
|
6
7
|
Author-email: Ly Hon Quang <lyhonquang@gmail.com>
|
|
7
8
|
License: MIT
|
|
@@ -14,10 +15,15 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
14
15
|
Classifier: Framework :: Pydantic :: 2
|
|
15
16
|
Classifier: Operating System :: OS Independent
|
|
16
17
|
Requires-Dist: a2a-sdk>=0.3.12
|
|
18
|
+
Requires-Dist: gymnasium>=1.2.3
|
|
19
|
+
Requires-Dist: nltk>=3.9.2
|
|
17
20
|
Requires-Dist: pandas>=2.0.0
|
|
18
21
|
Requires-Dist: rouge-score>=0.1.2
|
|
19
22
|
Requires-Dist: sacrebleu>=2.5.1
|
|
23
|
+
Requires-Dist: scipy>=1.15.3
|
|
24
|
+
Requires-Dist: wandb>=0.27.0
|
|
20
25
|
Requires-Python: >=3.10.12
|
|
21
26
|
Project-URL: Repository, https://github.com/quanghona/agent_design_pattern.git
|
|
22
27
|
Description-Content-Type: text/markdown
|
|
23
28
|
|
|
29
|
+
# AI Agent Pattern (AAP)
|
aap_core-0.2.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# AI Agent Pattern (AAP)
|
|
@@ -1,18 +1,23 @@
|
|
|
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" }
|
|
7
|
+
keywords = ["agent", "ai", "pattern", "llm"]
|
|
7
8
|
authors = [
|
|
8
9
|
{ name = "Ly Hon Quang", email = "lyhonquang@gmail.com" }
|
|
9
10
|
]
|
|
10
11
|
requires-python = ">=3.10.12"
|
|
11
12
|
dependencies = [
|
|
12
13
|
"a2a-sdk>=0.3.12",
|
|
14
|
+
"gymnasium>=1.2.3",
|
|
15
|
+
"nltk>=3.9.2",
|
|
13
16
|
"pandas>=2.0.0",
|
|
14
17
|
"rouge-score>=0.1.2",
|
|
15
18
|
"sacrebleu>=2.5.1",
|
|
19
|
+
"scipy>=1.15.3",
|
|
20
|
+
"wandb>=0.27.0",
|
|
16
21
|
]
|
|
17
22
|
classifiers = [
|
|
18
23
|
"Programming Language :: Python :: 3.10",
|
|
@@ -29,6 +34,10 @@ classifiers = [
|
|
|
29
34
|
Repository = "https://github.com/quanghona/agent_design_pattern.git"
|
|
30
35
|
|
|
31
36
|
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=9.0.3",
|
|
39
|
+
"ruff>=0.14.0",
|
|
40
|
+
]
|
|
32
41
|
lint = [
|
|
33
42
|
"ruff==0.14.0",
|
|
34
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):
|
|
@@ -5,32 +5,11 @@ from typing import Callable, Generic, List, Tuple
|
|
|
5
5
|
from pydantic import Field, PrivateAttr
|
|
6
6
|
|
|
7
7
|
from .guardrail import BaseGuardRail, PassGuardRail
|
|
8
|
-
from .
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
from .prompt_augmenter import (
|
|
9
|
+
BasePromptAugmenter,
|
|
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
|
|
|
@@ -108,8 +115,8 @@ class TypicalLLMChain(BaseLLMChain):
|
|
|
108
115
|
default=PassGuardRail(),
|
|
109
116
|
description="validate the input message",
|
|
110
117
|
)
|
|
111
|
-
|
|
112
|
-
default=
|
|
118
|
+
prompt_augmenter: BasePromptAugmenter = Field(
|
|
119
|
+
default=IdentityPromptAugmenter(),
|
|
113
120
|
description="add more context to the prompt or rewrite / refine the prompt",
|
|
114
121
|
)
|
|
115
122
|
tools: Sequence[Callable] = Field(
|
|
@@ -128,21 +135,7 @@ class TypicalLLMChain(BaseLLMChain):
|
|
|
128
135
|
|
|
129
136
|
def invoke(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
130
137
|
message = self.input_guardrail(message)
|
|
131
|
-
message = self.
|
|
138
|
+
message = self.prompt_augmenter(message)
|
|
132
139
|
message = self.generate(message, **kwargs)
|
|
133
140
|
message = self.output_guardrail(message)
|
|
134
141
|
return message
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
class LLMPromptEnhancer(BasePromptEnhancer):
|
|
138
|
-
"""A prompt enhancer that use an LLM chain to rewrite the prompt."""
|
|
139
|
-
|
|
140
|
-
chain: BaseLLMChain = Field(..., description="LLM chain that rewrite the prompt")
|
|
141
|
-
|
|
142
|
-
def __call__(self, message: AgentMessage, **kwargs) -> AgentMessage:
|
|
143
|
-
message = self.chain.invoke(message, **kwargs)
|
|
144
|
-
if message.execution_result != "success":
|
|
145
|
-
return message
|
|
146
|
-
_, message.query = message.responses[-1]
|
|
147
|
-
message.responses = []
|
|
148
|
-
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,6 +334,53 @@ 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_", "")
|
|
@@ -346,7 +397,7 @@ class CoordinatorAgent(BaseAgent):
|
|
|
346
397
|
query=self.summary_prompt,
|
|
347
398
|
context={summary_steps_key: sub_task_result},
|
|
348
399
|
responses=[],
|
|
349
|
-
)
|
|
400
|
+
)
|
|
350
401
|
summary_message = self.planner_agent.execute(summary_message, **kwargs)
|
|
351
402
|
else:
|
|
352
403
|
return message
|
|
@@ -357,7 +408,7 @@ class CoordinatorAgent(BaseAgent):
|
|
|
357
408
|
query=message.query,
|
|
358
409
|
context={summary_steps_key: sub_task_result},
|
|
359
410
|
responses=[],
|
|
360
|
-
)
|
|
411
|
+
)
|
|
361
412
|
summary_message = self.summary_chain.invoke(summary_message, **kwargs)
|
|
362
413
|
|
|
363
414
|
self.state = "idle"
|
|
@@ -520,7 +571,7 @@ class VotingAgent(BaseAgent):
|
|
|
520
571
|
scorer = rouge_scorer.RougeScorer([self.scorer], use_stemmer=True)
|
|
521
572
|
|
|
522
573
|
def rouge_score(hyp: str, ref: str) -> float:
|
|
523
|
-
return scorer.score(ref, hyp)[
|
|
574
|
+
return list(scorer.score(ref, hyp).values())[0].fmeasure
|
|
524
575
|
|
|
525
576
|
score_func = rouge_score
|
|
526
577
|
else:
|
|
@@ -529,7 +580,7 @@ class VotingAgent(BaseAgent):
|
|
|
529
580
|
scores = {agent.card.name: 0.0 for agent in self.agents}
|
|
530
581
|
message_map = {name: msg for name, msg in message.responses}
|
|
531
582
|
for agent_name, msg in message.responses:
|
|
532
|
-
total_score = 0
|
|
583
|
+
total_score = 0.0
|
|
533
584
|
for other_agent in self.agents:
|
|
534
585
|
if agent_name == other_agent.card.name:
|
|
535
586
|
continue
|
|
@@ -559,7 +610,7 @@ class VotingAgent(BaseAgent):
|
|
|
559
610
|
try:
|
|
560
611
|
voting_message = AgentMessage(
|
|
561
612
|
query=self.voting_prompt, responses=[]
|
|
562
|
-
)
|
|
613
|
+
)
|
|
563
614
|
voting_message = agent.execute(voting_message, **kwargs)
|
|
564
615
|
total_score += self.scorer(voting_message.responses[-1][1])
|
|
565
616
|
except Exception:
|