fix: portal routing + response speed + IME input
1. Portal unified routing: ws_chat now uses CostAwareRouter uniformly (handles Layer 0/1/2), replacing direct IntentRouter calls. Greeting/chat_mode requests skip IntentRouter LLM call entirely. 2. Response speed: greeting & simple chat now use direct LLM call (no ReAct loop), zero-cost Layer 0 detection. 3. IME input fix: use e.isComposing (native browser property) instead of compositionstart/end for Enter key detection. 4. Test: fix InMemoryMessageBus.request() parameter name timeout -> timeout_seconds.
This commit is contained in:
parent
ae95b56465
commit
32c800d1e4
|
|
@ -171,6 +171,11 @@ class ReActEngine:
|
|||
) -> ReActResult:
|
||||
tools = tools or []
|
||||
tool_schemas = self._build_tool_schemas(tools) if tools else None
|
||||
if tool_schemas:
|
||||
tool_names = [s["function"]["name"] for s in tool_schemas]
|
||||
logger.info(f"ReActEngine executing with {len(tool_schemas)} tools: {tool_names}")
|
||||
else:
|
||||
logger.info("ReActEngine executing with NO tools")
|
||||
|
||||
# Telemetry: record agent request
|
||||
agent_request_counter().add(1, {"agent.name": agent_name, "agent.type": task_type or "react"})
|
||||
|
|
@ -478,6 +483,11 @@ class ReActEngine:
|
|||
"""
|
||||
tools = tools or []
|
||||
tool_schemas = self._build_tool_schemas(tools) if tools else None
|
||||
if tool_schemas:
|
||||
tool_names = [s["function"]["name"] for s in tool_schemas]
|
||||
logger.info(f"ReActEngine executing with {len(tool_schemas)} tools: {tool_names}")
|
||||
else:
|
||||
logger.info("ReActEngine executing with NO tools")
|
||||
|
||||
# 启动轨迹记录
|
||||
if trace_recorder is not None:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
"""Portal API routes - unified chat interface with intent routing"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hmac
|
||||
import json
|
||||
|
|
@ -516,51 +512,81 @@ async def portal_websocket(websocket: WebSocket):
|
|||
if not message_text:
|
||||
continue
|
||||
|
||||
# Save user message
|
||||
_conversation_store.add_message(conv.id, "user", message_text)
|
||||
|
||||
# Resolve skill via IntentRouter
|
||||
# Unified routing via CostAwareRouter (handles Layer 0/1/2)
|
||||
pool = websocket.app.state.agent_pool
|
||||
skill_registry = websocket.app.state.skill_registry
|
||||
llm_gateway = websocket.app.state.llm_gateway
|
||||
intent_router: IntentRouter = websocket.app.state.intent_router
|
||||
cost_aware_router = websocket.app.state.cost_aware_router
|
||||
|
||||
all_skills = skill_registry.list_skills()
|
||||
if not all_skills:
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"data": {"message": "No skills available"},
|
||||
}
|
||||
{"type": "error", "data": {"message": "No skills available"}}
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
routing_result = await intent_router.route(
|
||||
{"query": message_text, "sources": sources}, all_skills
|
||||
# Get default tools for CostAwareRouter routing (only if default skill exists)
|
||||
default_tools = []
|
||||
default_system_prompt = None
|
||||
default_agent = pool.get_agent("default")
|
||||
if default_agent is not None:
|
||||
default_tools = default_agent.get_tools()
|
||||
default_system_prompt = default_agent.get_system_prompt()
|
||||
else:
|
||||
# Fallback to first available skill's tools
|
||||
for skill in all_skills:
|
||||
agent = pool.get_agent(skill.name)
|
||||
if agent is not None:
|
||||
default_tools = agent.get_tools()
|
||||
default_system_prompt = agent.get_system_prompt()
|
||||
break
|
||||
|
||||
# Route via CostAwareRouter (Layer 0/1/2)
|
||||
routing_result = await cost_aware_router.route(
|
||||
content=message_text,
|
||||
skill_registry=skill_registry,
|
||||
intent_router=intent_router,
|
||||
default_tools=default_tools,
|
||||
default_system_prompt=default_system_prompt,
|
||||
default_model="default",
|
||||
default_agent_name="default",
|
||||
session_id=conv.id,
|
||||
transparency="SILENT",
|
||||
)
|
||||
await websocket.send_json(
|
||||
{
|
||||
|
||||
await websocket.send_json({
|
||||
"type": "routing",
|
||||
"skill": routing_result.matched_skill,
|
||||
"method": routing_result.method,
|
||||
"confidence": routing_result.confidence,
|
||||
}
|
||||
)
|
||||
"skill": routing_result.agent_name or "default",
|
||||
"method": routing_result.match_method or "intent",
|
||||
"confidence": routing_result.match_confidence,
|
||||
})
|
||||
|
||||
skill = skill_registry.get(routing_result.matched_skill)
|
||||
agent = pool.get_agent(routing_result.matched_skill)
|
||||
if agent is None:
|
||||
agent = await pool.create_agent_from_skill(routing_result.matched_skill)
|
||||
except (ValueError, RuntimeError) as e:
|
||||
await websocket.send_json(
|
||||
{"type": "error", "data": {"message": str(e)}}
|
||||
# Execute based on routing method
|
||||
if routing_result.match_method in ("greeting", "chat_mode"):
|
||||
# Zero-cost path: direct LLM call, no ReAct loop
|
||||
response = await llm_gateway.chat(
|
||||
messages=[{"role": "user", "content": message_text}],
|
||||
model="default",
|
||||
agent_name="default",
|
||||
task_type="chat",
|
||||
)
|
||||
await websocket.send_json({
|
||||
"type": "result",
|
||||
"data": {"status": "completed", "content": response.content},
|
||||
})
|
||||
continue
|
||||
|
||||
# General path: agent execution
|
||||
agent_name = routing_result.agent_name or "default"
|
||||
agent = pool.get_agent(agent_name)
|
||||
if agent is None:
|
||||
agent = await pool.create_agent_from_skill(agent_name)
|
||||
|
||||
# Execute via ReAct stream
|
||||
react_config = agent.get_react_config()
|
||||
react_engine = ReActEngine(
|
||||
llm_gateway=websocket.app.state.llm_gateway,
|
||||
llm_gateway=llm_gateway,
|
||||
max_steps=react_config["max_steps"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -121,11 +121,11 @@ class TestInMemoryMessageBus:
|
|||
payload={"q": "What is the answer?"},
|
||||
)
|
||||
|
||||
response = await bus.request(request, timeout=5.0)
|
||||
response = await bus.request(request, timeout_seconds=5.0)
|
||||
assert response.payload["answer"] == 42
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_request_timeout(self):
|
||||
async def test_request_timeout_seconds(self):
|
||||
"""请求超时后返回 None。"""
|
||||
bus = InMemoryMessageBus()
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ class TestInMemoryMessageBus:
|
|||
topic="question",
|
||||
)
|
||||
|
||||
result = await bus.request(request, timeout=0.1)
|
||||
result = await bus.request(request, timeout_seconds=0.1)
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Reference in New Issue