zrb 1.15.11__py3-none-any.whl → 1.15.13__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.
- zrb/builtin/llm/tool/code.py +0 -1
- zrb/config/default_prompt/summarization_prompt.md +11 -14
- zrb/config/llm_context/config.py +67 -38
- zrb/task/llm/conversation_history.py +73 -2
- zrb/task/llm/conversation_history_model.py +0 -168
- zrb/task/llm/history_summarization.py +6 -8
- zrb/task/llm/history_summarization_tool.py +38 -0
- zrb/task/llm/tool_wrapper.py +2 -2
- zrb/task/llm_task.py +2 -1
- {zrb-1.15.11.dist-info → zrb-1.15.13.dist-info}/METADATA +1 -1
- {zrb-1.15.11.dist-info → zrb-1.15.13.dist-info}/RECORD +13 -12
- {zrb-1.15.11.dist-info → zrb-1.15.13.dist-info}/WHEEL +0 -0
- {zrb-1.15.11.dist-info → zrb-1.15.13.dist-info}/entry_points.txt +0 -0
zrb/builtin/llm/tool/code.py
CHANGED
@@ -122,7 +122,6 @@ async def analyze_repo(
|
|
122
122
|
)
|
123
123
|
if len(extracted_infos) == 1:
|
124
124
|
return extracted_infos[0]
|
125
|
-
ctx.print(stylize_faint(" 📝 Summarization"), plain=True)
|
126
125
|
summarized_infos = extracted_infos
|
127
126
|
while len(summarized_infos) > 1:
|
128
127
|
ctx.print(stylize_faint(" 📝 Summarization"), plain=True)
|
@@ -1,20 +1,17 @@
|
|
1
|
-
You are a memory management AI. Your
|
1
|
+
You are a memory management AI. Your goal is to curate the conversation history by calling the `update_conversation_memory` tool.
|
2
2
|
|
3
3
|
Follow these steps precisely:
|
4
4
|
|
5
|
-
**Step 1:
|
5
|
+
**Step 1: Consolidate Conversation Information**
|
6
6
|
|
7
|
-
1.
|
8
|
-
2.
|
7
|
+
1. Create a concise narrative summary that integrates the `Past Conversation Summary` with the `Recent Conversation`.
|
8
|
+
2. Extract ONLY the last 4 (four) turns of the `Recent Conversation` to serve as the new transcript. Do not change or shorten the content of these turns. Ensure the timestamp format is `[YYYY-MM-DD HH:MM:SS UTC+Z] Role: Message/Tool name being called`.
|
9
|
+
3. Review the `Notes` and the `Recent Conversation` to identify any new or updated facts.
|
10
|
+
* Update global facts about the user or their preferences for the `long_term_note`.
|
11
|
+
* Update facts specific to the current project or directory for the `contextual_note`.
|
12
|
+
* **CRITICAL:** When updating `contextual_note`, you MUST determine the correct `context_path` by analyzing the `Recent Conversation`. For example, if a fact was established when the working directory was `/app`, the `context_path` MUST be `/app`.
|
13
|
+
* **CRITICAL:** The content for notes must be raw, unformatted text. Do not use Markdown. Notes should be timeless facts, not a log of events. Only update notes if the information has actually changed.
|
9
14
|
|
10
|
-
**Step 2: Update
|
15
|
+
**Step 2: Update Memory**
|
11
16
|
|
12
|
-
1.
|
13
|
-
2. Call `write_long_term_note` AT MOST ONCE. Use it to add or update global facts about the user or their preferences.
|
14
|
-
3. Call `write_contextual_note` AT MOST ONCE. Use it to add or update facts specific to the current project or directory (`context_path`).
|
15
|
-
4. **CRITICAL:** When calling `write_contextual_note`, you MUST determine the correct `context_path` by analyzing the `Recent Conversation`. For example, if a fact was established when the working directory was `/app`, the `context_path` MUST be `/app`.
|
16
|
-
5. **CRITICAL:** The content for notes must be raw, unformatted text. Do not use Markdown. Notes should be timeless facts, not a log of events. Only update notes if the information has actually changed.
|
17
|
-
|
18
|
-
**Step 3: Finalize**
|
19
|
-
|
20
|
-
After making all necessary tool calls, your FINAL RESPONSE must be a single word "DONE" marking the end of this session.
|
17
|
+
1. Call the `update_conversation_memory` tool ONCE, providing all the information you consolidated in Step 1 as arguments.
|
zrb/config/llm_context/config.py
CHANGED
@@ -35,6 +35,17 @@ class LLMContextConfig:
|
|
35
35
|
all_sections.append((config_dir, sections))
|
36
36
|
return all_sections
|
37
37
|
|
38
|
+
def _normalize_context_path(
|
39
|
+
self,
|
40
|
+
path_str: str,
|
41
|
+
relative_to_dir: str,
|
42
|
+
) -> str:
|
43
|
+
"""Normalizes a context path string to an absolute path."""
|
44
|
+
expanded_path = os.path.expanduser(path_str)
|
45
|
+
if os.path.isabs(expanded_path):
|
46
|
+
return os.path.abspath(expanded_path)
|
47
|
+
return os.path.abspath(os.path.join(relative_to_dir, expanded_path))
|
48
|
+
|
38
49
|
def get_contexts(self, cwd: str | None = None) -> dict[str, str]:
|
39
50
|
"""Gathers all relevant contexts for a given path."""
|
40
51
|
if cwd is None:
|
@@ -44,15 +55,14 @@ class LLMContextConfig:
|
|
44
55
|
for config_dir, sections in reversed(all_sections):
|
45
56
|
for key, value in sections.items():
|
46
57
|
if key.startswith("Context:"):
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
contexts[context_path] = value
|
58
|
+
context_path_str = key[len("Context:") :].strip()
|
59
|
+
abs_context_path = self._normalize_context_path(
|
60
|
+
context_path_str,
|
61
|
+
config_dir,
|
62
|
+
)
|
63
|
+
# A context is relevant if its path is an ancestor of cwd
|
64
|
+
if os.path.commonpath([cwd, abs_context_path]) == abs_context_path:
|
65
|
+
contexts[abs_context_path] = value
|
56
66
|
return contexts
|
57
67
|
|
58
68
|
def get_workflows(self, cwd: str | None = None) -> dict[str, str]:
|
@@ -61,60 +71,79 @@ class LLMContextConfig:
|
|
61
71
|
cwd = os.getcwd()
|
62
72
|
all_sections = self._get_all_sections(cwd)
|
63
73
|
workflows: dict[str, str] = {}
|
64
|
-
|
74
|
+
# Iterate from closest to farthest
|
75
|
+
for _, sections in all_sections:
|
65
76
|
for key, value in sections.items():
|
66
77
|
if key.startswith("Workflow:"):
|
67
|
-
workflow_name = key[len("Workflow:") :].strip()
|
68
|
-
|
69
|
-
|
78
|
+
workflow_name = key[len("Workflow:") :].strip().lower()
|
79
|
+
# First one found wins
|
80
|
+
if workflow_name not in workflows:
|
81
|
+
workflows[workflow_name] = value
|
70
82
|
return workflows
|
71
83
|
|
84
|
+
def _format_context_path_for_writing(
|
85
|
+
self,
|
86
|
+
path_to_write: str,
|
87
|
+
cwd: str,
|
88
|
+
) -> str:
|
89
|
+
"""Formats a path for writing into a context file key."""
|
90
|
+
home_dir = os.path.expanduser("~")
|
91
|
+
abs_path_to_write = os.path.abspath(os.path.join(cwd, path_to_write))
|
92
|
+
abs_cwd = os.path.abspath(cwd)
|
93
|
+
# Rule 1: Inside CWD
|
94
|
+
if abs_path_to_write.startswith(abs_cwd):
|
95
|
+
if abs_path_to_write == abs_cwd:
|
96
|
+
return "."
|
97
|
+
return os.path.relpath(abs_path_to_write, abs_cwd)
|
98
|
+
# Rule 2: Inside Home
|
99
|
+
if abs_path_to_write.startswith(home_dir):
|
100
|
+
if abs_path_to_write == home_dir:
|
101
|
+
return "~"
|
102
|
+
return os.path.join("~", os.path.relpath(abs_path_to_write, home_dir))
|
103
|
+
# Rule 3: Absolute
|
104
|
+
return abs_path_to_write
|
105
|
+
|
72
106
|
def write_context(
|
73
|
-
self,
|
107
|
+
self,
|
108
|
+
content: str,
|
109
|
+
context_path: str | None = None,
|
110
|
+
cwd: str | None = None,
|
74
111
|
):
|
75
|
-
"""Writes content to a context block in
|
112
|
+
"""Writes content to a context block in CWD's configuration file."""
|
76
113
|
if cwd is None:
|
77
114
|
cwd = os.getcwd()
|
78
115
|
if context_path is None:
|
79
116
|
context_path = cwd
|
80
117
|
|
81
|
-
|
82
|
-
if config_files:
|
83
|
-
config_file = config_files[0] # Closest config file
|
84
|
-
else:
|
85
|
-
config_file = os.path.join(cwd, CFG.LLM_CONTEXT_FILE)
|
118
|
+
config_file = os.path.join(cwd, CFG.LLM_CONTEXT_FILE)
|
86
119
|
|
87
120
|
sections = {}
|
88
121
|
if os.path.exists(config_file):
|
89
122
|
sections = self._parse_config(config_file)
|
90
123
|
|
91
|
-
|
92
|
-
section_key_path = context_path
|
93
|
-
if not os.path.isabs(context_path):
|
94
|
-
config_dir = os.path.dirname(config_file)
|
95
|
-
section_key_path = os.path.abspath(os.path.join(config_dir, context_path))
|
124
|
+
abs_context_path = os.path.abspath(os.path.join(cwd, context_path))
|
96
125
|
|
97
|
-
|
98
|
-
found_key = ""
|
126
|
+
found_key = None
|
99
127
|
for key in sections.keys():
|
100
128
|
if not key.startswith("Context:"):
|
101
129
|
continue
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
)
|
109
|
-
if key_path == section_key_path:
|
130
|
+
context_path_str = key[len("Context:") :].strip()
|
131
|
+
abs_key_path = self._normalize_context_path(
|
132
|
+
context_path_str,
|
133
|
+
os.path.dirname(config_file),
|
134
|
+
)
|
135
|
+
if abs_key_path == abs_context_path:
|
110
136
|
found_key = key
|
111
137
|
break
|
112
138
|
|
113
|
-
if found_key
|
139
|
+
if found_key:
|
114
140
|
sections[found_key] = content
|
115
141
|
else:
|
116
|
-
|
117
|
-
|
142
|
+
formatted_path = self._format_context_path_for_writing(
|
143
|
+
context_path,
|
144
|
+
cwd,
|
145
|
+
)
|
146
|
+
new_key = f"Context: {formatted_path}"
|
118
147
|
sections[new_key] = content
|
119
148
|
|
120
149
|
# Serialize back to markdown
|
@@ -1,18 +1,46 @@
|
|
1
1
|
import json
|
2
|
+
import os
|
2
3
|
from collections.abc import Callable
|
3
4
|
from copy import deepcopy
|
4
5
|
from typing import Any
|
5
6
|
|
6
7
|
from zrb.attr.type import StrAttr
|
8
|
+
from zrb.config.llm_context.config import llm_context_config
|
7
9
|
from zrb.context.any_context import AnyContext
|
8
10
|
from zrb.context.any_shared_context import AnySharedContext
|
9
11
|
from zrb.task.llm.conversation_history_model import ConversationHistory
|
10
12
|
from zrb.task.llm.typing import ListOfDict
|
11
13
|
from zrb.util.attr import get_str_attr
|
12
|
-
from zrb.util.file import write_file
|
14
|
+
from zrb.util.file import read_file, write_file
|
15
|
+
from zrb.util.llm.prompt import make_prompt_section
|
13
16
|
from zrb.util.run import run_async
|
14
17
|
|
15
18
|
|
19
|
+
def inject_conversation_history_notes(conversation_history: ConversationHistory):
|
20
|
+
conversation_history.long_term_note = _fetch_long_term_note(
|
21
|
+
conversation_history.project_path
|
22
|
+
)
|
23
|
+
conversation_history.contextual_note = _fetch_contextual_note(
|
24
|
+
conversation_history.project_path
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def _fetch_long_term_note(project_path: str) -> str:
|
29
|
+
contexts = llm_context_config.get_contexts(cwd=project_path)
|
30
|
+
return contexts.get("/", "")
|
31
|
+
|
32
|
+
|
33
|
+
def _fetch_contextual_note(project_path: str) -> str:
|
34
|
+
contexts = llm_context_config.get_contexts(cwd=project_path)
|
35
|
+
return "\n".join(
|
36
|
+
[
|
37
|
+
make_prompt_section(header, content)
|
38
|
+
for header, content in contexts.items()
|
39
|
+
if header != "/"
|
40
|
+
]
|
41
|
+
)
|
42
|
+
|
43
|
+
|
16
44
|
def get_history_file(
|
17
45
|
ctx: AnyContext,
|
18
46
|
conversation_history_file_attr: StrAttr | None,
|
@@ -27,6 +55,49 @@ def get_history_file(
|
|
27
55
|
)
|
28
56
|
|
29
57
|
|
58
|
+
async def _read_from_source(
|
59
|
+
ctx: AnyContext,
|
60
|
+
reader: Callable[[AnyContext], dict[str, Any] | list | None] | None,
|
61
|
+
file_path: str | None,
|
62
|
+
) -> "ConversationHistory | None":
|
63
|
+
# Priority 1: Reader function
|
64
|
+
if reader:
|
65
|
+
try:
|
66
|
+
raw_data = await run_async(reader(ctx))
|
67
|
+
if raw_data:
|
68
|
+
instance = ConversationHistory.parse_and_validate(
|
69
|
+
ctx, raw_data, "reader"
|
70
|
+
)
|
71
|
+
if instance:
|
72
|
+
return instance
|
73
|
+
except Exception as e:
|
74
|
+
ctx.log_warning(
|
75
|
+
f"Error executing conversation history reader: {e}. Ignoring."
|
76
|
+
)
|
77
|
+
# Priority 2: History file
|
78
|
+
if file_path and os.path.isfile(file_path):
|
79
|
+
try:
|
80
|
+
content = read_file(file_path)
|
81
|
+
raw_data = json.loads(content)
|
82
|
+
instance = ConversationHistory.parse_and_validate(
|
83
|
+
ctx, raw_data, f"file '{file_path}'"
|
84
|
+
)
|
85
|
+
if instance:
|
86
|
+
return instance
|
87
|
+
except json.JSONDecodeError:
|
88
|
+
ctx.log_warning(
|
89
|
+
f"Could not decode JSON from history file '{file_path}'. "
|
90
|
+
"Ignoring file content."
|
91
|
+
)
|
92
|
+
except Exception as e:
|
93
|
+
ctx.log_warning(
|
94
|
+
f"Error reading history file '{file_path}': {e}. "
|
95
|
+
"Ignoring file content."
|
96
|
+
)
|
97
|
+
# Fallback: Return default value
|
98
|
+
return None
|
99
|
+
|
100
|
+
|
30
101
|
async def read_conversation_history(
|
31
102
|
ctx: AnyContext,
|
32
103
|
conversation_history_reader: (
|
@@ -46,7 +117,7 @@ async def read_conversation_history(
|
|
46
117
|
ctx, conversation_history_file_attr, render_history_file
|
47
118
|
)
|
48
119
|
# Use the class method defined above
|
49
|
-
history_data = await
|
120
|
+
history_data = await _read_from_source(
|
50
121
|
ctx=ctx,
|
51
122
|
reader=conversation_history_reader,
|
52
123
|
file_path=history_file,
|
@@ -1,14 +1,9 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
|
-
from collections.abc import Callable
|
4
3
|
from typing import Any
|
5
4
|
|
6
|
-
from zrb.config.llm_context.config import llm_context_config
|
7
5
|
from zrb.context.any_context import AnyContext
|
8
6
|
from zrb.task.llm.typing import ListOfDict
|
9
|
-
from zrb.util.file import read_file
|
10
|
-
from zrb.util.llm.prompt import make_prompt_section
|
11
|
-
from zrb.util.run import run_async
|
12
7
|
|
13
8
|
|
14
9
|
class ConversationHistory:
|
@@ -41,50 +36,6 @@ class ConversationHistory:
|
|
41
36
|
def model_dump_json(self, indent: int = 2) -> str:
|
42
37
|
return json.dumps(self.to_dict(), indent=indent)
|
43
38
|
|
44
|
-
@classmethod
|
45
|
-
async def read_from_source(
|
46
|
-
cls,
|
47
|
-
ctx: AnyContext,
|
48
|
-
reader: Callable[[AnyContext], dict[str, Any] | list | None] | None,
|
49
|
-
file_path: str | None,
|
50
|
-
) -> "ConversationHistory | None":
|
51
|
-
# Priority 1: Reader function
|
52
|
-
if reader:
|
53
|
-
try:
|
54
|
-
raw_data = await run_async(reader(ctx))
|
55
|
-
if raw_data:
|
56
|
-
instance = cls.parse_and_validate(ctx, raw_data, "reader")
|
57
|
-
if instance:
|
58
|
-
return instance
|
59
|
-
except Exception as e:
|
60
|
-
ctx.log_warning(
|
61
|
-
f"Error executing conversation history reader: {e}. Ignoring."
|
62
|
-
)
|
63
|
-
# Priority 2: History file
|
64
|
-
if file_path and os.path.isfile(file_path):
|
65
|
-
try:
|
66
|
-
content = read_file(file_path)
|
67
|
-
raw_data = json.loads(content)
|
68
|
-
instance = cls.parse_and_validate(ctx, raw_data, f"file '{file_path}'")
|
69
|
-
if instance:
|
70
|
-
return instance
|
71
|
-
except json.JSONDecodeError:
|
72
|
-
ctx.log_warning(
|
73
|
-
f"Could not decode JSON from history file '{file_path}'. "
|
74
|
-
"Ignoring file content."
|
75
|
-
)
|
76
|
-
except Exception as e:
|
77
|
-
ctx.log_warning(
|
78
|
-
f"Error reading history file '{file_path}': {e}. "
|
79
|
-
"Ignoring file content."
|
80
|
-
)
|
81
|
-
# Fallback: Return default value
|
82
|
-
return None
|
83
|
-
|
84
|
-
def fetch_newest_notes(self):
|
85
|
-
self._fetch_long_term_note()
|
86
|
-
self._fetch_contextual_note()
|
87
|
-
|
88
39
|
@classmethod
|
89
40
|
def parse_and_validate(
|
90
41
|
cls, ctx: AnyContext, data: Any, source: str
|
@@ -121,122 +72,3 @@ class ConversationHistory:
|
|
121
72
|
f"Error validating/parsing history data from {source}: {e}. Ignoring."
|
122
73
|
)
|
123
74
|
return cls()
|
124
|
-
|
125
|
-
def write_past_conversation_summary(self, past_conversation_summary: str):
|
126
|
-
"""
|
127
|
-
Write or update the past conversation summary.
|
128
|
-
|
129
|
-
Use this tool to store or update a summary of previous conversations for
|
130
|
-
future reference. This is useful for providing context to LLMs or other tools
|
131
|
-
that need a concise history.
|
132
|
-
|
133
|
-
Args:
|
134
|
-
past_conversation_summary (str): The summary text to store.
|
135
|
-
|
136
|
-
Returns:
|
137
|
-
str: A JSON object indicating the success or failure of the operation.
|
138
|
-
|
139
|
-
Raises:
|
140
|
-
Exception: If the summary cannot be written.
|
141
|
-
"""
|
142
|
-
self.past_conversation_summary = past_conversation_summary
|
143
|
-
return json.dumps({"success": True})
|
144
|
-
|
145
|
-
def write_past_conversation_transcript(self, past_conversation_transcript: str):
|
146
|
-
"""
|
147
|
-
Write or update the past conversation transcript.
|
148
|
-
|
149
|
-
Use this tool to store or update the full transcript of previous conversations.
|
150
|
-
This is useful for providing detailed context to LLMs or for record-keeping.
|
151
|
-
|
152
|
-
Args:
|
153
|
-
past_conversation_transcript (str): The transcript text to store.
|
154
|
-
|
155
|
-
Returns:
|
156
|
-
str: A JSON object indicating the success or failure of the operation.
|
157
|
-
|
158
|
-
Raises:
|
159
|
-
Exception: If the transcript cannot be written.
|
160
|
-
"""
|
161
|
-
self.past_conversation_transcript = past_conversation_transcript
|
162
|
-
return json.dumps({"success": True})
|
163
|
-
|
164
|
-
def read_long_term_note(self) -> str:
|
165
|
-
"""
|
166
|
-
Read the content of the long-term references.
|
167
|
-
|
168
|
-
This tool helps you retrieve knowledge or notes stored for long-term reference.
|
169
|
-
If the note does not exist, you may want to create it using the write tool.
|
170
|
-
|
171
|
-
Returns:
|
172
|
-
str: JSON with content of the notes.
|
173
|
-
|
174
|
-
Raises:
|
175
|
-
Exception: If the note cannot be read.
|
176
|
-
"""
|
177
|
-
return json.dumps({"content": self._fetch_long_term_note()})
|
178
|
-
|
179
|
-
def write_long_term_note(self, content: str) -> str:
|
180
|
-
"""
|
181
|
-
Write the entire content of the long-term references.
|
182
|
-
This will overwrite any existing long-term notes.
|
183
|
-
|
184
|
-
Args:
|
185
|
-
content (str): The full content of the long-term notes.
|
186
|
-
|
187
|
-
Returns:
|
188
|
-
str: JSON indicating success.
|
189
|
-
"""
|
190
|
-
llm_context_config.write_context(content, context_path="/")
|
191
|
-
return json.dumps({"success": True})
|
192
|
-
|
193
|
-
def read_contextual_note(self) -> str:
|
194
|
-
"""
|
195
|
-
Read the content of the contextual references for the current project.
|
196
|
-
|
197
|
-
This tool helps you retrieve knowledge or notes stored for contextual reference.
|
198
|
-
If the note does not exist, you may want to create it using the write tool.
|
199
|
-
|
200
|
-
Returns:
|
201
|
-
str: JSON with content of the notes.
|
202
|
-
|
203
|
-
Raises:
|
204
|
-
Exception: If the note cannot be read.
|
205
|
-
"""
|
206
|
-
return json.dumps({"content": self._fetch_contextual_note()})
|
207
|
-
|
208
|
-
def write_contextual_note(
|
209
|
-
self, content: str, context_path: str | None = None
|
210
|
-
) -> str:
|
211
|
-
"""
|
212
|
-
Write the entire content of the contextual references for a specific path.
|
213
|
-
This will overwrite any existing contextual notes for that path.
|
214
|
-
|
215
|
-
Args:
|
216
|
-
content (str): The full content of the contextual notes.
|
217
|
-
context_path (str, optional): The directory path for the context.
|
218
|
-
Defaults to the current project path.
|
219
|
-
|
220
|
-
Returns:
|
221
|
-
str: JSON indicating success.
|
222
|
-
"""
|
223
|
-
if context_path is None:
|
224
|
-
context_path = self.project_path
|
225
|
-
llm_context_config.write_context(content, context_path=context_path)
|
226
|
-
return json.dumps({"success": True})
|
227
|
-
|
228
|
-
def _fetch_long_term_note(self):
|
229
|
-
contexts = llm_context_config.get_contexts(cwd=self.project_path)
|
230
|
-
self.long_term_note = contexts.get("/", "")
|
231
|
-
return self.long_term_note
|
232
|
-
|
233
|
-
def _fetch_contextual_note(self):
|
234
|
-
contexts = llm_context_config.get_contexts(cwd=self.project_path)
|
235
|
-
self.contextual_note = "\n".join(
|
236
|
-
[
|
237
|
-
make_prompt_section(header, content)
|
238
|
-
for header, content in contexts.items()
|
239
|
-
if header != "/"
|
240
|
-
]
|
241
|
-
)
|
242
|
-
return self.contextual_note
|
@@ -9,9 +9,13 @@ from zrb.context.any_context import AnyContext
|
|
9
9
|
from zrb.task.llm.agent import run_agent_iteration
|
10
10
|
from zrb.task.llm.conversation_history import (
|
11
11
|
count_part_in_history_list,
|
12
|
+
inject_conversation_history_notes,
|
12
13
|
replace_system_prompt_in_history,
|
13
14
|
)
|
14
15
|
from zrb.task.llm.conversation_history_model import ConversationHistory
|
16
|
+
from zrb.task.llm.history_summarization_tool import (
|
17
|
+
create_history_summarization_tool,
|
18
|
+
)
|
15
19
|
from zrb.task.llm.typing import ListOfDict
|
16
20
|
from zrb.util.attr import get_bool_attr, get_int_attr
|
17
21
|
from zrb.util.cli.style import stylize_faint
|
@@ -93,6 +97,7 @@ async def summarize_history(
|
|
93
97
|
"""Runs an LLM call to update the conversation summary."""
|
94
98
|
from pydantic_ai import Agent
|
95
99
|
|
100
|
+
inject_conversation_history_notes(conversation_history)
|
96
101
|
ctx.log_info("Attempting to summarize conversation history...")
|
97
102
|
# Construct the user prompt for the summarization agent
|
98
103
|
user_prompt = "\n".join(
|
@@ -143,14 +148,7 @@ async def summarize_history(
|
|
143
148
|
system_prompt=system_prompt,
|
144
149
|
model_settings=settings,
|
145
150
|
retries=retries,
|
146
|
-
tools=[
|
147
|
-
conversation_history.write_past_conversation_summary,
|
148
|
-
conversation_history.write_past_conversation_transcript,
|
149
|
-
conversation_history.read_long_term_note,
|
150
|
-
conversation_history.write_long_term_note,
|
151
|
-
conversation_history.read_contextual_note,
|
152
|
-
conversation_history.write_contextual_note,
|
153
|
-
],
|
151
|
+
tools=[create_history_summarization_tool(conversation_history)],
|
154
152
|
)
|
155
153
|
try:
|
156
154
|
ctx.print(stylize_faint(" 📝 Rollup Conversation"), plain=True)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Callable
|
3
|
+
|
4
|
+
from zrb.config.llm_context.config import llm_context_config
|
5
|
+
from zrb.task.llm.conversation_history_model import ConversationHistory
|
6
|
+
|
7
|
+
|
8
|
+
def create_history_summarization_tool(
|
9
|
+
conversation_history: ConversationHistory,
|
10
|
+
) -> Callable:
|
11
|
+
def update_conversation_memory(
|
12
|
+
past_conversation_summary: str,
|
13
|
+
past_conversation_transcript: str,
|
14
|
+
long_term_note: str | None = None,
|
15
|
+
contextual_note: str | None = None,
|
16
|
+
context_path: str | None = None,
|
17
|
+
):
|
18
|
+
"""
|
19
|
+
Update the conversation memory including summary, transcript, and notes.
|
20
|
+
- past_conversation_summary: A concise narrative that integrates the
|
21
|
+
previous summary with the recent conversation.
|
22
|
+
- past_conversation_transcript: MUST be ONLY the last 4 (four) turns
|
23
|
+
of the conversation.
|
24
|
+
- long_term_note: Global facts about the user or their preferences.
|
25
|
+
- contextual_note: Facts specific to the current project or directory.
|
26
|
+
- context_path: The directory path for the contextual note.
|
27
|
+
"""
|
28
|
+
conversation_history.past_conversation_summary = past_conversation_summary
|
29
|
+
conversation_history.past_conversation_transcript = past_conversation_transcript
|
30
|
+
if long_term_note is not None:
|
31
|
+
llm_context_config.write_context(long_term_note, context_path="/")
|
32
|
+
if contextual_note is not None:
|
33
|
+
if context_path is None:
|
34
|
+
context_path = conversation_history.project_path
|
35
|
+
llm_context_config.write_context(contextual_note, context_path=context_path)
|
36
|
+
return json.dumps({"success": True})
|
37
|
+
|
38
|
+
return update_conversation_memory
|
zrb/task/llm/tool_wrapper.py
CHANGED
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
|
22
22
|
from pydantic_ai import Tool
|
23
23
|
|
24
24
|
|
25
|
-
class
|
25
|
+
class ToolExecutionCancelled(ValueError):
|
26
26
|
pass
|
27
27
|
|
28
28
|
|
@@ -113,7 +113,7 @@ def _create_wrapper(
|
|
113
113
|
if not is_yolo_mode and not ctx.is_web_mode and ctx.is_tty:
|
114
114
|
approval, reason = await _ask_for_approval(ctx, func, args, kwargs)
|
115
115
|
if not approval:
|
116
|
-
raise
|
116
|
+
raise ToolExecutionCancelled(f"User disapproving: {reason}")
|
117
117
|
return await run_async(func(*args, **kwargs))
|
118
118
|
except KeyboardInterrupt as e:
|
119
119
|
raise e
|
zrb/task/llm_task.py
CHANGED
@@ -17,6 +17,7 @@ from zrb.task.llm.config import (
|
|
17
17
|
get_model_settings,
|
18
18
|
)
|
19
19
|
from zrb.task.llm.conversation_history import (
|
20
|
+
inject_conversation_history_notes,
|
20
21
|
read_conversation_history,
|
21
22
|
write_conversation_history,
|
22
23
|
)
|
@@ -241,7 +242,7 @@ class LLMTask(BaseTask):
|
|
241
242
|
render_history_file=self._render_history_file,
|
242
243
|
conversation_history_attr=self._conversation_history,
|
243
244
|
)
|
244
|
-
conversation_history
|
245
|
+
inject_conversation_history_notes(conversation_history)
|
245
246
|
# 2. Get system prompt and user prompt
|
246
247
|
system_prompt, user_message = get_system_and_user_prompt(
|
247
248
|
ctx=ctx,
|
@@ -17,7 +17,7 @@ zrb/builtin/llm/previous-session.js,sha256=xMKZvJoAbrwiyHS0OoPrWuaKxWYLoyR5sgueP
|
|
17
17
|
zrb/builtin/llm/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
18
|
zrb/builtin/llm/tool/api.py,sha256=vMEiZhhTZ3o2jRBxWcJ62b0M85wd_w4W0X4Hx23NXto,2380
|
19
19
|
zrb/builtin/llm/tool/cli.py,sha256=8rugrKaNPEatHjr7nN4OIRLRT2TcF-oylEZGbLI9Brs,1254
|
20
|
-
zrb/builtin/llm/tool/code.py,sha256=
|
20
|
+
zrb/builtin/llm/tool/code.py,sha256=fr9FbmtfwizQTyTztvuvwAb9MD_auRZhPZfoJVBlKT4,8777
|
21
21
|
zrb/builtin/llm/tool/file.py,sha256=eXFGGFxxpdpWGVw0svyQNQc03I5M7wotSsA_HjkXw7c,23670
|
22
22
|
zrb/builtin/llm/tool/rag.py,sha256=Ab8_ZljnG_zfkwxPezImvorshuz3Fi4CmSzNOtU1a-g,9770
|
23
23
|
zrb/builtin/llm/tool/sub_agent.py,sha256=dPqdFCXxJ-3hZnPjheZr-bPeKNbfSBSDKcZZR5vkWFM,5075
|
@@ -223,10 +223,10 @@ zrb/config/default_prompt/interactive_system_prompt.md,sha256=XvXI51dMpQmuuYah_L
|
|
223
223
|
zrb/config/default_prompt/persona.md,sha256=GfUJ4-Mlf_Bm1YTzxFNkPkdVbAi06ZDVYh-iIma3NOs,253
|
224
224
|
zrb/config/default_prompt/repo_extractor_system_prompt.md,sha256=EGZ-zj78RlMEg2jduRBs8WzO4VJTkXHR96IpBepZMsY,3881
|
225
225
|
zrb/config/default_prompt/repo_summarizer_system_prompt.md,sha256=RNy37Wg7ibXj3DlsFKaYvgMpMS-lyXlM1LZlc59_4ic,2009
|
226
|
-
zrb/config/default_prompt/summarization_prompt.md,sha256=
|
226
|
+
zrb/config/default_prompt/summarization_prompt.md,sha256=GMqbw7mmDWQf7rhnJcJjXQFtGu7nVYIZfK6sStJ4coc,1472
|
227
227
|
zrb/config/default_prompt/system_prompt.md,sha256=gEb6N-cFg6VvOV-7ZffNwVt39DavAGesMqn9u0epbRc,2282
|
228
228
|
zrb/config/llm_config.py,sha256=xt-Xf8ZuNoUT_GKCSFz5yy0BhbeHzxP-jrezB06WeiY,8857
|
229
|
-
zrb/config/llm_context/config.py,sha256=
|
229
|
+
zrb/config/llm_context/config.py,sha256=TPQX_kU772r0AHmVFeo1WGLDAidacT-qDyuMWxY_avg,5878
|
230
230
|
zrb/config/llm_context/config_parser.py,sha256=h95FbOjvVobhrsfGtG_BY3hxS-OLzQj-9F5vGZuehkY,1473
|
231
231
|
zrb/config/llm_rate_limitter.py,sha256=_iQRv3d6kUPeRvmUYZX_iwCE7iDSEK1oKa4bQ9GROho,5261
|
232
232
|
zrb/config/web_auth_config.py,sha256=_PXatQTYh2mX9H3HSYSQKp13zm1RlLyVIoeIr6KYMQ8,6279
|
@@ -348,18 +348,19 @@ zrb/task/http_check.py,sha256=Gf5rOB2Se2EdizuN9rp65HpGmfZkGc-clIAlHmPVehs,2565
|
|
348
348
|
zrb/task/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
349
349
|
zrb/task/llm/agent.py,sha256=jfC90N8oER2z-fTmPEuWH3ASPgw5t8cZm8rk98HBBlw,8876
|
350
350
|
zrb/task/llm/config.py,sha256=n1SPmwab09K2i1sL_OCwrEOWHI0Owx_hvWelg3Dreus,3781
|
351
|
-
zrb/task/llm/conversation_history.py,sha256=
|
352
|
-
zrb/task/llm/conversation_history_model.py,sha256=
|
351
|
+
zrb/task/llm/conversation_history.py,sha256=oMdKUV2__mBZ4znnA-prl-gfyoleKC8Nj5KNpmLQJ4o,6764
|
352
|
+
zrb/task/llm/conversation_history_model.py,sha256=kk-7niTl29Rm2EUIhTHzPXgZ5tp4IThMnIB3dS-1OdU,3062
|
353
353
|
zrb/task/llm/default_workflow/coding.md,sha256=2uythvPsnBpYfIhiIH1cCinQXX0i0yUqsL474Zpemw0,2484
|
354
354
|
zrb/task/llm/default_workflow/copywriting.md,sha256=xSO7GeDolwGxiuz6kXsK2GKGpwp8UgtG0yRqTmill_s,1999
|
355
355
|
zrb/task/llm/default_workflow/researching.md,sha256=KD-aYHFHir6Ti-4FsBBtGwiI0seSVgleYbKJZi_POXA,2139
|
356
356
|
zrb/task/llm/error.py,sha256=QR-nIohS6pBpC_16cWR-fw7Mevo1sNYAiXMBsh_CJDE,4157
|
357
|
-
zrb/task/llm/history_summarization.py,sha256=
|
357
|
+
zrb/task/llm/history_summarization.py,sha256=UIT8bpdT3hy1xn559waDLFWZlNtIqdIpIvRGcZEpHm0,8057
|
358
|
+
zrb/task/llm/history_summarization_tool.py,sha256=Wazi4WMr3k1WJ1v7QgjAPbuY1JdBpHUsTWGt3DSTsLc,1706
|
358
359
|
zrb/task/llm/print_node.py,sha256=fNTL0LtoZBQQPHYCAUaOFP97nNNfobPXvY2RlxewQCE,7556
|
359
360
|
zrb/task/llm/prompt.py,sha256=FGXWYHecWtrNNkPnjg-uhnkqp7fYt8V91-AjFM_5fpA,11550
|
360
|
-
zrb/task/llm/tool_wrapper.py,sha256=
|
361
|
+
zrb/task/llm/tool_wrapper.py,sha256=IavXwW9C0ojI3ivGDY8j7XbMJE_NTJeI86TVbHNRwJ0,9684
|
361
362
|
zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
|
362
|
-
zrb/task/llm_task.py,sha256=
|
363
|
+
zrb/task/llm_task.py,sha256=ctEp-5R7ZnjA3V6pSkthKN_o7xjASre8MgMcQVHtsj8,14504
|
363
364
|
zrb/task/make_task.py,sha256=PD3b_aYazthS8LHeJsLAhwKDEgdurQZpymJDKeN60u0,2265
|
364
365
|
zrb/task/rsync_task.py,sha256=WfqNSaicJgYWpunNU34eYxXDqHDHOftuDHyWJKjqwg0,6365
|
365
366
|
zrb/task/scaffolder.py,sha256=rME18w1HJUHXgi9eTYXx_T2G4JdqDYzBoNOkdOOo5-o,6806
|
@@ -408,7 +409,7 @@ zrb/util/todo_model.py,sha256=hhzAX-uFl5rsg7iVX1ULlJOfBtblwQ_ieNUxBWfc-Os,1670
|
|
408
409
|
zrb/util/truncate.py,sha256=eSzmjBpc1Qod3lM3M73snNbDOcARHukW_tq36dWdPvc,921
|
409
410
|
zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
410
411
|
zrb/xcom/xcom.py,sha256=o79rxR9wphnShrcIushA0Qt71d_p3ZTxjNf7x9hJB78,1571
|
411
|
-
zrb-1.15.
|
412
|
-
zrb-1.15.
|
413
|
-
zrb-1.15.
|
414
|
-
zrb-1.15.
|
412
|
+
zrb-1.15.13.dist-info/METADATA,sha256=lDKm2YdMNhKPRFayict53Twk3Bd_nnQExw7tZFqWe5c,9775
|
413
|
+
zrb-1.15.13.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
414
|
+
zrb-1.15.13.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
|
415
|
+
zrb-1.15.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|