feat(ai): iteration budget in prompt + preserve photo-knowledge links
- Inject the max-iterations budget into the agentic system prompt for both insight generation and chat turns. Chat does this per-turn by appending a note to the replayed system message and restoring it before persistence so the note doesn't accumulate across turns. - Stop deleting entity_photo_links at the start of agentic insight generation. The clear made recall_facts_for_photo always return empty, wasting a tool call and discarding knowledge from prior runs. Re-linking the same entity is already an INSERT OR IGNORE no-op. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -382,6 +382,12 @@ impl InsightChatService {
|
||||
// 7. Append the new user turn.
|
||||
messages.push(ChatMessage::user(req.user_message.clone()));
|
||||
|
||||
// Temporarily annotate the system message with this turn's iteration
|
||||
// budget so the model knows how many tool-calling rounds it has. We
|
||||
// restore the original content before persistence so the note doesn't
|
||||
// accumulate across turns.
|
||||
let original_system_content = annotate_system_with_budget(&mut messages, max_iterations);
|
||||
|
||||
let insight_cx = parent_cx.with_span(span);
|
||||
|
||||
// 8. Agentic loop — same shape as insight_generator's, but capped
|
||||
@@ -468,6 +474,10 @@ impl InsightChatService {
|
||||
|
||||
loop_cx.span().set_status(Status::Ok);
|
||||
|
||||
// Drop the per-turn iteration-budget note from the system message
|
||||
// before we persist so it doesn't snowball on each subsequent turn.
|
||||
restore_system_content(&mut messages, original_system_content);
|
||||
|
||||
// 9. Persist. Append mode rewrites the JSON blob in place; amend
|
||||
// mode regenerates the title and inserts a new insight row,
|
||||
// relying on store_insight to flip prior rows' is_current=false.
|
||||
@@ -789,6 +799,8 @@ impl InsightChatService {
|
||||
|
||||
messages.push(ChatMessage::user(req.user_message.clone()));
|
||||
|
||||
let original_system_content = annotate_system_with_budget(&mut messages, max_iterations);
|
||||
|
||||
let mut tool_calls_made = 0usize;
|
||||
let mut iterations_used = 0usize;
|
||||
let mut last_prompt_eval_count: Option<i32> = None;
|
||||
@@ -917,6 +929,10 @@ impl InsightChatService {
|
||||
messages.push(final_response);
|
||||
}
|
||||
|
||||
// Drop the per-turn iteration-budget note from the system message
|
||||
// before we persist so it doesn't snowball on each subsequent turn.
|
||||
restore_system_content(&mut messages, original_system_content);
|
||||
|
||||
// Persist.
|
||||
let json = serde_json::to_string(&messages)
|
||||
.map_err(|e| anyhow!("failed to serialize chat history: {}", e))?;
|
||||
@@ -1080,6 +1096,40 @@ fn env_max_iterations() -> usize {
|
||||
.max(1)
|
||||
}
|
||||
|
||||
/// Append a per-turn iteration-budget reminder to the replayed system
|
||||
/// message so the model knows how many tool-calling rounds this turn gets.
|
||||
/// Returns the original `content` so the caller can restore it before
|
||||
/// persistence — otherwise the note would accumulate across turns.
|
||||
///
|
||||
/// No-op (returns `None`) when `messages` has no leading system message.
|
||||
fn annotate_system_with_budget(
|
||||
messages: &mut [ChatMessage],
|
||||
max_iterations: usize,
|
||||
) -> Option<String> {
|
||||
let first = messages.first_mut()?;
|
||||
if first.role != "system" {
|
||||
return None;
|
||||
}
|
||||
let original = first.content.clone();
|
||||
first.content = format!(
|
||||
"{}\n\n(Budget for this chat turn: up to {} tool-calling iterations. Produce your final reply before the budget is exhausted.)",
|
||||
first.content, max_iterations
|
||||
);
|
||||
Some(original)
|
||||
}
|
||||
|
||||
/// Restore a system-message content previously captured by
|
||||
/// [`annotate_system_with_budget`]. No-op when `original` is `None` or the
|
||||
/// first message isn't a system message.
|
||||
fn restore_system_content(messages: &mut [ChatMessage], original: Option<String>) {
|
||||
let Some(original) = original else { return };
|
||||
if let Some(first) = messages.first_mut()
|
||||
&& first.role == "system"
|
||||
{
|
||||
first.content = original;
|
||||
}
|
||||
}
|
||||
|
||||
/// View returned to clients for chat-UI rendering.
|
||||
#[derive(Debug)]
|
||||
pub struct HistoryView {
|
||||
|
||||
Reference in New Issue
Block a user