OpenRouter Support, Insight Chat and User injection #56
@@ -382,6 +382,12 @@ impl InsightChatService {
|
|||||||
// 7. Append the new user turn.
|
// 7. Append the new user turn.
|
||||||
messages.push(ChatMessage::user(req.user_message.clone()));
|
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);
|
let insight_cx = parent_cx.with_span(span);
|
||||||
|
|
||||||
// 8. Agentic loop — same shape as insight_generator's, but capped
|
// 8. Agentic loop — same shape as insight_generator's, but capped
|
||||||
@@ -468,6 +474,10 @@ impl InsightChatService {
|
|||||||
|
|
||||||
loop_cx.span().set_status(Status::Ok);
|
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
|
// 9. Persist. Append mode rewrites the JSON blob in place; amend
|
||||||
// mode regenerates the title and inserts a new insight row,
|
// mode regenerates the title and inserts a new insight row,
|
||||||
// relying on store_insight to flip prior rows' is_current=false.
|
// 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()));
|
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 tool_calls_made = 0usize;
|
||||||
let mut iterations_used = 0usize;
|
let mut iterations_used = 0usize;
|
||||||
let mut last_prompt_eval_count: Option<i32> = None;
|
let mut last_prompt_eval_count: Option<i32> = None;
|
||||||
@@ -917,6 +929,10 @@ impl InsightChatService {
|
|||||||
messages.push(final_response);
|
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.
|
// Persist.
|
||||||
let json = serde_json::to_string(&messages)
|
let json = serde_json::to_string(&messages)
|
||||||
.map_err(|e| anyhow!("failed to serialize chat history: {}", e))?;
|
.map_err(|e| anyhow!("failed to serialize chat history: {}", e))?;
|
||||||
@@ -1080,6 +1096,40 @@ fn env_max_iterations() -> usize {
|
|||||||
.max(1)
|
.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.
|
/// View returned to clients for chat-UI rendering.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HistoryView {
|
pub struct HistoryView {
|
||||||
|
|||||||
@@ -2892,8 +2892,11 @@ Return ONLY the summary, nothing else."#,
|
|||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// 6. Clear existing entity-photo links for this file so the run starts fresh,
|
// 6. Ensure the owner entity exists so the agent can reference it.
|
||||||
// and ensure the owner entity exists so the agent can reference it.
|
// Prior entity_photo_links for this file are intentionally preserved
|
||||||
|
// across regenerations — clearing them made `recall_facts_for_photo`
|
||||||
|
// always return empty and discarded hard-won knowledge. Re-linking
|
||||||
|
// the same entity is a no-op (INSERT OR IGNORE).
|
||||||
let owner_name = user_display_name();
|
let owner_name = user_display_name();
|
||||||
let owner_entity_id: Option<i32> = {
|
let owner_entity_id: Option<i32> = {
|
||||||
let mut kdao = self
|
let mut kdao = self
|
||||||
@@ -2901,14 +2904,6 @@ Return ONLY the summary, nothing else."#,
|
|||||||
.lock()
|
.lock()
|
||||||
.expect("Unable to lock KnowledgeDao");
|
.expect("Unable to lock KnowledgeDao");
|
||||||
|
|
||||||
if let Err(e) = kdao.delete_photo_links_for_file(&insight_cx, &file_path) {
|
|
||||||
log::warn!(
|
|
||||||
"Failed to clear entity_photo_links for {}: {:?}",
|
|
||||||
file_path,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upsert the owner entity so the agent always has a stable entity ID to reference.
|
// Upsert the owner entity so the agent always has a stable entity ID to reference.
|
||||||
let owner = crate::database::models::InsertEntity {
|
let owner = crate::database::models::InsertEntity {
|
||||||
name: owner_name.clone(),
|
name: owner_name.clone(),
|
||||||
@@ -3000,9 +2995,11 @@ Return ONLY the summary, nothing else."#,
|
|||||||
4. Use recall_entities to look up known people, places, or things that appear in this photo.\n\
|
4. Use recall_entities to look up known people, places, or things that appear in this photo.\n\
|
||||||
5. When you identify people, places, events, or notable things in this photo: use store_entity to record them and store_fact to record key facts (relationships, roles, attributes). This builds a persistent memory for future insights.\n\
|
5. When you identify people, places, events, or notable things in this photo: use store_entity to record them and store_fact to record key facts (relationships, roles, attributes). This builds a persistent memory for future insights.\n\
|
||||||
6. Only produce your final insight AFTER you have gathered context from at least 5 tool calls.\n\
|
6. Only produce your final insight AFTER you have gathered context from at least 5 tool calls.\n\
|
||||||
7. If a tool returns no results, that is useful information — continue calling the remaining tools anyway.",
|
7. If a tool returns no results, that is useful information — continue calling the remaining tools anyway.\n\
|
||||||
|
8. You have a hard budget of {max_iterations} tool-calling iterations before the loop ends. Plan your context gathering so you can write a complete final insight within that budget.",
|
||||||
owner_id_note = owner_id_note,
|
owner_id_note = owner_id_note,
|
||||||
owner_name = owner_name
|
owner_name = owner_name,
|
||||||
|
max_iterations = max_iterations
|
||||||
);
|
);
|
||||||
let system_content = if let Some(ref custom) = custom_system_prompt {
|
let system_content = if let Some(ref custom) = custom_system_prompt {
|
||||||
format!("{}\n\n{}", custom, base_system)
|
format!("{}\n\n{}", custom, base_system)
|
||||||
|
|||||||
Reference in New Issue
Block a user