deer-agent-framework 0.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.
Files changed (52) hide show
  1. deer/__init__.py +36 -0
  2. deer/builtins/__init__.py +7 -0
  3. deer/builtins/python_manager/agent.py +29 -0
  4. deer/builtins/python_manager/tools.py +54 -0
  5. deer/core/__init__.py +1 -0
  6. deer/core/agent.py +463 -0
  7. deer/core/ui.py +31 -0
  8. deer/drivers/__init__.py +66 -0
  9. deer/drivers/base_driver.py +56 -0
  10. deer/drivers/gemini_driver.py +62 -0
  11. deer/drivers/ollama_driver.py +69 -0
  12. deer/executor/__init__.py +1 -0
  13. deer/executor/executor.py +168 -0
  14. deer/executor/logic.py +75 -0
  15. deer/executor/logic_secure.py +102 -0
  16. deer/main.py +71 -0
  17. deer/planner/__init__.py +1 -0
  18. deer/planner/planner.py +71 -0
  19. deer/prompts/__init__.py +6 -0
  20. deer/prompts/error_explain.py +30 -0
  21. deer/prompts/goal_improvement.py +65 -0
  22. deer/prompts/goal_validation.py +31 -0
  23. deer/prompts/humanizer.py +20 -0
  24. deer/prompts/planner.py +92 -0
  25. deer/prompts/response_improvement.py +23 -0
  26. deer/schema/__init__.py +2 -0
  27. deer/schema/io.py +40 -0
  28. deer/schema/plan.py +75 -0
  29. deer/tools/__init__.py +4 -0
  30. deer/tools/base.py +141 -0
  31. deer/tools/builtin/__init__.py +3 -0
  32. deer/tools/builtin/file_manager.py +136 -0
  33. deer/tools/builtin/git_manager.py +67 -0
  34. deer/tools/builtin/search_manager.py +123 -0
  35. deer/tools/decorators.py +127 -0
  36. deer/tools/registry.py +114 -0
  37. deer/tracing/__init__.py +2 -0
  38. deer/tracing/logging_config.py +20 -0
  39. deer/tracing/store.py +21 -0
  40. deer/utils/__init__.py +0 -0
  41. deer/utils/console.py +11 -0
  42. deer/utils/plots/__init__.py +7 -0
  43. deer/utils/plots/plot_traces.py +1034 -0
  44. deer/validator/__init__.py +1 -0
  45. deer/validator/plan_validator.py +23 -0
  46. deer/validator/rules.py +98 -0
  47. deer_agent_framework-0.0.dist-info/METADATA +163 -0
  48. deer_agent_framework-0.0.dist-info/RECORD +52 -0
  49. deer_agent_framework-0.0.dist-info/WHEEL +5 -0
  50. deer_agent_framework-0.0.dist-info/entry_points.txt +2 -0
  51. deer_agent_framework-0.0.dist-info/licenses/LICENSE +24 -0
  52. deer_agent_framework-0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,56 @@
