MULTI-AGENT TRAVEL SYSTEM
TRAVEL ASSISTANT
A Plan-and-Execute travel assistant that coordinates intent recognition, itinerary planning, enterprise-policy RAG, live information lookup, and persistent user preferences.
THE CHALLENGE
A useful travel assistant must handle changing user intent, external information, company rules, multi-step planning, and long-term preferences without allowing one failed tool call to collapse the entire task.
01 / SYSTEM MAP
A request becomes a plan, not a single prompt
The assistant first classifies intent, then executes specialized skills in priority groups. Policy retrieval, live lookup, preference handling and itinerary planning can contribute to one response.Request
Receive the user message plus recent and persistent context.
Intention
Detect one or more of six intent types and extract required entities.
Plan
Build a priority-aware agent schedule.
Execute
Lazy-load skills and run equal-priority tasks concurrently.
Aggregate
Normalize partial results without collapsing the whole request on one failure.
Remember
Write useful preferences and trip facts into the long-term layer.
02 / REAL BEHAVIOR TRACE
Preference memory changes a later itinerary
One QA sequence first stored explicit preferences and then requested a new trip. The later itinerary reused those preferences instead of asking for them again.- INPUT
- Remember Marriott / Hilton, Air China / China Eastern, window seat, Beijing Chaoyang.
- MEMORY WRITE
- hotel · airline · seat · home_location
- LATER REQUEST
- Plan a five-day business trip to Shanghai.
- REUSED CONTEXT
- Preferred hotels, airlines, seat and Beijing origin appeared in the itinerary.
↳ Short-term: recent dialogue, capped by turn count
↳ Long-term: JSON-backed preferences and trip history
↳ Agent context: selected memory is injected before execution
03 / SELECTED IMPLEMENTATION
Orchestration, loading and failure recovery
These excerpts come from the working Python project—not a diagram-only concept.agents/orchestration_agent.pyPriority-group parallel execution+
Agents at the same priority run through asyncio.gather; exceptions are returned as partial failures instead of crashing aggregation.
async def _execute_parallel_agents(
self, tasks, context, previous_results
):
parallel_coroutines = []
for task in tasks:
agent_name = task.get("agent_name")
priority = task.get("priority", 0)
coroutine = self._execute_agent(
agent_name=agent_name,
context=context,
reason=task.get("reason", ""),
expected_output=task.get("expected_output", ""),
previous_results=previous_results,
)
parallel_coroutines.append((agent_name, priority, coroutine))
execution_results = await asyncio.gather(
*[coro for _, _, coro in parallel_coroutines],
return_exceptions=True,
)
results = []
for (agent_name, priority, _), exec_result in zip(
parallel_coroutines, execution_results
):
# Exceptions are converted to structured partial failures here.
results.append({
"agent_name": agent_name,
"priority": priority,
"result": exec_result,
})
return resultsagents/lazy_agent_registry.pySkills load only when requested+
The registry discovers plugin agents, resolves legacy names, imports the matching module and caches the instance after first use.
def __getitem__(self, agent_name: str):
if agent_name in self.cache:
return self.cache[agent_name]
skill_name = self._resolve_agent_name(agent_name)
script_path = self._skill_map[skill_name]
module_name = f"skills.{skill_name}.agent"
spec = importlib.util.spec_from_file_location(module_name, script_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, AgentBase):
agent_class = obj
break
agent_instance = agent_class(**init_params)
self.cache[agent_name] = agent_instance
return agent_instanceutils/circuit_breaker.pyClosed → open → half-open recovery+
Repeated failures stop further model calls for a recovery window, then allow controlled probing instead of cascading failure.
class CircuitBreaker:
def record_failure(self) -> None:
if self._state == CircuitState.HALF_OPEN:
self._state = CircuitState.OPEN
self._opened_at = time.monotonic()
return
self._failure_count += 1
if self._failure_count >= self.failure_threshold:
self._state = CircuitState.OPEN
self._opened_at = time.monotonic()
def raise_if_open(self) -> None:
if not self.allow_call():
raise CircuitOpenError("服务暂时不可用,请稍后再试")04 / QA EVIDENCE
The test report is useful because it exposes failure—not because it says 10/10
The latest runner completed all ten prompts in 1,288.6 seconds. Reviewing the actual answers found one safe no-answer and one downstream generation failure. The original “success” counter only meant the outer test raised no exception.total_questions 10
runner_completed 10
total_time_sec 1288.6
review_status QUALITY AUDIT REQUIRED
VALIDATION
- The latest project QA report completed all 10 defined end-to-end scenarios.
- The system supports six intent types with a two-layer memory design.
- The architecture demonstrates applied AI engineering beyond prompt writing: orchestration, retrieval, memory, tools, and failure recovery.