mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 00:41:43 +08:00
Compare commits
1 Commits
fix/plugin
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7acf16a6f0 |
39
run_agent.py
39
run_agent.py
@@ -1173,6 +1173,26 @@ class AIAgent:
|
|||||||
return
|
return
|
||||||
self._safe_print(*args, **kwargs)
|
self._safe_print(*args, **kwargs)
|
||||||
|
|
||||||
|
def _emit_status(self, message: str) -> None:
|
||||||
|
"""Emit a lifecycle status message to both CLI and gateway channels.
|
||||||
|
|
||||||
|
CLI users see the message via ``_vprint(force=True)`` so it is always
|
||||||
|
visible regardless of verbose/quiet mode. Gateway consumers receive
|
||||||
|
it through ``status_callback("lifecycle", ...)``.
|
||||||
|
|
||||||
|
This helper never raises — exceptions are swallowed so it cannot
|
||||||
|
interrupt the retry/fallback logic.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._vprint(f"{self.log_prefix}{message}", force=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if self.status_callback:
|
||||||
|
try:
|
||||||
|
self.status_callback("lifecycle", message)
|
||||||
|
except Exception:
|
||||||
|
logger.debug("status_callback error in _emit_status", exc_info=True)
|
||||||
|
|
||||||
def _is_direct_openai_url(self, base_url: str = None) -> bool:
|
def _is_direct_openai_url(self, base_url: str = None) -> bool:
|
||||||
"""Return True when a base URL targets OpenAI's native API."""
|
"""Return True when a base URL targets OpenAI's native API."""
|
||||||
url = (base_url or self._base_url_lower).lower()
|
url = (base_url or self._base_url_lower).lower()
|
||||||
@@ -4082,8 +4102,8 @@ class AIAgent:
|
|||||||
or is_native_anthropic
|
or is_native_anthropic
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
self._emit_status(
|
||||||
f"{self.log_prefix}🔄 Primary model failed — switching to fallback: "
|
f"🔄 Primary model failed — switching to fallback: "
|
||||||
f"{fb_model} via {fb_provider}"
|
f"{fb_model} via {fb_provider}"
|
||||||
)
|
)
|
||||||
logging.info(
|
logging.info(
|
||||||
@@ -6085,6 +6105,8 @@ class AIAgent:
|
|||||||
# Eager fallback: empty/malformed responses are a common
|
# Eager fallback: empty/malformed responses are a common
|
||||||
# rate-limit symptom. Switch to fallback immediately
|
# rate-limit symptom. Switch to fallback immediately
|
||||||
# rather than retrying with extended backoff.
|
# rather than retrying with extended backoff.
|
||||||
|
if not self._fallback_activated:
|
||||||
|
self._emit_status("⚠️ Empty/malformed response — switching to fallback...")
|
||||||
if not self._fallback_activated and self._try_activate_fallback():
|
if not self._fallback_activated and self._try_activate_fallback():
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
continue
|
continue
|
||||||
@@ -6119,10 +6141,11 @@ class AIAgent:
|
|||||||
|
|
||||||
if retry_count >= max_retries:
|
if retry_count >= max_retries:
|
||||||
# Try fallback before giving up
|
# Try fallback before giving up
|
||||||
|
self._emit_status(f"⚠️ Max retries ({max_retries}) for invalid responses — trying fallback...")
|
||||||
if self._try_activate_fallback():
|
if self._try_activate_fallback():
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
continue
|
continue
|
||||||
self._vprint(f"{self.log_prefix}❌ Max retries ({max_retries}) exceeded for invalid responses. Giving up.", force=True)
|
self._emit_status(f"❌ Max retries ({max_retries}) exceeded for invalid responses. Giving up.")
|
||||||
logging.error(f"{self.log_prefix}Invalid API response after {max_retries} retries.")
|
logging.error(f"{self.log_prefix}Invalid API response after {max_retries} retries.")
|
||||||
self._persist_session(messages, conversation_history)
|
self._persist_session(messages, conversation_history)
|
||||||
return {
|
return {
|
||||||
@@ -6468,6 +6491,7 @@ class AIAgent:
|
|||||||
or "quota" in error_msg
|
or "quota" in error_msg
|
||||||
)
|
)
|
||||||
if is_rate_limited and not self._fallback_activated:
|
if is_rate_limited and not self._fallback_activated:
|
||||||
|
self._emit_status("⚠️ Rate limited — switching to fallback provider...")
|
||||||
if self._try_activate_fallback():
|
if self._try_activate_fallback():
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
continue
|
continue
|
||||||
@@ -6492,7 +6516,7 @@ class AIAgent:
|
|||||||
"error": f"Request payload too large: max compression attempts ({max_compression_attempts}) reached.",
|
"error": f"Request payload too large: max compression attempts ({max_compression_attempts}) reached.",
|
||||||
"partial": True
|
"partial": True
|
||||||
}
|
}
|
||||||
self._vprint(f"{self.log_prefix}⚠️ Request payload too large (413) — compression attempt {compression_attempts}/{max_compression_attempts}...")
|
self._emit_status(f"⚠️ Request payload too large (413) — compression attempt {compression_attempts}/{max_compression_attempts}...")
|
||||||
|
|
||||||
original_len = len(messages)
|
original_len = len(messages)
|
||||||
messages, active_system_prompt = self._compress_context(
|
messages, active_system_prompt = self._compress_context(
|
||||||
@@ -6501,7 +6525,7 @@ class AIAgent:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if len(messages) < original_len:
|
if len(messages) < original_len:
|
||||||
self._vprint(f"{self.log_prefix} 🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
self._emit_status(f"🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
||||||
time.sleep(2) # Brief pause between compression retries
|
time.sleep(2) # Brief pause between compression retries
|
||||||
restart_with_compressed_messages = True
|
restart_with_compressed_messages = True
|
||||||
break
|
break
|
||||||
@@ -6594,7 +6618,7 @@ class AIAgent:
|
|||||||
|
|
||||||
if len(messages) < original_len or new_ctx and new_ctx < old_ctx:
|
if len(messages) < original_len or new_ctx and new_ctx < old_ctx:
|
||||||
if len(messages) < original_len:
|
if len(messages) < original_len:
|
||||||
self._vprint(f"{self.log_prefix} 🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
self._emit_status(f"🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
||||||
time.sleep(2) # Brief pause between compression retries
|
time.sleep(2) # Brief pause between compression retries
|
||||||
restart_with_compressed_messages = True
|
restart_with_compressed_messages = True
|
||||||
break
|
break
|
||||||
@@ -6640,6 +6664,7 @@ class AIAgent:
|
|||||||
if is_client_error:
|
if is_client_error:
|
||||||
# Try fallback before aborting — a different provider
|
# Try fallback before aborting — a different provider
|
||||||
# may not have the same issue (rate limit, auth, etc.)
|
# may not have the same issue (rate limit, auth, etc.)
|
||||||
|
self._emit_status(f"⚠️ Non-retryable error (HTTP {status_code}) — trying fallback...")
|
||||||
if self._try_activate_fallback():
|
if self._try_activate_fallback():
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
continue
|
continue
|
||||||
@@ -6683,6 +6708,7 @@ class AIAgent:
|
|||||||
|
|
||||||
if retry_count >= max_retries:
|
if retry_count >= max_retries:
|
||||||
# Try fallback before giving up entirely
|
# Try fallback before giving up entirely
|
||||||
|
self._emit_status(f"⚠️ Max retries ({max_retries}) exhausted — trying fallback...")
|
||||||
if self._try_activate_fallback():
|
if self._try_activate_fallback():
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
continue
|
continue
|
||||||
@@ -6708,6 +6734,7 @@ class AIAgent:
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_time = min(2 ** retry_count, 60) # Exponential backoff: 2s, 4s, 8s, 16s, 32s, 60s, 60s
|
wait_time = min(2 ** retry_count, 60) # Exponential backoff: 2s, 4s, 8s, 16s, 32s, 60s, 60s
|
||||||
|
self._emit_status(f"⏳ Retrying in {wait_time}s (attempt {retry_count}/{max_retries})...")
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Retrying API call in %ss (attempt %s/%s) %s error=%s",
|
"Retrying API call in %ss (attempt %s/%s) %s error=%s",
|
||||||
wait_time,
|
wait_time,
|
||||||
|
|||||||
Reference in New Issue
Block a user