Skip to content
5 changes: 5 additions & 0 deletions astrbot/core/agent/context/compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def __init__(
instruction_text: str | None = None,
compression_threshold: float = 0.82,
token_counter: TokenCounter | None = None,
conversation_id: str | None = None,
) -> None:
"""Initialize the LLM summary compressor.

Expand All @@ -139,8 +140,11 @@ def __init__(
exact context. Clamped to 0-0.3.
instruction_text: Custom instruction for summary generation.
compression_threshold: The compression trigger threshold (default: 0.82).
token_counter: Token counter used to preserve recent context.
conversation_id: Conversation UUID for the summary request.
"""
self.provider = provider
self.conversation_id = conversation_id
self.keep_recent_ratio = min(max(float(keep_recent_ratio), 0.0), 0.3)
self.compression_threshold = compression_threshold
self.token_counter = token_counter or EstimateTokenCounter()
Expand Down Expand Up @@ -275,6 +279,7 @@ async def __call__(self, messages: list[Message]) -> list[Message]:
try:
response = await self.provider.text_chat(
contexts=sanitized_summary_contexts,
conversation_id=self.conversation_id,
)
summary_content = (response.completion_text or "").strip()
except Exception as e:
Expand Down
3 changes: 3 additions & 0 deletions astrbot/core/agent/context/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ContextManager:
def __init__(
self,
config: ContextConfig,
conversation_id: str | None = None,
) -> None:
"""Initialize the context manager.

Expand All @@ -22,6 +23,7 @@ def __init__(

Args:
config: The context configuration.
conversation_id: Conversation UUID forwarded to summary requests.
"""
self.config = config

