mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(session-search): report source from resolved parent, not FTS5 child session (#15909)
When a delegation child session (e.g. source='telegram') contains the
FTS5 hit but _resolve_to_parent() maps it to a different root session
(source='api_server'), the result entry was still reporting the child's
source because the loop discarded session_meta as `_` and fell back to
match_info.get('source'), which carries the child session's value.
Use the resolved parent's session_meta for source, model, and started_at
with match_info as a fallback, so the output accurately reflects the
session the user actually interacted with.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b46b0c9888
commit
6b4ccb9b14
2 changed files with 73 additions and 4 deletions
|
|
@ -498,3 +498,65 @@ class TestSessionSearch:
|
||||||
assert result["count"] == 0
|
assert result["count"] == 0
|
||||||
assert result["results"] == []
|
assert result["results"] == []
|
||||||
assert result["sessions_searched"] == 0
|
assert result["sessions_searched"] == 0
|
||||||
|
|
||||||
|
def test_source_from_resolved_parent_not_fts5_child(self):
|
||||||
|
"""source in output must reflect the resolved parent session, not the child that matched FTS5.
|
||||||
|
|
||||||
|
Regression test for #15909: when a delegation child session (source='telegram')
|
||||||
|
resolves to a parent (source='api_server'), the result entry must report
|
||||||
|
'api_server', not 'telegram'.
|
||||||
|
"""
|
||||||
|
from unittest.mock import MagicMock, AsyncMock, patch as _patch
|
||||||
|
from tools.session_search_tool import session_search
|
||||||
|
|
||||||
|
mock_db = MagicMock()
|
||||||
|
# FTS5 hit is in the child delegation session which carries source='telegram'
|
||||||
|
mock_db.search_messages.return_value = [
|
||||||
|
{
|
||||||
|
"session_id": "child_sid",
|
||||||
|
"content": "hello world",
|
||||||
|
"source": "telegram", # child session source — wrong value to surface
|
||||||
|
"session_started": 1709400000,
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_session(session_id):
|
||||||
|
if session_id == "child_sid":
|
||||||
|
return {
|
||||||
|
"id": "child_sid",
|
||||||
|
"parent_session_id": "parent_sid",
|
||||||
|
"source": "telegram",
|
||||||
|
"started_at": 1709400000,
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
}
|
||||||
|
if session_id == "parent_sid":
|
||||||
|
return {
|
||||||
|
"id": "parent_sid",
|
||||||
|
"parent_session_id": None,
|
||||||
|
"source": "api_server", # correct parent source
|
||||||
|
"started_at": 1709300000,
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
mock_db.get_session.side_effect = _get_session
|
||||||
|
mock_db.get_messages_as_conversation.return_value = [
|
||||||
|
{"role": "user", "content": "hello world"},
|
||||||
|
{"role": "assistant", "content": "hi there"},
|
||||||
|
]
|
||||||
|
|
||||||
|
with _patch(
|
||||||
|
"tools.session_search_tool.async_call_llm",
|
||||||
|
new_callable=AsyncMock,
|
||||||
|
side_effect=RuntimeError("no provider"),
|
||||||
|
):
|
||||||
|
result = json.loads(session_search(query="hello world", db=mock_db))
|
||||||
|
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["count"] == 1
|
||||||
|
entry = result["results"][0]
|
||||||
|
assert entry["session_id"] == "parent_sid", "should report resolved parent session ID"
|
||||||
|
assert entry["source"] == "api_server", (
|
||||||
|
f"source should be parent's 'api_server', got {entry['source']!r}"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -486,7 +486,7 @@ def session_search(
|
||||||
}, ensure_ascii=False)
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
summaries = []
|
summaries = []
|
||||||
for (session_id, match_info, conversation_text, _), result in zip(tasks, results):
|
for (session_id, match_info, conversation_text, session_meta), result in zip(tasks, results):
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Failed to summarize session %s: %s",
|
"Failed to summarize session %s: %s",
|
||||||
|
|
@ -494,11 +494,18 @@ def session_search(
|
||||||
)
|
)
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
# Prefer resolved parent session metadata over FTS5 match metadata.
|
||||||
|
# match_info carries source/model from the *child* session that contained
|
||||||
|
# the FTS5 hit; after _resolve_to_parent() the session_id points to the
|
||||||
|
# root, so session_meta has the authoritative platform/source for the
|
||||||
|
# session the user actually cares about (#15909).
|
||||||
entry = {
|
entry = {
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
"when": _format_timestamp(match_info.get("session_started")),
|
"when": _format_timestamp(
|
||||||
"source": match_info.get("source", "unknown"),
|
session_meta.get("started_at") or match_info.get("session_started")
|
||||||
"model": match_info.get("model"),
|
),
|
||||||
|
"source": session_meta.get("source") or match_info.get("source", "unknown"),
|
||||||
|
"model": session_meta.get("model") or match_info.get("model"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue