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.
- deer/__init__.py +36 -0
- deer/builtins/__init__.py +7 -0
- deer/builtins/python_manager/agent.py +29 -0
- deer/builtins/python_manager/tools.py +54 -0
- deer/core/__init__.py +1 -0
- deer/core/agent.py +463 -0
- deer/core/ui.py +31 -0
- deer/drivers/__init__.py +66 -0
- deer/drivers/base_driver.py +56 -0
- deer/drivers/gemini_driver.py +62 -0
- deer/drivers/ollama_driver.py +69 -0
- deer/executor/__init__.py +1 -0
- deer/executor/executor.py +168 -0
- deer/executor/logic.py +75 -0
- deer/executor/logic_secure.py +102 -0
- deer/main.py +71 -0
- deer/planner/__init__.py +1 -0
- deer/planner/planner.py +71 -0
- deer/prompts/__init__.py +6 -0
- deer/prompts/error_explain.py +30 -0
- deer/prompts/goal_improvement.py +65 -0
- deer/prompts/goal_validation.py +31 -0
- deer/prompts/humanizer.py +20 -0
- deer/prompts/planner.py +92 -0
- deer/prompts/response_improvement.py +23 -0
- deer/schema/__init__.py +2 -0
- deer/schema/io.py +40 -0
- deer/schema/plan.py +75 -0
- deer/tools/__init__.py +4 -0
- deer/tools/base.py +141 -0
- deer/tools/builtin/__init__.py +3 -0
- deer/tools/builtin/file_manager.py +136 -0
- deer/tools/builtin/git_manager.py +67 -0
- deer/tools/builtin/search_manager.py +123 -0
- deer/tools/decorators.py +127 -0
- deer/tools/registry.py +114 -0
- deer/tracing/__init__.py +2 -0
- deer/tracing/logging_config.py +20 -0
- deer/tracing/store.py +21 -0
- deer/utils/__init__.py +0 -0
- deer/utils/console.py +11 -0
- deer/utils/plots/__init__.py +7 -0
- deer/utils/plots/plot_traces.py +1034 -0
- deer/validator/__init__.py +1 -0
- deer/validator/plan_validator.py +23 -0
- deer/validator/rules.py +98 -0
- deer_agent_framework-0.0.dist-info/METADATA +163 -0
- deer_agent_framework-0.0.dist-info/RECORD +52 -0
- deer_agent_framework-0.0.dist-info/WHEEL +5 -0
- deer_agent_framework-0.0.dist-info/entry_points.txt +2 -0
- deer_agent_framework-0.0.dist-info/licenses/LICENSE +24 -0
- deer_agent_framework-0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from deer.tools import ToolProvider, tool, Return
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitManagerError(ValueError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CommandOut = Return(stdout=str, stderr=str, returncode=int)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class GitManager(ToolProvider):
|
|
17
|
+
|
|
18
|
+
def git(self, path: str, *args: str | int) -> CommandOut:
|
|
19
|
+
quoted_args = " ".join(shlex.quote(str(arg)) for arg in args)
|
|
20
|
+
return self.run_command(f"git {quoted_args}", cwd=path)
|
|
21
|
+
|
|
22
|
+
@tool()
|
|
23
|
+
def git_status(self, path: str) -> CommandOut:
|
|
24
|
+
"""Returns the short git status for a repository path inside the jail."""
|
|
25
|
+
return self.git(path, "status", "--short")
|
|
26
|
+
|
|
27
|
+
@tool()
|
|
28
|
+
def git_current_branch(self, path: str) -> CommandOut:
|
|
29
|
+
"""Returns the current branch name for a repository path inside the jail."""
|
|
30
|
+
return self.git(path, "branch", "--show-current")
|
|
31
|
+
|
|
32
|
+
@tool()
|
|
33
|
+
def git_log(self, path: str, max_count: int) -> CommandOut:
|
|
34
|
+
"""Returns the recent commit log for a repository path inside the jail."""
|
|
35
|
+
return self.git(
|
|
36
|
+
path, "log", "--oneline", "--decorate", f"--max-count={max_count}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@tool()
|
|
40
|
+
def git_diff(self, path: str, target: str) -> CommandOut:
|
|
41
|
+
"""Returns the unstaged diff for a target path inside a repository."""
|
|
42
|
+
return self.git(path, "diff", "--", target)
|
|
43
|
+
|
|
44
|
+
@tool()
|
|
45
|
+
def git_staged_diff(self, path: str, target: str) -> CommandOut:
|
|
46
|
+
"""Returns the staged diff for a target path inside a repository."""
|
|
47
|
+
return self.git(path, "diff", "--cached", "--", target)
|
|
48
|
+
|
|
49
|
+
@tool()
|
|
50
|
+
def git_show(self, path: str, revision: str) -> CommandOut:
|
|
51
|
+
"""Returns details for a git revision inside a repository path."""
|
|
52
|
+
return self.git(path, "show", "--stat", "--patch", revision)
|
|
53
|
+
|
|
54
|
+
@tool(modifies_state=True)
|
|
55
|
+
def git_add(self, path: str, target: str) -> CommandOut:
|
|
56
|
+
"""Stages a target path inside a repository."""
|
|
57
|
+
return self.git(path, "add", "--", target)
|
|
58
|
+
|
|
59
|
+
@tool(modifies_state=True)
|
|
60
|
+
def git_commit(self, path: str, message: str) -> CommandOut:
|
|
61
|
+
"""Creates a commit in a repository path with the provided message."""
|
|
62
|
+
return self.git(path, "commit", "-m", message)
|
|
63
|
+
|
|
64
|
+
@tool(modifies_state=True)
|
|
65
|
+
def git_restore(self, path: str, target: str) -> CommandOut:
|
|
66
|
+
"""Restores unstaged changes for a target path inside a repository."""
|
|
67
|
+
return self.git(path, "restore", "--", target)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from deer.tools import ToolProvider, tool, Return
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchManagerError(ValueError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CommandOut = Return(stdout=str, stderr=str, returncode=int)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SearchManager(ToolProvider):
|
|
17
|
+
|
|
18
|
+
def rg(self, path: str, *args: str | int) -> CommandOut:
|
|
19
|
+
quoted_args = " ".join(shlex.quote(str(arg)) for arg in args)
|
|
20
|
+
return self.run_command(f"rg {quoted_args}", cwd=path)
|
|
21
|
+
|
|
22
|
+
@tool()
|
|
23
|
+
def search_text(self, path: str, query: str) -> CommandOut:
|
|
24
|
+
"""Searches for literal text under a path inside the jail."""
|
|
25
|
+
return self.rg(
|
|
26
|
+
path,
|
|
27
|
+
"--fixed-strings",
|
|
28
|
+
"--line-number",
|
|
29
|
+
"--column",
|
|
30
|
+
"--no-heading",
|
|
31
|
+
query,
|
|
32
|
+
".",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@tool()
|
|
36
|
+
def search_regex(self, path: str, pattern: str) -> CommandOut:
|
|
37
|
+
"""Searches for a regex pattern under a path inside the jail."""
|
|
38
|
+
return self.rg(
|
|
39
|
+
path,
|
|
40
|
+
"--line-number",
|
|
41
|
+
"--column",
|
|
42
|
+
"--no-heading",
|
|
43
|
+
pattern,
|
|
44
|
+
".",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@tool()
|
|
48
|
+
def search_text_ignore_case(self, path: str, query: str) -> CommandOut:
|
|
49
|
+
"""Searches for literal text case-insensitively under a path inside the jail."""
|
|
50
|
+
return self.rg(
|
|
51
|
+
path,
|
|
52
|
+
"--fixed-strings",
|
|
53
|
+
"--ignore-case",
|
|
54
|
+
"--line-number",
|
|
55
|
+
"--column",
|
|
56
|
+
"--no-heading",
|
|
57
|
+
query,
|
|
58
|
+
".",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@tool()
|
|
62
|
+
def search_regex_ignore_case(self, path: str, pattern: str) -> CommandOut:
|
|
63
|
+
"""Searches for a regex pattern case-insensitively under a path inside the jail."""
|
|
64
|
+
return self.rg(
|
|
65
|
+
path,
|
|
66
|
+
"--ignore-case",
|
|
67
|
+
"--line-number",
|
|
68
|
+
"--column",
|
|
69
|
+
"--no-heading",
|
|
70
|
+
pattern,
|
|
71
|
+
".",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@tool()
|
|
75
|
+
def find_files(self, path: str, pattern: str) -> CommandOut:
|
|
76
|
+
"""Finds files matching a glob pattern under a path inside the jail."""
|
|
77
|
+
return self.rg(path, "--files", "-g", pattern, ".")
|
|
78
|
+
|
|
79
|
+
@tool()
|
|
80
|
+
def search_file_names(self, path: str, pattern: str) -> CommandOut:
|
|
81
|
+
"""Finds files whose names contain text under a path inside the jail."""
|
|
82
|
+
return self.rg(path, "--files", "-g", f"*{pattern}*", ".")
|
|
83
|
+
|
|
84
|
+
@tool()
|
|
85
|
+
def list_files(self, path: str) -> CommandOut:
|
|
86
|
+
"""Lists files under a path inside the jail."""
|
|
87
|
+
return self.rg(path, "--files", ".")
|
|
88
|
+
|
|
89
|
+
@tool()
|
|
90
|
+
def search_by_extension(self, path: str, extension: str) -> CommandOut:
|
|
91
|
+
"""Lists files with an extension under a path inside the jail."""
|
|
92
|
+
normalized_extension = extension.lstrip(".")
|
|
93
|
+
return self.rg(path, "--files", "-g", f"*.{normalized_extension}", ".")
|
|
94
|
+
|
|
95
|
+
@tool()
|
|
96
|
+
def search_text_in_files(self, path: str, query: str, glob: str) -> CommandOut:
|
|
97
|
+
"""Searches for literal text under a path, restricted to files matching a glob."""
|
|
98
|
+
return self.rg(
|
|
99
|
+
path,
|
|
100
|
+
"--fixed-strings",
|
|
101
|
+
"--line-number",
|
|
102
|
+
"--column",
|
|
103
|
+
"--no-heading",
|
|
104
|
+
"-g",
|
|
105
|
+
glob,
|
|
106
|
+
query,
|
|
107
|
+
".",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@tool()
|
|
111
|
+
def files_with_matches(self, path: str, query: str) -> CommandOut:
|
|
112
|
+
"""Lists files containing literal text under a path inside the jail."""
|
|
113
|
+
return self.rg(path, "--fixed-strings", "--files-with-matches", query, ".")
|
|
114
|
+
|
|
115
|
+
@tool()
|
|
116
|
+
def files_without_matches(self, path: str, query: str) -> CommandOut:
|
|
117
|
+
"""Lists files that do not contain literal text under a path inside the jail."""
|
|
118
|
+
return self.rg(path, "--fixed-strings", "--files-without-match", query, ".")
|
|
119
|
+
|
|
120
|
+
@tool()
|
|
121
|
+
def count_matches(self, path: str, query: str) -> CommandOut:
|
|
122
|
+
"""Counts literal text matches per file under a path inside the jail."""
|
|
123
|
+
return self.rg(path, "--fixed-strings", "--count-matches", query, ".")
|
deer/tools/decorators.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import get_type_hints, Any, Callable
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from .base import Tool
|
|
8
|
+
|
|
9
|
+
_TOOL_METADATA_ATTR = "__deer_tool_metadata__"
|
|
10
|
+
_MISSING = object()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def tool(
|
|
14
|
+
*,
|
|
15
|
+
modifies_state: bool = False,
|
|
16
|
+
):
|
|
17
|
+
"""Mark an instance method as a deterministic tool."""
|
|
18
|
+
|
|
19
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
20
|
+
assert func.__doc__, f"Tool '{func.__name__}' must have a docstring annotation."
|
|
21
|
+
|
|
22
|
+
metadata = _build_tool_metadata(
|
|
23
|
+
func=func,
|
|
24
|
+
name=func.__name__,
|
|
25
|
+
description=func.__doc__,
|
|
26
|
+
modifies_state=modifies_state,
|
|
27
|
+
)
|
|
28
|
+
setattr(
|
|
29
|
+
func,
|
|
30
|
+
_TOOL_METADATA_ATTR,
|
|
31
|
+
metadata,
|
|
32
|
+
)
|
|
33
|
+
return func
|
|
34
|
+
# return staticmethod(func)
|
|
35
|
+
|
|
36
|
+
return decorator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class MethodTool(Tool):
|
|
41
|
+
"""Adapter that exposes a decorated method as a Tool instance."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
description: str
|
|
45
|
+
full_description: str
|
|
46
|
+
params_type: Any
|
|
47
|
+
return_type: Any
|
|
48
|
+
method: Callable[..., Any]
|
|
49
|
+
modifies_state: bool
|
|
50
|
+
|
|
51
|
+
def run(
|
|
52
|
+
self,
|
|
53
|
+
params: dict[str, Any] | None = None,
|
|
54
|
+
) -> Any:
|
|
55
|
+
return self.method(**(params or {}))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_tool_method(obj: Any) -> bool:
|
|
59
|
+
return hasattr(obj, _TOOL_METADATA_ATTR) and (
|
|
60
|
+
not obj.__self__.tools or obj.__name__ in obj.__self__.tools
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_tool_metadata(obj: Any) -> dict[str, Any]:
|
|
65
|
+
return getattr(obj, _TOOL_METADATA_ATTR)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _build_tool_metadata(
|
|
69
|
+
*,
|
|
70
|
+
func: Callable[..., Any],
|
|
71
|
+
name: str,
|
|
72
|
+
description: str,
|
|
73
|
+
modifies_state: bool,
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
if not name or not name.strip():
|
|
76
|
+
raise ValueError("Tool name cannot be empty.")
|
|
77
|
+
|
|
78
|
+
if not description or not description.strip():
|
|
79
|
+
raise ValueError("Tool description cannot be empty.")
|
|
80
|
+
|
|
81
|
+
signature = inspect.signature(func)
|
|
82
|
+
type_hints = get_type_hints(func)
|
|
83
|
+
|
|
84
|
+
return_type = type_hints.pop("return")
|
|
85
|
+
params_type = type_hints
|
|
86
|
+
|
|
87
|
+
if issubclass(return_type, BaseModel):
|
|
88
|
+
return_type = {
|
|
89
|
+
name: field.annotation for name, field in return_type.model_fields.items()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if return_type is _MISSING:
|
|
93
|
+
raise TypeError(
|
|
94
|
+
f"Tool '{name}' must declare a return type annotation on "
|
|
95
|
+
f"{func.__qualname__}."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
full_description = _build_description(
|
|
99
|
+
base_description=description.strip(),
|
|
100
|
+
params_type=params_type,
|
|
101
|
+
return_type=return_type,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"name": name,
|
|
106
|
+
"description": description.strip(),
|
|
107
|
+
"full_description": full_description,
|
|
108
|
+
"modifies_state": modifies_state,
|
|
109
|
+
"params_type": params_type,
|
|
110
|
+
"return_type": return_type,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _build_description(
|
|
115
|
+
*,
|
|
116
|
+
base_description: str,
|
|
117
|
+
params_type: Any,
|
|
118
|
+
return_type: Any,
|
|
119
|
+
) -> str:
|
|
120
|
+
|
|
121
|
+
return "\n".join(
|
|
122
|
+
[
|
|
123
|
+
base_description,
|
|
124
|
+
f"Parameters: {params_type}.",
|
|
125
|
+
f"Return: {return_type}.",
|
|
126
|
+
]
|
|
127
|
+
)
|
deer/tools/registry.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from typing import Any, Dict, Iterable
|
|
2
|
+
|
|
3
|
+
from .base import Tool
|
|
4
|
+
from .decorators import MethodTool, get_tool_metadata, is_tool_method
|
|
5
|
+
from deer.schema.io import Return
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ToolRegistry:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self._tools: Dict[str, Tool] = {}
|
|
11
|
+
self._providers: list = []
|
|
12
|
+
|
|
13
|
+
def register(self, *provider_tools: list):
|
|
14
|
+
for tool in provider_tools:
|
|
15
|
+
if isinstance(tool, Tool):
|
|
16
|
+
self._register_tool(tool)
|
|
17
|
+
else:
|
|
18
|
+
self._register_collection(tool)
|
|
19
|
+
|
|
20
|
+
def _register_tool(self, tool: Tool) -> None:
|
|
21
|
+
if not tool.name or not tool.name.strip():
|
|
22
|
+
raise ValueError("Tool name cannot be empty.")
|
|
23
|
+
|
|
24
|
+
self._tools[tool.name] = tool
|
|
25
|
+
|
|
26
|
+
def _register_collection(self, provider: Any) -> None:
|
|
27
|
+
self._providers.append(provider)
|
|
28
|
+
for attr_name in dir(provider):
|
|
29
|
+
|
|
30
|
+
if attr_name == "jail":
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
attr = getattr(provider, attr_name)
|
|
34
|
+
|
|
35
|
+
if not is_tool_method(attr):
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
metadata = get_tool_metadata(attr)
|
|
39
|
+
|
|
40
|
+
self.register(
|
|
41
|
+
MethodTool(
|
|
42
|
+
name=metadata["name"],
|
|
43
|
+
description=metadata["description"],
|
|
44
|
+
full_description=metadata["full_description"],
|
|
45
|
+
modifies_state=metadata["modifies_state"],
|
|
46
|
+
params_type=metadata["params_type"],
|
|
47
|
+
return_type=metadata["return_type"],
|
|
48
|
+
method=attr,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def get(self, name: str) -> Tool:
|
|
53
|
+
if not name or not name.strip():
|
|
54
|
+
raise KeyError("Tool name cannot be empty.")
|
|
55
|
+
|
|
56
|
+
if name not in self._tools:
|
|
57
|
+
raise KeyError(f"Tool not found: {name}")
|
|
58
|
+
|
|
59
|
+
return self._tools[name]
|
|
60
|
+
|
|
61
|
+
def has(self, name: str | None) -> bool:
|
|
62
|
+
if not name:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
return name in self._tools
|
|
66
|
+
|
|
67
|
+
def list_tools(self) -> Iterable[str]:
|
|
68
|
+
return self._tools.keys()
|
|
69
|
+
|
|
70
|
+
def providers(self):
|
|
71
|
+
return self._providers
|
|
72
|
+
|
|
73
|
+
def set_jail(self, jail_path):
|
|
74
|
+
self.jail_path = jail_path
|
|
75
|
+
for provider in self._providers:
|
|
76
|
+
provider.jail = jail_path
|
|
77
|
+
|
|
78
|
+
def describe(self, include_state_modifying=True, markdown=False) -> str:
|
|
79
|
+
if not self._tools:
|
|
80
|
+
return "- No tools are available."
|
|
81
|
+
|
|
82
|
+
lines = []
|
|
83
|
+
|
|
84
|
+
for tool in self._tools.values():
|
|
85
|
+
|
|
86
|
+
if not include_state_modifying and tool.modifies_state:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if markdown:
|
|
90
|
+
cont = tool.description.split('\n')[0]
|
|
91
|
+
lines.append(f" - `{tool.name}`: *{cont}*")
|
|
92
|
+
else:
|
|
93
|
+
cont = tool.full_description.replace("\n", "\n ")
|
|
94
|
+
lines.append(
|
|
95
|
+
f"- {tool.name}: {cont}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return "\n".join(lines)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class EchoTool(Tool):
|
|
102
|
+
name = "echo"
|
|
103
|
+
description = "Returns params['echo'] when provided; otherwise returns the input value unchanged."
|
|
104
|
+
modifies_state = False
|
|
105
|
+
|
|
106
|
+
def run(self, params: dict[str, Any] | None = None) -> Return(echo=dict):
|
|
107
|
+
params = params or {}
|
|
108
|
+
return params
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def default_registry() -> ToolRegistry:
|
|
112
|
+
reg = ToolRegistry()
|
|
113
|
+
reg.register(EchoTool())
|
|
114
|
+
return reg
|
deer/tracing/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("DEER")
|
|
6
|
+
if not logger.handlers:
|
|
7
|
+
handler = logging.StreamHandler()
|
|
8
|
+
handler.setFormatter(formatter)
|
|
9
|
+
logger.addHandler(handler)
|
|
10
|
+
logger.setLevel(logging.WARNING)
|
|
11
|
+
logger.propagate = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger_llm = logging.getLogger("DEER-LLM")
|
|
15
|
+
if not logger_llm.handlers:
|
|
16
|
+
handler = logging.StreamHandler()
|
|
17
|
+
handler.setFormatter(formatter)
|
|
18
|
+
logger_llm.addHandler(handler)
|
|
19
|
+
logger_llm.setLevel(logging.WARNING)
|
|
20
|
+
logger_llm.propagate = False
|
deer/tracing/store.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from deer.schema.io import StepTrace
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TraceStore:
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self._buffer: List[StepTrace] = []
|
|
8
|
+
|
|
9
|
+
def reset(self) -> None:
|
|
10
|
+
"""Limpia la traza actual (para una nueva ejecución)."""
|
|
11
|
+
self._buffer.clear()
|
|
12
|
+
|
|
13
|
+
def append(self, trace: StepTrace) -> None:
|
|
14
|
+
self._buffer.append(trace)
|
|
15
|
+
|
|
16
|
+
def extend(self, traces: list[StepTrace]):
|
|
17
|
+
self._buffer.extend(traces)
|
|
18
|
+
|
|
19
|
+
def get_trace(self) -> List[StepTrace]:
|
|
20
|
+
"""Devuelve una copia de la traza acumulada."""
|
|
21
|
+
return list(self._buffer)
|
deer/utils/__init__.py
ADDED
|
File without changes
|
deer/utils/console.py
ADDED