Expand All @@ -36,6 +38,7 @@ def __init__(
keep_recent_ratio=config.llm_compress_keep_recent_ratio,
instruction_text=config.llm_compress_instruction,
token_counter=self.token_counter,
conversation_id=conversation_id,
)
else:
self.compressor = TruncateByTurnsCompressor(
Expand Down
15 changes: 14 additions & 1 deletion astrbot/core/agent/runners/tool_loop_agent_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,18 @@ async def reset(
request_max_retries: int | None = None,
tool_result_overflow_dir: str | None = None,
read_tool: FunctionTool | None = None,
# stable identity for plugin-managed conversations when
# request.conversation is None (e.g. Context.tool_loop_agent)
conversation_id: str | None = None,
**kwargs: T.Any,
) -> None:
self.req = request
# Transient agents need one identity across tool calls and summary requests.
self._conversation_id = (
request.conversation.cid
if request.conversation is not None
else (conversation_id or uuid.uuid4().hex)
)
self.streaming = streaming
self.enforce_max_turns = enforce_max_turns
self.llm_compress_instruction = llm_compress_instruction
Expand All @@ -257,7 +266,8 @@ async def reset(
custom_compressor=self.custom_compressor,
)
self.request_context_manager = ContextManager(
self.request_context_manager_config
self.request_context_manager_config,
conversation_id=self._conversation_id,
)

self.provider = provider
Expand Down Expand Up @@ -505,6 +515,7 @@ async def _iter_llm_responses(
"contexts": self._sanitize_contexts_for_provider(self.run_context.messages),
"func_tool": self._func_tool_for_provider(),
"session_id": self.req.session_id,
"conversation_id": self._conversation_id,
"extra_user_content_parts": self.req.extra_user_content_parts, # list[ContentPart]
"abort_signal": self._abort_signal,
"request_max_retries": self.request_max_retries,
Expand Down Expand Up @@ -1447,6 +1458,7 @@ async def _resolve_tool_exec(
func_tool=param_subset,
model=self.req.model,
session_id=self.req.session_id,
conversation_id=self._conversation_id,
extra_user_content_parts=self.req.extra_user_content_parts,
# tool_choice="required",
abort_signal=self._abort_signal,
Expand Down Expand Up @@ -1479,6 +1491,7 @@ async def _resolve_tool_exec(
func_tool=param_subset,
model=self.req.model,
session_id=self.req.session_id,
conversation_id=self._conversation_id,
extra_user_content_parts=self.req.extra_user_content_parts,
# tool_choice="required",
abort_signal=self._abort_signal,
Expand Down
7 changes: 7 additions & 0 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ async def _request_img_caption(
cfg: dict,
image_urls: list[str],
plugin_context: Context,
conversation_id: str | None = None,
) -> str:
prov = plugin_context.get_provider_by_id(provider_id)
if prov is None:
Expand All @@ -711,6 +712,7 @@ async def _request_img_caption(
llm_resp = await prov.text_chat(
prompt=img_cap_prompt,
image_urls=image_urls,
conversation_id=conversation_id,
)
return llm_resp.completion_text

Expand All @@ -728,6 +730,7 @@ async def _ensure_img_caption(
cfg,
req.image_urls,
plugin_context,
conversation_id=req.conversation.cid if req.conversation else None,
)
if caption:
req.extra_user_content_parts.append(
Expand Down Expand Up @@ -881,6 +884,9 @@ async def _process_quote_message(
llm_resp = await prov.text_chat(
prompt="Please describe the image content.",
image_urls=[image_ref],
conversation_id=req.conversation.cid
if req.conversation
else None,
)
if llm_resp.completion_text:
content_parts.append(
Expand Down Expand Up @@ -1019,6 +1025,7 @@ async def _handle_webchat(

try:
llm_resp = await prov.text_chat(
conversation_id=req.conversation.cid if req.conversation else None,
system_prompt=(
"You are a conversation title generator. "
"Generate a concise title in the same language as the user’s input, "
Expand Down
36 changes: 36 additions & 0 deletions astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,42 @@
"proxy": "",
"custom_headers": {},
},
"OpenCode Go Chat Completions": {
Comment thread
piexian marked this conversation as resolved.
"id": "opencode-go",
"provider": "opencode-go",
"type": "opencode_go_chat_completion",
"provider_type": "chat_completion",
"enable": True,
"key": [],
"api_base": "https://opencode.ai/zen/go/v1",
"timeout": 120,
"proxy": "",
"custom_headers": {},
},
"OpenCode Go Responses": {
"id": "opencode-go-responses",
"provider": "opencode-go",
"type": "opencode_go_responses",
"provider_type": "chat_completion",
"enable": True,
"key": [],
"api_base": "https://opencode.ai/zen/go/v1",
"timeout": 120,
"proxy": "",
"custom_headers": {},
},
"OpenCode Go Messages": {
"id": "opencode-go-messages",
"provider": "opencode-go",
"type": "opencode_go_messages",
"provider_type": "chat_completion",
"enable": True,
"key": [],
"api_base": "https://opencode.ai/zen/go/v1",
"timeout": 120,
"proxy": "",
"custom_headers": {},
},
"Google Gemini": {
"id": "google_gemini",
"provider": "google",
Expand Down
8 changes: 8 additions & 0 deletions astrbot/core/provider/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,14 @@ def dynamic_import_provider(self, type: str) -> None:
from .sources.mirarouter_source import (
ProviderMiraRouter as ProviderMiraRouter,
)
case (
"opencode_go_chat_completion"
| "opencode_go_responses"
| "opencode_go_messages"
):
from .sources.opencode_go_source import (
ProviderOpenCodeGo as ProviderOpenCodeGo,
)
case "openrouter_chat_completion":
from .sources.openrouter_source import (
ProviderOpenRouter as ProviderOpenRouter,
Expand Down
4 changes: 4 additions & 0 deletions astrbot/core/provider/sources/anthropic_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,8 @@ async def text_chat(
model = model or self.get_model()

payloads = {"messages": new_messages, "model": model}
if extra_headers := kwargs.get("extra_headers"):
payloads["extra_headers"] = extra_headers
if func_tool and not func_tool.empty():
payloads["tool_choice"] = tool_choice

Expand Down Expand Up @@ -884,6 +886,8 @@ async def text_chat_stream(
model = model or self.get_model()

payloads = {"messages": new_messages, "model": model}
if extra_headers := kwargs.get("extra_headers"):
payloads["extra_headers"] = extra_headers
if func_tool and not func_tool.empty():
payloads["tool_choice"] = tool_choice

Expand Down
4 changes: 4 additions & 0 deletions astrbot/core/provider/sources/openai_responses_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ async def _prepare_chat_payload(
tool_calls_result: ToolCallsResult | list[ToolCallsResult] | None = None,
model: str | None = None,
extra_user_content_parts: list[ContentPart] | None = None,
extra_headers: dict[str, str] | None = None,
**kwargs: Any,
) -> tuple[dict, list[dict]]:
"""Build a stateless Responses API payload and replayable context.
Expand All @@ -254,6 +255,7 @@ async def _prepare_chat_payload(
tool_calls_result: Function calls and their returned outputs.
model: Optional per-request model override.
extra_user_content_parts: Additional user content blocks.
extra_headers: HTTP headers applied only to this request.
**kwargs: Reserved provider request arguments.

Returns:
Expand Down Expand Up @@ -291,6 +293,8 @@ async def _prepare_chat_payload(
}
if system_prompt:
payloads["instructions"] = system_prompt
if extra_headers:
payloads["extra_headers"] = extra_headers

return payloads, context_query

Expand Down
3 changes: 3 additions & 0 deletions astrbot/core/provider/sources/openai_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ async def _prepare_chat_payload(
tool_calls_result: ToolCallsResult | list[ToolCallsResult] | None = None,
model: str | None = None,
extra_user_content_parts: list[ContentPart] | None = None,
extra_headers: dict[str, str] | None = None,
**kwargs,
) -> tuple:
"""准备聊天所需的有效载荷和上下文"""
Expand Down Expand Up @@ -987,6 +988,8 @@ async def _prepare_chat_payload(
model = model or self.get_model()

payloads = {"messages": context_query, "model": model}
if extra_headers:
payloads["extra_headers"] = extra_headers

self._finally_convert_payload(payloads)

Expand Down
Loading
Loading