1
+ from typing import Protocol, Type, TypeVar
2
+ import re
3
+ import json
4
+ import urllib.request
5
+ import urllib.error
6
+ from typing import TypeVar, Type, Optional, Any
7
+ import logging
8
+
9
+ from pydantic import BaseModel
10
+
11
+ # T represents any class inheriting from BaseModel
12
+ T = TypeVar("T", bound=BaseModel)
13
+ logger = logging.getLogger("DEER-LLM")
14
+
15
+
16
+ class LLMDriver(Protocol):
17
+ """Contract for drivers supporting Architectural Brilliance."""
18
+ temperature_json = 0.0
19
+ top_p = 1.0
20
+
21
+ def __init__(self, model_name: str):
22
+ self.model_name = model_name
23
+
24
+ def _send_post_request(self, url: str, payload: dict, headers: Optional[dict] = None) -> dict:
25
+ """Centralized helper to handle HTTP POST requests for all drivers."""
26
+ if headers is None:
27
+ headers = {"Content-Type": "application/json"}
28
+ else:
29
+ headers.setdefault("Content-Type", "application/json")
30
+
31
+ data = json.dumps(payload).encode("utf-8")
32
+ req = urllib.request.Request(url, data=data, headers=headers)
33
+
34
+ try:
35
+ with urllib.request.urlopen(req) as response:
36
+ return json.loads(response.read().decode("utf-8"))
37
+ except urllib.error.HTTPError as e:
38
+ error_body = e.read().decode("utf-8")
39
+ logger.error(f"HTTP Error {e.code}: {error_body}")
40
+ raise RuntimeError(f"API request failed with status {e.code}: {error_body}")
41
+ except urllib.error.URLError as e:
42
+ logger.error(f"URL Error: {e.reason}")
43
+ raise RuntimeError(f"Failed to reach server: {e.reason}")
44
+
45
+ def generate_text(self, prompt: str) -> str:
46
+ raise NotImplementedError("Subclasses must implement generate_text")
47
+
48
+ def generate_json(self, prompt: str, response_model: Optional[Type[T]] = None) -> T:
49
+ raise NotImplementedError("Subclasses must implement generate_json")
50
+
51
+ def extract_json(self, text: str) -> dict:
52
+ text = text.strip()
53
+ text = re.sub(r"^```(?:json)?\s*", "", text)
54
+ text = re.sub(r"^json\s*", "", text)
55
+ text = re.sub(r"\s*```$", "", text)
56
+ return json.loads(text)
@@ -0,0 +1,62 @@
1
+ import json
2
+ import os
3
+ from typing import TypeVar, Type, Optional
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from .base_driver import LLMDriver, logger
8
+
9
+ T = TypeVar("T", bound=BaseModel)
10
+
11
+
12
+ class GeminiDriver(LLMDriver):
13
+ def __init__(self, model_name: str, api_version: str = "v1beta"):
14
+ super().__init__(model_name)
15
+ self.api_key = os.getenv("GEMINI_API_KEY")
16
+ if not self.api_key:
17
+ raise ValueError("GEMINI_API_KEY environment variable is not set.")
18
+
19
+ # Setup the definitive REST endpoint for Gemini
20
+ self.url = f"https://generativelanguage.googleapis.com/{api_version}/models/{self.model_name}:generateContent?key={self.api_key}"
21
+
22
+ def __repr__(self) -> str:
23
+ return "Gemini"
24
+
25
+ def generate_text(self, prompt: str) -> str:
26
+ logger.debug(f"Generating text with model {self.model_name} and prompt: {prompt}")
27
+
28
+ payload = {
29
+ "contents": [{"parts": [{"text": prompt}]}],
30
+ "generationConfig": {"responseMimeType": "text/plain"}
31
+ }
32
+
33
+ try:
34
+ response_json = self._send_post_request(self.url, payload)
35
+ return response_json["candidates"][0]["content"]["parts"][0]["text"]
36
+ except (KeyError, IndexError) as e:
37
+ logger.error(f"Unexpected response structure from Gemini API: {e}")
38
+ raise RuntimeError("Failed to parse text from Gemini API response.")
39
+
40
+ def generate_json(self, prompt: str, response_model: Optional[Type[T]] = None) -> T:
41
+ logger.debug(f"Generating JSON with model {self.model_name} and prompt: {prompt}")
42
+
43
+ payload = {
44
+ "contents": [{"parts": [{"text": prompt}]}],
45
+ "generationConfig": {"responseMimeType": "application/json"}
46
+ }
47
+
48
+ if response_model:
49
+ payload["generationConfig"]["responseSchema"] = response_model.model_json_schema()
50
+
51
+ try:
52
+ response_json = self._send_post_request(self.url, payload)
53
+ response_text = response_json["candidates"][0]["content"]["parts"][0]["text"]
54
+ data = json.loads(response_text)
55
+ except (KeyError, IndexError, json.JSONDecodeError) as e:
56
+ logger.error(f"Error parsing JSON response from Gemini: {e}")
57
+ raise RuntimeError(f"Error processing JSON output from Gemini: {e}")
58
+
59
+ if response_model:
60
+ data = response_model.model_validate(data)
61
+
62
+ return data
@@ -0,0 +1,69 @@
1
+ import json
2
+ from typing import TypeVar, Type, Optional
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from .base_driver import LLMDriver, logger
7
+
8
+ T = TypeVar("T", bound=BaseModel)
9
+
10
+
11
+ class OllamaDriver(LLMDriver):
12
+ def __init__(self, model_name: str, host: str = "http://localhost:11434"):
13
+ super().__init__(model_name)
14
+ self.base_url = host.rstrip("/")
15
+ self.url = f"{self.base_url}/api/generate"
16
+
17
+ # Default fallback values for generation options if not defined externally
18
+ self.temperature_json = getattr(self, "temperature_json", 0.2)
19
+ self.top_p = getattr(self, "top_p", 0.9)
20
+
21
+ def __repr__(self) -> str:
22
+ return "Ollama"
23
+
24
+ def generate_text(self, prompt: str) -> str:
25
+ logger.debug(f"Generating text with model {self.model_name} and prompt: {prompt}")
26
+
27
+ payload = {
28
+ "model": self.model_name,
29
+ "prompt": prompt,
30
+ "stream": False
31
+ }
32
+
33
+ response_json = self._send_post_request(self.url, payload)
34
+ return response_json.get("response", "")
35
+
36
+ def generate_json(self, prompt: str, response_model: Optional[Type[T]] = None) -> T:
37
+ logger.debug(f"Generating JSON with model {self.model_name} and prompt: {prompt}")
38
+
39
+ payload = {
40
+ "model": self.model_name,
41
+ "prompt": prompt,
42
+ "stream": False,
43
+ "options": {
44
+ "temperature": self.temperature_json,
45
+ "top_p": self.top_p,
46
+ }
47
+ }
48
+
49
+ if response_model:
50
+ payload["format"] = response_model.model_json_schema()
51
+ else:
52
+ payload["format"] = "json"
53
+
54
+ response_json = self._send_post_request(self.url, payload)
55
+ response_text = response_json.get("response", "")
56
+
57
+ try:
58
+ data = json.loads(response_text)
59
+ except json.JSONDecodeError as e:
60
+ if hasattr(self, "extract_json"):
61
+ data = self.extract_json(response_text)
62
+ else:
63
+ logger.error(f"Failed to decode JSON from response: {e}")
64
+ raise
65
+
66
+ if response_model:
67
+ data = response_model.model_validate(data)
68
+
69
+ return data
@@ -0,0 +1 @@
1
+ from .executor import Executor
@@ -0,0 +1,168 @@
1
+ import logging
2
+ import re
3
+ from typing import Any, Dict
4
+
5
+ from deer.schema.io import AgentOutput, StepTrace
6
+ from deer.schema.plan import Plan
7
+ from deer.tracing.store import TraceStore
8
+ from deer.tools.registry import ToolRegistry
9
+ from .logic import evaluate_logic
10
+
11
+ logger = logging.getLogger("DEER")
12
+
13
+
14
+ class Executor:
15
+ def __init__(self, registry: ToolRegistry) -> None:
16
+ self.registry = registry
17
+ self.trace_store = TraceStore()
18
+
19
+ def execute(self, plan: Plan, payload: {}) -> AgentOutput:
20
+ self.trace_store.reset()
21
+ context: Dict[str, Any] = {}
22
+ last_output: Any = None
23
+
24
+ for step in plan.steps:
25
+ value = self.resolve_input(step.input_from, step.id, payload, context)
26
+ logger.debug(f"Executing step {step.id} with input '{step.input_from}'")
27
+
28
+ try:
29
+ if step.logic is not None:
30
+ output = self.execute_logic(
31
+ logic=step.logic,
32
+ input_value=value,
33
+ params=step.params or {},
34
+ context=context,
35
+ )
36
+ trace_tool = "logic"
37
+
38
+ if step.tool is not None:
39
+ params = self.resolve_params(step.params or {}, value, context)
40
+ output = self.execute_tool(
41
+ tool_name=step.tool,
42
+ params=params,
43
+ )
44
+ trace_tool = step.tool
45
+
46
+ context[step.id] = output
47
+ last_output = output
48
+
49
+ self.trace_store.append(
50
+ StepTrace(
51
+ step_id=step.id,
52
+ tool=trace_tool,
53
+ input=value,
54
+ output=output,
55
+ )
56
+ )
57
+ except Exception as ex:
58
+ self.trace_store.append(
59
+ StepTrace(
60
+ step_id=step.id,
61
+ tool=step.tool or "logic",
62
+ input=value,
63
+ output=None,
64
+ error=str(ex),
65
+ )
66
+ )
67
+ raise
68
+
69
+ return AgentOutput(result=last_output, trace=self.trace_store.get_trace())
70
+
71
+ def resolve_input(
72
+ self,
73
+ input_from: str | None,
74
+ step_id: str,
75
+ payload: {},
76
+ context: Dict[str, Any],
77
+ ) -> Any:
78
+ if input_from is None:
79
+ return payload
80
+
81
+ if input_from not in context:
82
+ raise ValueError(
83
+ f"Unresolved dependency: '{input_from}' required by step '{step_id}'."
84
+ )
85
+
86
+ output = context[input_from]
87
+
88
+ # # Automatically unwrap single-key dictionaries to simplify logic and tool usage.
89
+ # # This ensures 'input' refers directly to the value (e.g., file content)
90
+ # # instead of the wrapper dictionary.
91
+ # if isinstance(output, dict) and len(output) == 1:
92
+ # return next(iter(output.values()))
93
+
94
+ return output
95
+
96
+ def resolve_params(
97
+ self,
98
+ params: Dict[str, Any],
99
+ input_value: Any,
100
+ context: Dict[str, Any],
101
+ ) -> Dict[str, Any]:
102
+ """Resolves placeholders like 'input' or step IDs in parameters."""
103
+ resolved = {}
104
+ for k, v in params.items():
105
+ if v == "input":
106
+ resolved[k] = input_value
107
+ elif isinstance(v, str) and v in context:
108
+ resolved[k] = context[v]
109
+ else:
110
+ resolved[k] = v
111
+ return resolved
112
+
113
+ def execute_tool(
114
+ self,
115
+ tool_name: str,
116
+ params: Dict[str, Any],
117
+ ) -> Any:
118
+ tool = self.registry.get(tool_name)
119
+ input_params = tool.validate_input(params)
120
+ output = tool.run(params=input_params)
121
+ logger.info(f"Executed tool: {tool_name}")
122
+ logger.debug(f"Tool {tool_name} output: {output}")
123
+ return tool.validate_output(output)
124
+
125
+ def execute_logic(
126
+ self,
127
+ logic: str,
128
+ input_value: Any,
129
+ params: Dict[str, Any],
130
+ context: Dict[str, Any],
131
+ ) -> Any:
132
+ # logic = self.fix_multiline_strings(logic)
133
+ logger.debug(f"Executing logic: {logic}")
134
+ return evaluate_logic(
135
+ logic,
136
+ input_value=input_value,
137
+ params=params,
138
+ context=context,
139
+ )
140
+
141
+ #
142
+ # def fix_multiline_strings(self, code: str) -> str:
143
+ # pattern = r'=\s*"([^"\n]*\n(?:.*\n)*?.*?)"'
144
+ #
145
+ # def replacer(match):
146
+ # content = match.group(1)
147
+ # return '= """' + content + '"""'
148
+ #
149
+ # return re.sub(pattern, replacer, code, flags=re.MULTILINE)
150
+
151
+ # def normalize_logic(self, logic: str) -> str:
152
+ # logic = logic.strip()
153
+ #
154
+ # # multiline JSON escapes
155
+ # logic = logic.replace("\\n", "\n")
156
+ #
157
+ # # Caso: string completo serializado
158
+ # try:
159
+ # parsed = json.loads(logic)
160
+ # if isinstance(parsed, str):
161
+ # logic = parsed
162
+ # except Exception:
163
+ # pass
164
+ #
165
+ # # Caso: código Python contaminado con escaping JSON
166
+ # logic = logic.replace('\\"', '"')
167
+ #
168
+ # return logic
deer/executor/logic.py ADDED
@@ -0,0 +1,75 @@
1
+ import ast
2
+ from typing import Any, Dict, Mapping
3
+ from .logic_secure import ALLOWED_NODE_TYPES, SAFE_GLOBALS
4
+
5
+
6
+ class UnsafeLogicError(ValueError):
7
+ """Raised when the code contains disallowed or dangerous syntax."""
8
+
9
+
10
+ def _is_safe_variable_name(name: str) -> bool:
11
+ return name.isidentifier() and not name.startswith("_")
12
+
13
+
14
+ def evaluate_logic(
15
+ logic_code: str,
16
+ *,
17
+ input_value: Any,
18
+ params: Mapping[str, Any],
19
+ context: Mapping[str, Any],
20
+ ) -> Any:
21
+ """Safely executes Python code and returns the value of 'result'."""
22
+
23
+ # 1. Parse and Validate AST
24
+ try:
25
+ tree = ast.parse(logic_code, mode="exec")
26
+ except SyntaxError as e:
27
+ raise UnsafeLogicError(f"Syntax error in logic: {e}")
28
+
29
+ _validate_ast(tree)
30
+
31
+ # 2. Prepare Scope
32
+ local_scope = {
33
+ "input": input_value,
34
+ "params": dict(params),
35
+ "context": dict(context),
36
+ }
37
+
38
+ # Flatten dictionaries for direct access
39
+ if isinstance(input_value, dict):
40
+ for key, value in input_value.items():
41
+ if _is_safe_variable_name(key):
42
+ local_scope[key] = value
43
+
44
+ for key, value in params.items():
45
+ if _is_safe_variable_name(key):
46
+ local_scope[key] = value
47
+
48
+ # 3. Execute
49
+ try:
50
+ compiled = compile(tree, filename="<step.logic>", mode="exec")
51
+ exec(compiled, SAFE_GLOBALS, local_scope)
52
+ except Exception as e:
53
+ raise RuntimeError(f"Runtime error in logic execution: {e}")
54
+
55
+ if "result" not in local_scope:
56
+ raise UnsafeLogicError("Logic must assign a value to the 'result' variable.")
57
+
58
+ return local_scope["result"]
59
+
60
+
61
+ def _validate_ast(tree: ast.AST) -> None:
62
+ for node in ast.walk(tree):
63
+ if not isinstance(node, ALLOWED_NODE_TYPES):
64
+ raise UnsafeLogicError(f"Disallowed syntax: {type(node).__name__}")
65
+
66
+ # Do not allow access to private attributes (._secret)
67
+ if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
68
+ raise UnsafeLogicError("Access to private attributes is not allowed.")
69
+
70
+ # Do not allow assignments to protected variables
71
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store):
72
+ if node.id.startswith("_") or node.id in SAFE_GLOBALS:
73
+ raise UnsafeLogicError(
74
+ f"Cannot assign a value to protected variable: {node.id}"
75
+ )
@@ -0,0 +1,102 @@
1
+ import ast
2
+ import math
3
+
4
+ # Allowed nodes to support mathematical logic, strings, and basic assignments
5
+ ALLOWED_NODE_TYPES = (
6
+ # --- Basic Structure ---
7
+ ast.Module, # Root container
8
+ ast.Expr, # Standalone expressions
9
+ ast.Assign, # Variable assignment (e.g., x = 10)
10
+ ast.Load, # Load variable value
11
+ ast.Store, # Store variable value
12
+ ast.Name, # Variable names
13
+ ast.Constant, # Literal values (numbers, strings, None, True, False)
14
+ # --- Mathematical Operations ---
15
+ ast.BinOp, # Binary operations (+, -, *, /)
16
+ ast.UnaryOp, # Unary operations (not, -x)
17
+ ast.Add,
18
+ ast.Sub,
19
+ ast.Mult,
20
+ ast.Div,
21
+ ast.FloorDiv,
22
+ ast.Mod,
23
+ ast.Pow,
24
+ ast.USub,
25
+ ast.UAdd,
26
+ # --- Logic and Comparison ---
27
+ ast.Compare, # Comparisons (==, !=, <, etc.)
28
+ ast.Eq,
29
+ ast.NotEq,
30
+ ast.Lt,
31
+ ast.LtE,
32
+ ast.Gt,
33
+ ast.GtE,
34
+ ast.In,
35
+ ast.NotIn,
36
+ ast.BoolOp, # Logical operators (and, or)
37
+ ast.And,
38
+ ast.Or,
39
+ ast.IfExp, # Ternary expressions (x if condition else y)
40
+ # --- Data Structures and Access ---
41
+ ast.List, # [1, 2, 3]
42
+ ast.Dict, # {'a': 1}
43
+ ast.Tuple, # (1, 2)
44
+ ast.Set, # {1, 2}
45
+ ast.Subscript, # Indexed/key access: list[0] or dict['k']
46
+ ast.Slice, # Slicing: list[1:5]
47
+ ast.Attribute, # Attribute access: object.property (required for Pydantic)
48
+ # --- Strings and Formatting ---
49
+ ast.JoinedStr, # f-strings
50
+ ast.FormattedValue, # Values inside f-strings
51
+ # --- Calls and Functions ---
52
+ ast.Call, # Allow calling functions from _SAFE_GLOBALS
53
+ ast.keyword, # Keyword arguments: func(x=1)
54
+ # --- Advanced Safe Flexibility ---
55
+ ast.ListComp, # [x for x in items] (useful for data transformation)
56
+ ast.DictComp, # {k: v for k, v in items}
57
+ ast.comprehension, # Internal comprehension logic
58
+ )
59
+
60
+ SAFE_GLOBALS = {
61
+ # --- Sandbox Privacy ---
62
+ "__builtins__": {}, # Blocks access to dangerous functions like eval, exec, open
63
+ # --- Mathematical Constants ---
64
+ "pi": math.pi,
65
+ "e": math.e,
66
+ # --- Arithmetic and Numbers ---
67
+ "abs": abs,
68
+ "min": min,
69
+ "max": max,
70
+ "round": round,
71
+ "pow": pow,
72
+ "sum": sum,
73
+ "sqrt": math.sqrt,
74
+ "log": math.log,
75
+ "exp": math.exp,
76
+ "ceil": math.ceil,
77
+ "floor": math.floor,
78
+ "sin": math.sin,
79
+ "cos": math.cos,
80
+ "tan": math.tan,
81
+ # --- Type Conversion (Casting) ---
82
+ "str": str,
83
+ "int": int,
84
+ "float": float,
85
+ "bool": bool,
86
+ "list": list,
87
+ "dict": dict,
88
+ "set": set,
89
+ "tuple": tuple,
90
+ # --- Collection Processing ---
91
+ "len": len,
92
+ "range": range,
93
+ "enumerate": enumerate,
94
+ "zip": zip,
95
+ "sorted": sorted, # Allows safe list sorting
96
+ "reversed": reversed,
97
+ # --- Advanced Logic ---
98
+ "any": any, # Is any value True?
99
+ "all": all, # Are all values True?
100
+ "filter": filter,
101
+ "map": map,
102
+ }
deer/main.py ADDED
@@ -0,0 +1,71 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ from rich.text import Text
5
+
6
+ from deer.builtins import agents
7
+ from deer.utils.console import console, error, info
8
+ from deer.drivers import drivers_parser
9
+
10
+
11
+ def title():
12
+ console.print()
13
+ console.print(
14
+ Text(
15
+ "DEER",
16
+ style="bold cyan",
17
+ )
18
+ )
19
+ console.print(
20
+ "Deterministic Executable Engine for Runtime Agents",
21
+ style="dim",
22
+ )
23
+ console.print()
24
+
25
+
26
+ def agents_list():
27
+ console.print()
28
+ console.print("[bold]Available agents[/bold]")
29
+ for name in agents:
30
+ console.print(f" • [green]{name}[/green]")
31
+ console.print()
32
+
33
+
34
+ def example():
35
+ console.print("[dim]Example:[/dim] deer python_manager")
36
+
37
+
38
+ def main():
39
+ args = drivers_parser.parse_args()
40
+
41
+ title()
42
+
43
+ if selected_agent := args.agent:
44
+ if selected_agent in agents:
45
+ info(f"Launching agent '{selected_agent}'")
46
+
47
+ command = [
48
+ sys.executable,
49
+ agents[selected_agent],
50
+ ]
51
+
52
+ if args.backend:
53
+ command.extend(["--backend", args.backend])
54
+
55
+ if args.model:
56
+ command.extend(["--model", args.model])
57
+
58
+ result = subprocess.run(command)
59
+ sys.exit(result.returncode)
60
+
61
+ error(f"Unknown agent '{selected_agent}'")
62
+ agents_list()
63
+ sys.exit(1)
64
+
65
+ error("No agent selected")
66
+ example()
67
+ agents_list()
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
@@ -0,0 +1 @@
1
+ from .planner import Planner
@@ -0,0 +1,71 @@
1
+ import json
2
+
3
+ from deer.prompts import PLANNER_PROMPT, GOAL_IMPROVEMENT_PROMPT
4
+ from deer.schema.io import AgentInput
5
+ from deer.tools.registry import ToolRegistry
6
+ from deer.drivers.base_driver import LLMDriver
7
+ from deer.schema.plan import Plan
8
+
9
+
10
+ class Planner:
11
+
12
+ def __init__(
13
+ self,
14
+ identity: str,
15
+ driver: LLMDriver | None = None,
16
+ registry: ToolRegistry | None = None,
17
+ format_response: str = "plain",
18
+ improve_goal: bool = True,
19
+ ):
20
+ self.llm_driver = driver
21
+ self.registry = registry
22
+ self.identity = identity
23
+ self.format_response = format_response
24
+ self.improve_goal = improve_goal
25
+
26
+ self.goal = ""
27
+
28
+ def plan(
29
+ self,
30
+ agent_input: AgentInput,
31
+ feedback={},
32
+ include_state_modifying: bool = True,
33
+ ) -> Plan:
34
+ if self.improve_goal:
35
+ self.run_improve_goal(agent_input=agent_input)
36
+ plan_prompt = self.build_prompt(
37
+ agent_input,
38
+ feedback,
39
+ include_state_modifying=include_state_modifying,
40
+ )
41
+ plan = self.llm_driver.generate_json(plan_prompt, response_model=Plan)
42
+ return plan
43
+
44
+ def run_improve_goal(self, agent_input: AgentInput):
45
+ goal_prompt = GOAL_IMPROVEMENT_PROMPT.format(
46
+ identity=self.identity,
47
+ goal=agent_input.goal,
48
+ payload=json.dumps(agent_input.payload, ensure_ascii=False),
49
+ )
50
+ improved_goal = self.llm_driver.generate_text(goal_prompt)
51
+ agent_input.goal = improved_goal
52
+ self.goal = improved_goal
53
+
54
+ def build_prompt(
55
+ self,
56
+ agent_input: AgentInput,
57
+ feedback={},
58
+ include_state_modifying=True,
59
+ ) -> str:
60
+ # As usable data must be JSON consistent.
61
+ agent_input.payload.update(feedback)
62
+ payload = json.dumps(agent_input.payload, ensure_ascii=False)
63
+
64
+ planner_prompt = PLANNER_PROMPT.format(
65
+ identity=self.identity,
66
+ goal=agent_input.goal,
67
+ payload=payload,
68
+ tools=self.registry.describe(include_state_modifying),
69
+ format_response=self.format_response,
70
+ )
71
+ return planner_prompt
@@ -0,0 +1,6 @@
1
+ from .error_explain import ERROR_EXPLAIN_PROMPT
2
+ from .goal_improvement import GOAL_IMPROVEMENT_PROMPT
3
+ from .humanizer import HUMANIZER_PROMPT
4
+ from .planner import PLANNER_PROMPT
5
+ from .response_improvement import RESPONSE_IMPROVEMENT_PROMPT
6
+ from .goal_validation import VERIFIER_JUDGE_PROMPT, GOAL_VERIFIER_PROMPT