pal-code 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.
- pal/__init__.py +29 -0
- pal/__main__.py +3 -0
- pal/access_manager/__init__.py +16 -0
- pal/access_manager/components/__init__.py +14 -0
- pal/access_manager/components/access_control.py +41 -0
- pal/access_manager/components/base.py +39 -0
- pal/access_manager/components/intervention.py +31 -0
- pal/access_manager/components/privilege_registry.py +30 -0
- pal/access_manager/pipeline.py +68 -0
- pal/access_manager/utils/__init__.py +2 -0
- pal/agent_constraints/__init__.py +3 -0
- pal/agent_constraints/components/AGENT_OUTPUT.md +13 -0
- pal/agent_constraints/components/__init__.py +3 -0
- pal/agent_constraints/components/constraints.py +22 -0
- pal/agent_constraints/pipeline.py +22 -0
- pal/agent_constraints/utils/__init__.py +2 -0
- pal/agents/__init__.py +12 -0
- pal/agents/coding.py +14 -0
- pal/agents/docs.py +15 -0
- pal/chat/__init__.py +4 -0
- pal/chat/demo.py +231 -0
- pal/chat/providers.py +23 -0
- pal/chat/service.py +591 -0
- pal/chat/services/__init__.py +26 -0
- pal/chat/services/base.py +142 -0
- pal/chat/services/data.py +142 -0
- pal/chat/services/media.py +123 -0
- pal/chat/services/web.py +60 -0
- pal/chat/sessions.py +108 -0
- pal/chat/tools.py +446 -0
- pal/chat/uploads.py +81 -0
- pal/cli.py +502 -0
- pal/compression/__init__.py +12 -0
- pal/compression/components/__init__.py +64 -0
- pal/compression/components/adverbs.py +20 -0
- pal/compression/components/hedges.py +54 -0
- pal/compression/components/interjections.py +20 -0
- pal/compression/components/minifier.py +56 -0
- pal/compression/components/output_compressor.py +13 -0
- pal/compression/components/pleonasms.py +54 -0
- pal/compression/components/punctuations.py +36 -0
- pal/compression/components/stopwords.py +62 -0
- pal/compression/history.py +346 -0
- pal/compression/pipeline.py +58 -0
- pal/compression/utils/__init__.py +30 -0
- pal/compression/utils/negations.py +18 -0
- pal/compression/utils/nltk_setup.py +20 -0
- pal/compression/utils/pronouns.py +27 -0
- pal/compression/utils/proselint_setup.py +9 -0
- pal/compression/utils/spacy_setup.py +22 -0
- pal/config/default.yaml +163 -0
- pal/config.py +504 -0
- pal/context_manager/__init__.py +7 -0
- pal/context_manager/chat_context.py +117 -0
- pal/context_manager/components/__init__.py +5 -0
- pal/context_manager/components/assembler.py +75 -0
- pal/context_manager/components/context_cache.py +45 -0
- pal/context_manager/components/kv_bridge.py +16 -0
- pal/context_manager/components/pager.py +48 -0
- pal/context_manager/components/simple_context_manager.py +56 -0
- pal/context_manager/components/turns.py +88 -0
- pal/context_manager/pipeline.py +27 -0
- pal/context_manager/utils/__init__.py +18 -0
- pal/context_manager/utils/hashing.py +5 -0
- pal/context_manager/utils/relevance.py +59 -0
- pal/context_manager/utils/storage.py +37 -0
- pal/documents/__init__.py +4 -0
- pal/documents/components/__init__.py +0 -0
- pal/documents/components/chunker.py +79 -0
- pal/documents/components/index.py +155 -0
- pal/documents/utils/__init__.py +1 -0
- pal/errors.py +40 -0
- pal/input_converters/__init__.py +8 -0
- pal/input_converters/components/__init__.py +0 -0
- pal/input_converters/components/base.py +38 -0
- pal/input_converters/components/pandoc_converter.py +87 -0
- pal/input_converters/components/pdf_converter.py +46 -0
- pal/input_converters/components/pptx_converter.py +52 -0
- pal/input_converters/components/xlsx_converter.py +38 -0
- pal/input_converters/pipeline.py +72 -0
- pal/input_converters/utils/__init__.py +1 -0
- pal/input_converters/workspace.py +56 -0
- pal/input_savers/INPUT_RULES.md +2 -0
- pal/input_savers/__init__.py +5 -0
- pal/input_savers/components/__init__.py +0 -0
- pal/input_savers/components/base.py +40 -0
- pal/input_savers/components/methods.py +41 -0
- pal/input_savers/components/verifier.py +39 -0
- pal/input_savers/pipeline.py +154 -0
- pal/input_savers/utils/__init__.py +0 -0
- pal/input_savers/utils/guards.py +94 -0
- pal/knowledge_graph/__init__.py +10 -0
- pal/knowledge_graph/components/__init__.py +0 -0
- pal/knowledge_graph/components/base.py +50 -0
- pal/knowledge_graph/components/builder.py +54 -0
- pal/knowledge_graph/components/retriever.py +100 -0
- pal/knowledge_graph/components/workspace.py +116 -0
- pal/knowledge_graph/pipeline.py +215 -0
- pal/knowledge_graph/utils/__init__.py +1 -0
- pal/latency_tracker/__init__.py +3 -0
- pal/latency_tracker/pipeline.py +86 -0
- pal/llm_scheduler/__init__.py +11 -0
- pal/llm_scheduler/api.py +137 -0
- pal/llm_scheduler/code.py +28 -0
- pal/llm_scheduler/core.py +92 -0
- pal/llm_scheduler/local.py +215 -0
- pal/llm_scheduler/pipeline.py +42 -0
- pal/llm_scheduler/types.py +92 -0
- pal/llm_wrapper/__init__.py +34 -0
- pal/llm_wrapper/adapter.py +316 -0
- pal/llm_wrapper/components/__init__.py +23 -0
- pal/llm_wrapper/components/adapter_types.py +90 -0
- pal/llm_wrapper/components/base.py +195 -0
- pal/llm_wrapper/components/features.py +104 -0
- pal/llm_wrapper/components/litellm_wrapper.py +215 -0
- pal/llm_wrapper/components/openrouter_wrapper.py +114 -0
- pal/llm_wrapper/modes.py +73 -0
- pal/llm_wrapper/pipeline.py +61 -0
- pal/llm_wrapper/utils/__init__.py +4 -0
- pal/llm_wrapper/utils/registry.py +49 -0
- pal/llm_wrapper/utils/token_counting.py +10 -0
- pal/local_llm/__init__.py +5 -0
- pal/local_llm/components/__init__.py +0 -0
- pal/local_llm/components/huggingface_local.py +217 -0
- pal/local_llm/components/local_llm.py +60 -0
- pal/logging_setup.py +18 -0
- pal/memory/__init__.py +7 -0
- pal/memory/components/__init__.py +6 -0
- pal/memory/components/lru_k.py +56 -0
- pal/memory/components/manager.py +98 -0
- pal/memory/components/note.py +54 -0
- pal/memory/components/storage_manager.py +60 -0
- pal/memory/pipeline.py +122 -0
- pal/memory/utils/__init__.py +3 -0
- pal/memory/utils/keyword_overlap.py +3 -0
- pal/memory_tracker/__init__.py +3 -0
- pal/memory_tracker/components/__init__.py +0 -0
- pal/memory_tracker/components/process.py +56 -0
- pal/memory_tracker/pipeline.py +44 -0
- pal/metrics/__init__.py +4 -0
- pal/metrics/components/__init__.py +4 -0
- pal/metrics/components/collector.py +140 -0
- pal/metrics/components/exporter.py +28 -0
- pal/metrics/pipeline.py +49 -0
- pal/metrics/utils/__init__.py +2 -0
- pal/notes_manager/__init__.py +5 -0
- pal/notes_manager/components/__init__.py +0 -0
- pal/notes_manager/components/privacy.py +14 -0
- pal/notes_manager/components/recall.py +31 -0
- pal/notes_manager/components/tidy.py +28 -0
- pal/notes_manager/pipeline.py +138 -0
- pal/orchestrator/__init__.py +26 -0
- pal/orchestrator/components/__init__.py +26 -0
- pal/orchestrator/components/agent.py +105 -0
- pal/orchestrator/components/cgroup_manager.py +209 -0
- pal/orchestrator/components/context_interrupt.py +220 -0
- pal/orchestrator/components/limits.py +9 -0
- pal/orchestrator/components/scheduler.py +69 -0
- pal/orchestrator/components/syscall_scheduler.py +200 -0
- pal/orchestrator/pipeline.py +228 -0
- pal/orchestrator/utils/__init__.py +4 -0
- pal/orchestrator/utils/device.py +164 -0
- pal/orchestrator/utils/protocol.py +32 -0
- pal/os_kernel/__init__.py +5 -0
- pal/os_kernel/filesystem.py +80 -0
- pal/os_kernel/network.py +146 -0
- pal/os_kernel/pipeline.py +175 -0
- pal/os_kernel/sysinfo.py +36 -0
- pal/paths.py +59 -0
- pal/pipeline.py +177 -0
- pal/py.typed +0 -0
- pal/redact.py +19 -0
- pal/sdk/__init__.py +33 -0
- pal/sdk/adapters.py +43 -0
- pal/sdk/agent.py +99 -0
- pal/sdk/components/__init__.py +14 -0
- pal/sdk/components/autogen_adapter.py +65 -0
- pal/sdk/components/autogen_client.py +98 -0
- pal/sdk/components/framework_adapter_base.py +69 -0
- pal/sdk/components/metagpt_adapter.py +46 -0
- pal/sdk/components/open_interpreter_adapter.py +47 -0
- pal/sdk/components/tool_catalog.py +44 -0
- pal/sdk/os_api.py +84 -0
- pal/sdk/patterns.py +129 -0
- pal/sdk/pipeline.py +240 -0
- pal/sdk/queries.py +68 -0
- pal/sdk/savers.py +88 -0
- pal/sdk/utils/__init__.py +2 -0
- pal/storage_manager/__init__.py +17 -0
- pal/storage_manager/components/__init__.py +19 -0
- pal/storage_manager/components/base.py +50 -0
- pal/storage_manager/components/local_file_backend.py +84 -0
- pal/storage_manager/components/storage_queue.py +91 -0
- pal/storage_manager/components/vector_store_backend.py +122 -0
- pal/storage_manager/pipeline.py +153 -0
- pal/storage_manager/utils/__init__.py +4 -0
- pal/storage_manager/utils/memory_adapter.py +60 -0
- pal/storage_manager/utils/sdk.py +93 -0
- pal/studio/__init__.py +0 -0
- pal/studio/builder.py +166 -0
- pal/studio/catalog.py +90 -0
- pal/studio/checks.py +129 -0
- pal/studio/clients.py +6 -0
- pal/studio/editor.py +137 -0
- pal/studio/files.py +6 -0
- pal/studio/models.py +6 -0
- pal/studio/plan.py +149 -0
- pal/studio/planner.py +205 -0
- pal/studio/policy.py +69 -0
- pal/studio/routes.py +254 -0
- pal/studio/runs.py +210 -0
- pal/task_router/__init__.py +11 -0
- pal/task_router/components/__init__.py +0 -0
- pal/task_router/components/answerer.py +37 -0
- pal/task_router/components/base.py +34 -0
- pal/task_router/components/classifier.py +40 -0
- pal/task_router/components/gates.py +23 -0
- pal/task_router/pipeline.py +61 -0
- pal/task_router/utils/__init__.py +1 -0
- pal/terminal/__init__.py +1 -0
- pal/terminal/backend.py +141 -0
- pal/terminal/bench.py +173 -0
- pal/terminal/cli.py +826 -0
- pal/terminal/clients.py +170 -0
- pal/terminal/commands.py +401 -0
- pal/terminal/decide.py +242 -0
- pal/terminal/files.py +55 -0
- pal/terminal/flow_cli.py +439 -0
- pal/terminal/hardware.py +199 -0
- pal/terminal/history.py +88 -0
- pal/terminal/keys.py +83 -0
- pal/terminal/local_models.py +261 -0
- pal/terminal/logo.py +117 -0
- pal/terminal/models.py +99 -0
- pal/terminal/policy.py +54 -0
- pal/terminal/project.py +413 -0
- pal/terminal/review.py +65 -0
- pal/terminal/security.py +239 -0
- pal/terminal/session.py +503 -0
- pal/terminal/setup.py +354 -0
- pal/terminal/syntax.py +98 -0
- pal/terminal/theme.py +37 -0
- pal/terminal/ui.py +647 -0
- pal/terminal/voice.py +137 -0
- pal/terminal/workflow.py +624 -0
- pal/token_tracker/__init__.py +3 -0
- pal/token_tracker/pipeline.py +54 -0
- pal/tokens.py +21 -0
- pal/tool_catalog.py +107 -0
- pal/tool_manager/__init__.py +17 -0
- pal/tool_manager/builtin.py +82 -0
- pal/tool_manager/components/__init__.py +19 -0
- pal/tool_manager/components/base.py +53 -0
- pal/tool_manager/components/conflict_resolver.py +27 -0
- pal/tool_manager/components/param_validator.py +41 -0
- pal/tool_manager/components/tool_call_queue.py +111 -0
- pal/tool_manager/components/tool_registry.py +60 -0
- pal/tool_manager/pipeline.py +80 -0
- pal/tool_manager/utils/__init__.py +3 -0
- pal/web/__init__.py +3 -0
- pal/web/app.py +276 -0
- pal/web/static/editor.js +219 -0
- pal/web/static/fonts/LICENSE-Inter.txt +3 -0
- pal/web/static/fonts/inter-400.woff2 +0 -0
- pal/web/static/fonts/inter-500.woff2 +0 -0
- pal/web/static/fonts/inter-600.woff2 +0 -0
- pal/web/static/fonts/inter-700.woff2 +0 -0
- pal/web/static/inter.css +5 -0
- pal/web/static/pal-logo-256.png +0 -0
- pal/web/static/pal-logo-32.png +0 -0
- pal/web/static/pal-logo-64.png +0 -0
- pal/web/static/studio.css +306 -0
- pal/web/static/studio.js +533 -0
- pal/web/static/voice.js +76 -0
- pal/web/templates/index.html +368 -0
- pal/web/templates/studio.html +120 -0
- pal_code-0.1.0.dist-info/METADATA +379 -0
- pal_code-0.1.0.dist-info/RECORD +282 -0
- pal_code-0.1.0.dist-info/WHEEL +5 -0
- pal_code-0.1.0.dist-info/entry_points.txt +2 -0
- pal_code-0.1.0.dist-info/licenses/LICENSE +21 -0
- pal_code-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter for AutoGen (Wu et al., 2023). AutoGen's ConversableAgent
|
|
3
|
+
ultimately calls out to an OpenAIWrapper instance's .create() method,
|
|
4
|
+
which builds the actual chat-completion request. This adapter
|
|
5
|
+
monkeypatches OpenAIWrapper.create to call the AgentSDK's chat()
|
|
6
|
+
instead, and wraps the reply back into the minimal ChatCompletion-like
|
|
7
|
+
shape (.choices[0].message.content) AutoGen's own code reads from --
|
|
8
|
+
so callers inside AutoGen don't notice the difference.
|
|
9
|
+
|
|
10
|
+
Targets the AutoGen 0.2 API (`autogen.oai.client.OpenAIWrapper`, the `pyautogen<0.3` / `ag2<1` line). The
|
|
11
|
+
redesigned AutoGen (autogen-agentchat 0.4+) has no such class; use `PalChatCompletionClient` for that one.
|
|
12
|
+
Text replies only: tool and function calling are not redirected.
|
|
13
|
+
"""
|
|
14
|
+
from types import SimpleNamespace
|
|
15
|
+
|
|
16
|
+
from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AutoGenAdapter(FrameworkAdapter):
|
|
20
|
+
framework_name = "autogen"
|
|
21
|
+
|
|
22
|
+
def install(self) -> None:
|
|
23
|
+
try:
|
|
24
|
+
from autogen.oai.client import OpenAIWrapper
|
|
25
|
+
except ImportError as e:
|
|
26
|
+
raise FrameworkAdapterError(
|
|
27
|
+
"AutoGen 0.2 is not installed -- run `pip install \"pyautogen==0.2.35\"` to use this adapter. For AutoGen 0.4+ "
|
|
28
|
+
"use pal.sdk.autogen_model_client(sdk) instead"
|
|
29
|
+
) from e
|
|
30
|
+
|
|
31
|
+
if not hasattr(OpenAIWrapper, "create"):
|
|
32
|
+
raise FrameworkAdapterError(
|
|
33
|
+
"autogen.oai.client.OpenAIWrapper has no 'create' method -- this adapter "
|
|
34
|
+
"targets the AutoGen API this project was built against; a newer or older "
|
|
35
|
+
"AutoGen version may have changed it"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self._check_free(OpenAIWrapper.create)
|
|
39
|
+
self._target = OpenAIWrapper
|
|
40
|
+
self._original = OpenAIWrapper.create
|
|
41
|
+
sdk = self.sdk
|
|
42
|
+
|
|
43
|
+
def _redirected_create(self_wrapper, **kwargs):
|
|
44
|
+
messages = kwargs.get("messages", [])
|
|
45
|
+
call = {k: kwargs[k] for k in ("max_tokens", "temperature") if isinstance(kwargs.get(k), (int, float))}
|
|
46
|
+
if "max_tokens" in call:
|
|
47
|
+
call["max_tokens"] = int(call["max_tokens"])
|
|
48
|
+
resp = sdk.chat_response(messages, **call)
|
|
49
|
+
message = SimpleNamespace(content=resp.text, role="assistant", function_call=None, tool_calls=None)
|
|
50
|
+
choice = SimpleNamespace(message=message, finish_reason="stop", index=0)
|
|
51
|
+
usage = SimpleNamespace(prompt_tokens=resp.prompt_tokens, completion_tokens=resp.completion_tokens,
|
|
52
|
+
total_tokens=resp.prompt_tokens + resp.completion_tokens)
|
|
53
|
+
# AutoGen 0.2 reads these back from every response (extract_text_or_completion_object, usage, cost)
|
|
54
|
+
return SimpleNamespace(
|
|
55
|
+
choices=[choice], model=kwargs.get("model", "pal-code"), usage=usage, cost=0.0, config_id=0,
|
|
56
|
+
pass_filter=None,
|
|
57
|
+
message_retrieval_function=lambda r: [c.message.content for c in r.choices])
|
|
58
|
+
|
|
59
|
+
OpenAIWrapper.create = self._mark(_redirected_create)
|
|
60
|
+
self._installed = True
|
|
61
|
+
|
|
62
|
+
def uninstall(self) -> None:
|
|
63
|
+
if self._installed:
|
|
64
|
+
self._target.create = self._original
|
|
65
|
+
self._installed = False
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Pal-backed model client for the current AutoGen (autogen-agentchat 0.4 and later).
|
|
2
|
+
|
|
3
|
+
The redesigned AutoGen takes a `model_client=` object instead of calling a patchable class, so no monkeypatching
|
|
4
|
+
is needed: pass this client to any AutoGen agent and every model call goes through the Pal SDK.
|
|
5
|
+
|
|
6
|
+
sdk = AgentSDK.standalone(WrapperConfig(backend="gemini", model="...", api_key="..."))
|
|
7
|
+
agent = AssistantAgent("helper", model_client=autogen_model_client(sdk))
|
|
8
|
+
|
|
9
|
+
Text only: AutoGen's tool and function calling are not redirected (a clear error is raised if requested).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from pal.sdk.components.framework_adapter_base import FrameworkAdapterError
|
|
18
|
+
from pal.tokens import estimate_tokens
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def autogen_model_client(sdk, model: str = "pal-code") -> Any:
|
|
22
|
+
try:
|
|
23
|
+
from autogen_core.models import (
|
|
24
|
+
AssistantMessage,
|
|
25
|
+
ChatCompletionClient,
|
|
26
|
+
CreateResult,
|
|
27
|
+
ModelInfo,
|
|
28
|
+
RequestUsage,
|
|
29
|
+
SystemMessage,
|
|
30
|
+
)
|
|
31
|
+
except ImportError as e:
|
|
32
|
+
raise FrameworkAdapterError(
|
|
33
|
+
"autogen-agentchat is not installed -- run `pip install autogen-agentchat` (AutoGen 0.4+) to use this client"
|
|
34
|
+
) from e
|
|
35
|
+
|
|
36
|
+
def to_text(content: Any) -> str:
|
|
37
|
+
return content if isinstance(content, str) else " ".join(str(getattr(p, "content", p)) for p in content)
|
|
38
|
+
|
|
39
|
+
def convert(messages: Sequence[Any]) -> list[dict]:
|
|
40
|
+
out = []
|
|
41
|
+
for m in messages:
|
|
42
|
+
role = "system" if isinstance(m, SystemMessage) else "assistant" if isinstance(m, AssistantMessage) else "user"
|
|
43
|
+
out.append({"role": role, "content": to_text(getattr(m, "content", ""))})
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
class PalChatCompletionClient(ChatCompletionClient):
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self._sdk = sdk
|
|
49
|
+
self._total = RequestUsage(prompt_tokens=0, completion_tokens=0)
|
|
50
|
+
self._last = RequestUsage(prompt_tokens=0, completion_tokens=0)
|
|
51
|
+
|
|
52
|
+
async def create(self, messages, *, tools=(), tool_choice="auto", json_output=None, extra_create_args=None,
|
|
53
|
+
cancellation_token=None):
|
|
54
|
+
if tools:
|
|
55
|
+
raise FrameworkAdapterError("tool calling is not supported through the Pal model client")
|
|
56
|
+
args = dict(extra_create_args or {})
|
|
57
|
+
call = {k: args[k] for k in ("max_tokens", "temperature") if isinstance(args.get(k), (int, float))}
|
|
58
|
+
if "max_tokens" in call:
|
|
59
|
+
call["max_tokens"] = int(call["max_tokens"])
|
|
60
|
+
resp = await asyncio.to_thread(self._sdk.chat_response, convert(messages), **call) # never block the loop
|
|
61
|
+
self._last = RequestUsage(prompt_tokens=resp.prompt_tokens or 0, completion_tokens=resp.completion_tokens or 0)
|
|
62
|
+
self._total = RequestUsage(prompt_tokens=self._total.prompt_tokens + self._last.prompt_tokens,
|
|
63
|
+
completion_tokens=self._total.completion_tokens + self._last.completion_tokens)
|
|
64
|
+
return CreateResult(finish_reason="stop", content=resp.text, usage=self._last,
|
|
65
|
+
cached=bool(resp.meta.get("cache")))
|
|
66
|
+
|
|
67
|
+
async def create_stream(self, messages, *, tools=(), tool_choice="auto", json_output=None,
|
|
68
|
+
extra_create_args=None, cancellation_token=None):
|
|
69
|
+
result = await self.create(messages, tools=tools, tool_choice=tool_choice, json_output=json_output,
|
|
70
|
+
extra_create_args=extra_create_args, cancellation_token=cancellation_token)
|
|
71
|
+
yield str(result.content)
|
|
72
|
+
yield result
|
|
73
|
+
|
|
74
|
+
async def close(self) -> None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def actual_usage(self):
|
|
78
|
+
return self._total
|
|
79
|
+
|
|
80
|
+
def total_usage(self):
|
|
81
|
+
return self._total
|
|
82
|
+
|
|
83
|
+
def count_tokens(self, messages, *, tools=()) -> int:
|
|
84
|
+
return sum(estimate_tokens(m["content"]) for m in convert(messages))
|
|
85
|
+
|
|
86
|
+
def remaining_tokens(self, messages, *, tools=()) -> int:
|
|
87
|
+
return max(0, 100_000 - self.count_tokens(messages))
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def capabilities(self):
|
|
91
|
+
return self.model_info
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def model_info(self):
|
|
95
|
+
return ModelInfo(vision=False, function_calling=False, json_output=False, family=model,
|
|
96
|
+
structured_output=False)
|
|
97
|
+
|
|
98
|
+
return PalChatCompletionClient()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent framework adapters (AIOS section 3.9): let agents built with an
|
|
3
|
+
existing framework (Autogen, Open Interpreter, MetaGPT, ...) run inside
|
|
4
|
+
pal without any modification to the agent's own code. Each
|
|
5
|
+
adapter locates that framework's core LLM-completion function and
|
|
6
|
+
monkeypatches it to redirect through an AgentSDK's chat() instead -- so
|
|
7
|
+
the framework keeps its own agent-creation API, but every actual model
|
|
8
|
+
call goes through pal's llm_wrapper (compression, caching, paging,
|
|
9
|
+
the syscall scheduler, all of it) rather than the framework's own HTTP
|
|
10
|
+
client.
|
|
11
|
+
|
|
12
|
+
These adapters target each framework's documented core completion
|
|
13
|
+
entry point. A framework's internals can and do change between
|
|
14
|
+
releases, so install() raises a clear FrameworkAdapterError (naming the
|
|
15
|
+
missing attribute) rather than failing silently if a targeted attribute
|
|
16
|
+
path isn't found -- an adapter that silently no-ops would leave an
|
|
17
|
+
agent quietly calling the real API instead of pal, which is worse
|
|
18
|
+
than a loud failure that tells the caller exactly what broke.
|
|
19
|
+
"""
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
|
|
22
|
+
from pal.sdk.pipeline import AgentSDK
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FrameworkAdapterError(RuntimeError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FrameworkAdapter(ABC):
|
|
30
|
+
framework_name: str = "base"
|
|
31
|
+
|
|
32
|
+
def __init__(self, sdk: AgentSDK):
|
|
33
|
+
self.sdk = sdk
|
|
34
|
+
self._installed = False
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def install(self) -> None:
|
|
38
|
+
"""Monkeypatches the framework's core completion function to
|
|
39
|
+
redirect through self.sdk.chat(). Raises FrameworkAdapterError
|
|
40
|
+
if the framework isn't installed, or if its expected entry
|
|
41
|
+
point isn't found (version mismatch)."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def uninstall(self) -> None:
|
|
46
|
+
"""Restores the framework's original completion function."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def installed(self) -> bool:
|
|
51
|
+
return self._installed
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _check_free(fn) -> None:
|
|
55
|
+
"""Refuse to patch a function another adapter already replaced: two SDKs cannot share one framework."""
|
|
56
|
+
if getattr(fn, "_pal_adapter", False):
|
|
57
|
+
raise FrameworkAdapterError("this framework is already adapted by another SDK; uninstall that one first")
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _mark(fn):
|
|
61
|
+
fn._pal_adapter = True
|
|
62
|
+
return fn
|
|
63
|
+
|
|
64
|
+
def __enter__(self):
|
|
65
|
+
self.install()
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def __exit__(self, *exc) -> None:
|
|
69
|
+
self.uninstall()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter for MetaGPT (Hong et al., 2023). Every MetaGPT LLM provider
|
|
3
|
+
(OpenAILLM, etc.) subclasses BaseLLM and implements `acompletion_text`
|
|
4
|
+
as the actual async call to the model. This adapter monkeypatches
|
|
5
|
+
BaseLLM.acompletion_text so MetaGPT's roles/actions keep working
|
|
6
|
+
unmodified while the underlying call routes through pal.
|
|
7
|
+
"""
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MetaGPTAdapter(FrameworkAdapter):
|
|
14
|
+
framework_name = "metagpt"
|
|
15
|
+
|
|
16
|
+
def install(self) -> None:
|
|
17
|
+
try:
|
|
18
|
+
from metagpt.provider.base_llm import BaseLLM
|
|
19
|
+
except ImportError as e:
|
|
20
|
+
raise FrameworkAdapterError(
|
|
21
|
+
"metagpt is not installed -- run `pip install metagpt` to use this adapter"
|
|
22
|
+
) from e
|
|
23
|
+
|
|
24
|
+
if not hasattr(BaseLLM, "acompletion_text"):
|
|
25
|
+
raise FrameworkAdapterError(
|
|
26
|
+
"metagpt.provider.base_llm.BaseLLM has no 'acompletion_text' method -- this "
|
|
27
|
+
"adapter targets the MetaGPT API this project was built against; a newer or "
|
|
28
|
+
"older version may have changed it"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self._check_free(BaseLLM.acompletion_text)
|
|
32
|
+
self._target = BaseLLM
|
|
33
|
+
self._original = BaseLLM.acompletion_text
|
|
34
|
+
sdk = self.sdk
|
|
35
|
+
|
|
36
|
+
async def _redirected_acompletion_text(self_llm, messages, stream=False, timeout=None):
|
|
37
|
+
# the SDK call is synchronous: run it in a thread so MetaGPT's event loop is never blocked
|
|
38
|
+
return await asyncio.to_thread(sdk.chat, messages)
|
|
39
|
+
|
|
40
|
+
BaseLLM.acompletion_text = self._mark(_redirected_acompletion_text)
|
|
41
|
+
self._installed = True
|
|
42
|
+
|
|
43
|
+
def uninstall(self) -> None:
|
|
44
|
+
if self._installed:
|
|
45
|
+
self._target.acompletion_text = self._original
|
|
46
|
+
self._installed = False
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter for Open Interpreter (Lucas, 2024). Open Interpreter's core
|
|
3
|
+
completion call runs through its `interpreter.llm` object's
|
|
4
|
+
`completions` generator (normally built on top of litellm). This
|
|
5
|
+
adapter monkeypatches that object's `completions` attribute so the
|
|
6
|
+
AgentSDK is what actually produces tokens, while Open Interpreter's own
|
|
7
|
+
tool-execution loop keeps running unmodified.
|
|
8
|
+
"""
|
|
9
|
+
from pal.sdk.components.framework_adapter_base import FrameworkAdapter, FrameworkAdapterError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenInterpreterAdapter(FrameworkAdapter):
|
|
13
|
+
framework_name = "open_interpreter"
|
|
14
|
+
|
|
15
|
+
def install(self) -> None:
|
|
16
|
+
try:
|
|
17
|
+
import interpreter as oi
|
|
18
|
+
except ImportError as e:
|
|
19
|
+
raise FrameworkAdapterError(
|
|
20
|
+
"open-interpreter is not installed -- run `pip install open-interpreter` to use this adapter"
|
|
21
|
+
) from e
|
|
22
|
+
|
|
23
|
+
llm = getattr(oi.interpreter, "llm", None)
|
|
24
|
+
if llm is None:
|
|
25
|
+
raise FrameworkAdapterError(
|
|
26
|
+
"interpreter.interpreter.llm was not found -- this adapter targets the "
|
|
27
|
+
"Open Interpreter API this project was built against; a newer or older "
|
|
28
|
+
"version may have changed it"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self._check_free(getattr(llm, "completions", None))
|
|
32
|
+
self._target = llm
|
|
33
|
+
self._original = getattr(llm, "completions", None)
|
|
34
|
+
sdk = self.sdk
|
|
35
|
+
|
|
36
|
+
def _redirected_completions(**kwargs):
|
|
37
|
+
messages = kwargs.get("messages", [])
|
|
38
|
+
text = sdk.chat(messages)
|
|
39
|
+
yield {"choices": [{"delta": {"content": text}}]}
|
|
40
|
+
|
|
41
|
+
llm.completions = self._mark(_redirected_completions)
|
|
42
|
+
self._installed = True
|
|
43
|
+
|
|
44
|
+
def uninstall(self) -> None:
|
|
45
|
+
if self._installed:
|
|
46
|
+
self._target.completions = self._original
|
|
47
|
+
self._installed = False
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A small built-in tool catalog (AIOS section 3.9's "Tool Integration"):
|
|
3
|
+
a minimal, dependency-free set of example tools covering a few
|
|
4
|
+
different input/output modalities (free text, structured expressions,
|
|
5
|
+
plain numbers), registered into a ToolManagerPipeline so a fresh
|
|
6
|
+
AgentSDK has something callable out of the box.
|
|
7
|
+
|
|
8
|
+
This is intentionally small. AIOS's own SDK ships a much larger
|
|
9
|
+
catalog of tools sourced from real external platforms, which needs
|
|
10
|
+
network access and API keys this project doesn't assume are available.
|
|
11
|
+
Real deployments register their own tools alongside (or instead of)
|
|
12
|
+
these via ToolManagerPipeline.register_tool() -- this catalog is a
|
|
13
|
+
starting point, not a ceiling.
|
|
14
|
+
"""
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from pal.tool_manager.builtin import safe_eval
|
|
18
|
+
from pal.tool_manager.components.base import ToolParam
|
|
19
|
+
from pal.tool_manager.pipeline import ToolManagerPipeline
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _calculator():
|
|
23
|
+
# AST-based evaluator: never eval(), so it cannot execute code and cannot be made to hang.
|
|
24
|
+
return safe_eval
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _current_time():
|
|
28
|
+
def current_time() -> str:
|
|
29
|
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
|
30
|
+
|
|
31
|
+
return current_time
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _word_count():
|
|
35
|
+
def word_count(text: str) -> int:
|
|
36
|
+
return len(text.split())
|
|
37
|
+
|
|
38
|
+
return word_count
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_builtin_tools(tool_manager: ToolManagerPipeline) -> None:
|
|
42
|
+
tool_manager.register_tool("calculator", _calculator, params=[ToolParam("expression", str)])
|
|
43
|
+
tool_manager.register_tool("current_time", _current_time, params=[])
|
|
44
|
+
tool_manager.register_tool("word_count", _word_count, params=[ToolParam("text", str)])
|
pal/sdk/os_api.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""`sdk.os`: friendly names for the OS-kernel queries. Each call is an OSQuery routed to the OS kernel; a refusal
|
|
2
|
+
raises OSKernelError instead of returning an error value."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pal.os_kernel import OSKernelError
|
|
8
|
+
from pal.sdk.queries import OSQuery
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OSAPI:
|
|
12
|
+
def __init__(self, sdk: Any):
|
|
13
|
+
self._sdk = sdk
|
|
14
|
+
|
|
15
|
+
def _call(self, op: str, **params: Any) -> Any:
|
|
16
|
+
resp = self._sdk.send_request(OSQuery(op=op, params=params))
|
|
17
|
+
if not resp.ok:
|
|
18
|
+
raise OSKernelError(resp.error)
|
|
19
|
+
return resp.data
|
|
20
|
+
|
|
21
|
+
def read_file(self, path: str) -> str:
|
|
22
|
+
return self._call("file_read", path=path)
|
|
23
|
+
|
|
24
|
+
def write_file(self, path: str, content: str) -> dict:
|
|
25
|
+
return self._call("file_write", path=path, content=content)
|
|
26
|
+
|
|
27
|
+
def read_bytes(self, path: str) -> bytes:
|
|
28
|
+
"""A workspace file as bytes (images, audio, PDFs, ...), within the OS kernel's binary size limit."""
|
|
29
|
+
import base64
|
|
30
|
+
return base64.b64decode(self._call("file_read_b64", path=path)["b64"])
|
|
31
|
+
|
|
32
|
+
def file_to_markdown(self, path: str) -> str:
|
|
33
|
+
"""Convert a PDF, Word, PowerPoint, Excel, EPUB, HTML or LaTeX file in the workspace to markdown, save it under converted/,
|
|
34
|
+
and return a description with the beginning of the text."""
|
|
35
|
+
from pal.errors import PalError
|
|
36
|
+
from pal.input_converters.components.base import ConversionError
|
|
37
|
+
from pal.input_converters.workspace import convert_workspace_file, describe
|
|
38
|
+
try:
|
|
39
|
+
r = convert_workspace_file(path, read_bytes=self.read_bytes, write_text=lambda p, t: self.write_file(p, t),
|
|
40
|
+
exists=lambda p: p in self.list_files(p))
|
|
41
|
+
except ConversionError as e:
|
|
42
|
+
raise PalError(str(e)) from e
|
|
43
|
+
return describe(r)
|
|
44
|
+
|
|
45
|
+
def list_files(self, prefix: str = "") -> list[str]:
|
|
46
|
+
return self._call("file_list", prefix=prefix)
|
|
47
|
+
|
|
48
|
+
def delete_file(self, path: str) -> dict:
|
|
49
|
+
return self._call("file_delete", path=path)
|
|
50
|
+
|
|
51
|
+
def http_get(self, url: str) -> dict:
|
|
52
|
+
return self._call("net_get", url=url)
|
|
53
|
+
|
|
54
|
+
def http_post(self, url: str, json: Any = None) -> dict:
|
|
55
|
+
return self._call("net_post", url=url, json=json)
|
|
56
|
+
|
|
57
|
+
def system_info(self) -> dict:
|
|
58
|
+
return self._call("sys_info")
|
|
59
|
+
|
|
60
|
+
def keyboard(self, **kw: Any) -> Any:
|
|
61
|
+
return self._call("keyboard", **kw)
|
|
62
|
+
|
|
63
|
+
def mouse(self, **kw: Any) -> Any:
|
|
64
|
+
return self._call("mouse", **kw)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def register_os_tools(sdk: Any) -> list[str]:
|
|
68
|
+
"""Make the OS operations callable as tools (for ReAct and similar agents). The tools run as OSQuery calls, so they
|
|
69
|
+
reach the OS kernel and never a model. Not registered by default: the chat's tools stay read-only."""
|
|
70
|
+
from pal.tool_manager.components.base import ToolParam
|
|
71
|
+
|
|
72
|
+
tm = sdk._tool_manager
|
|
73
|
+
api = sdk.os
|
|
74
|
+
specs = [
|
|
75
|
+
("read_file", lambda: api.read_file, [ToolParam("path", str)]),
|
|
76
|
+
("write_file", lambda: api.write_file, [ToolParam("path", str), ToolParam("content", str)]),
|
|
77
|
+
("list_files", lambda: api.list_files, [ToolParam("prefix", str, required=False, default="")]),
|
|
78
|
+
("file_to_markdown", lambda: api.file_to_markdown, [ToolParam("path", str)]),
|
|
79
|
+
("http_get", lambda: api.http_get, [ToolParam("url", str)]),
|
|
80
|
+
("system_info", lambda: api.system_info, []),
|
|
81
|
+
]
|
|
82
|
+
for name, loader, params in specs:
|
|
83
|
+
tm.register_tool(name, loader, params=params)
|
|
84
|
+
return [n for n, _, _ in specs]
|
pal/sdk/patterns.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""ReAct and Reflexion as native agent patterns.
|
|
2
|
+
|
|
3
|
+
Both are reasoning *patterns* (papers), not libraries with a single call to intercept, so Pal ships them as
|
|
4
|
+
ready-made native agents built only on SDK calls. Subclass or configure them to make your own agent.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pal.sdk.agent import AgentResult, NativeAgent
|
|
15
|
+
|
|
16
|
+
_FINAL = re.compile(r"Final Answer:\s*(.*)", re.S | re.I)
|
|
17
|
+
_ACTION = re.compile(r"^\s*Action:\s*([A-Za-z_][\w.-]*)\s*(.*)$", re.M)
|
|
18
|
+
_CUT = re.compile(r"\n\s*Observation:.*", re.S)
|
|
19
|
+
|
|
20
|
+
REACT_FORMAT = (
|
|
21
|
+
"Solve the task step by step. In each reply write 'Thought: <short reasoning>' and then either "
|
|
22
|
+
"'Action: <tool_name> {\"arg\": value}' (JSON arguments) or 'Final Answer: <answer>'. After an Action you "
|
|
23
|
+
"will receive 'Observation: <result>'. Tools: {tools}.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ReActStep:
|
|
28
|
+
thought: str
|
|
29
|
+
tool: str
|
|
30
|
+
args: dict
|
|
31
|
+
observation: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ReActAgent(NativeAgent):
|
|
35
|
+
"""Reason, act, observe (Yao et al., 2023): the model alternates thoughts with tool calls until it answers."""
|
|
36
|
+
|
|
37
|
+
max_steps = 6
|
|
38
|
+
max_tokens = 512
|
|
39
|
+
tools: list[str] | None = None # tool names to offer; None = every tool the SDK has
|
|
40
|
+
|
|
41
|
+
def run(self, task: str, attachments: list[dict] | None = None, session: str = "agent") -> AgentResult:
|
|
42
|
+
names = self.tools if self.tools is not None else self.sdk.list_tools()
|
|
43
|
+
role = self.spec.role + "\n" if self.spec else ""
|
|
44
|
+
transcript: list[dict] = [{"role": "system", "content": role + REACT_FORMAT.replace("{tools}", ", ".join(names) or "none")},
|
|
45
|
+
{"role": "user", "content": task}]
|
|
46
|
+
steps: list[ReActStep] = []
|
|
47
|
+
for _ in range(self.max_steps):
|
|
48
|
+
text = _CUT.sub("", self.sdk.chat(transcript, max_tokens=self.max_tokens) or "") # ignore invented observations
|
|
49
|
+
final = _FINAL.search(text)
|
|
50
|
+
if final:
|
|
51
|
+
return AgentResult(final.group(1).strip(), steps=steps)
|
|
52
|
+
act = _ACTION.search(text)
|
|
53
|
+
if act is None: # no action and no marker: treat the reply as the answer
|
|
54
|
+
return AgentResult(text.strip(), steps=steps)
|
|
55
|
+
tool, rest = act.group(1), act.group(2).strip()
|
|
56
|
+
try:
|
|
57
|
+
args = json.loads(rest) if rest else {}
|
|
58
|
+
if not isinstance(args, dict):
|
|
59
|
+
raise ValueError("arguments must be a JSON object")
|
|
60
|
+
observation = str(self.sdk.call_tool(tool, **args))
|
|
61
|
+
except Exception as e: # noqa: BLE001 (a bad call becomes an observation the model can react to)
|
|
62
|
+
args, observation = {}, f"error: {e}"
|
|
63
|
+
thought = text.split("Action:")[0].replace("Thought:", "").strip()
|
|
64
|
+
steps.append(ReActStep(thought, tool, args, observation))
|
|
65
|
+
transcript += [{"role": "assistant", "content": text}, {"role": "user", "content": f"Observation: {observation}"}]
|
|
66
|
+
return AgentResult("", ok=False, steps=steps, error=f"no final answer after {self.max_steps} steps")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class Trial:
|
|
71
|
+
answer: str
|
|
72
|
+
ok: bool
|
|
73
|
+
feedback: str
|
|
74
|
+
reflection: str = ""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Evaluator = Callable[[str, str], "bool | tuple[bool, str]"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ReflexionAgent(NativeAgent):
|
|
81
|
+
"""Try, evaluate, reflect, retry (Shinn et al., 2023).
|
|
82
|
+
|
|
83
|
+
The actor (any native agent; plain model call by default) answers. An evaluator (your function, or the model
|
|
84
|
+
judging itself) says pass or fail. On failure the model writes a short lesson, which is kept in memory and
|
|
85
|
+
shown to the next attempt.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
max_trials = 3
|
|
89
|
+
max_tokens = 400
|
|
90
|
+
|
|
91
|
+
def __init__(self, sdk: Any = None, kernel: Any = None, actor: NativeAgent | None = None,
|
|
92
|
+
evaluator: Evaluator | None = None):
|
|
93
|
+
super().__init__(sdk, kernel)
|
|
94
|
+
self.actor = actor
|
|
95
|
+
self.evaluator = evaluator
|
|
96
|
+
|
|
97
|
+
def _act(self, prompt: str) -> str:
|
|
98
|
+
if self.actor is not None:
|
|
99
|
+
return self.actor.run(prompt).answer
|
|
100
|
+
return self.sdk.chat([{"role": "user", "content": prompt}], max_tokens=self.max_tokens)
|
|
101
|
+
|
|
102
|
+
def _evaluate(self, task: str, answer: str) -> tuple[bool, str]:
|
|
103
|
+
if self.evaluator is not None:
|
|
104
|
+
r = self.evaluator(task, answer)
|
|
105
|
+
return (r, "") if isinstance(r, bool) else (bool(r[0]), str(r[1]))
|
|
106
|
+
verdict = self.sdk.chat([
|
|
107
|
+
{"role": "system", "content": "Judge whether the answer correctly and completely solves the task. "
|
|
108
|
+
"Reply 'PASS' or 'FAIL: <reason>'."},
|
|
109
|
+
{"role": "user", "content": f"Task: {task}\nAnswer: {answer}"}], max_tokens=80) or ""
|
|
110
|
+
return verdict.strip().upper().startswith("PASS"), verdict.strip()
|
|
111
|
+
|
|
112
|
+
def run(self, task: str, attachments: list[dict] | None = None, session: str = "agent") -> AgentResult:
|
|
113
|
+
lessons: list[str] = []
|
|
114
|
+
trials: list[Trial] = []
|
|
115
|
+
for _ in range(self.max_trials):
|
|
116
|
+
prompt = task if not lessons else task + "\n\nLessons from earlier attempts:\n- " + "\n- ".join(lessons)
|
|
117
|
+
answer = self._act(prompt)
|
|
118
|
+
ok, feedback = self._evaluate(task, answer)
|
|
119
|
+
trial = Trial(answer, ok, feedback)
|
|
120
|
+
trials.append(trial)
|
|
121
|
+
if ok:
|
|
122
|
+
return AgentResult(answer, steps=trials)
|
|
123
|
+
trial.reflection = (self.sdk.chat([
|
|
124
|
+
{"role": "system", "content": "In one or two sentences say what went wrong and what to do differently."},
|
|
125
|
+
{"role": "user", "content": f"Task: {task}\nAnswer: {answer}\nFeedback: {feedback}"}],
|
|
126
|
+
max_tokens=120) or "").strip()
|
|
127
|
+
lessons.append(trial.reflection)
|
|
128
|
+
self.sdk.remember(trial.reflection, tags=["reflexion"])
|
|
129
|
+
return AgentResult(trials[-1].answer, ok=False, steps=trials, error=f"not solved in {self.max_trials} trials")
|