Files
test-repo/docs/.vitepress/theme/components/appendix/context-engineering/AgentContextFlow.vue
T

532 lines
13 KiB
Vue
Raw Normal View History

<!--
* Component: AgentContextFlow.vue
* Description: Visualizes the data flow in an agentic system, showing how context is built, used, and updated during interactions.
* Features:
* - Step-by-step visualization of the Agent Loop (Input -> Context -> Decision -> Action -> Observation -> Update)
* - Animation of data flowing between components
* - Metrics display for context usage and cache hits
-->
2026-01-15 20:10:19 +08:00
<script setup>
import { ref, computed, onUnmounted } from 'vue'
2026-01-15 20:10:19 +08:00
const steps = [
{ id: 'input', label: '用户输入', icon: '👤', desc: '用户提出问题或指令' },
{ id: 'context', label: '构建上下文', icon: '📚', desc: '检索历史消息与相关知识' },
{ id: 'reasoning', label: '模型推理', icon: '🧠', desc: 'LLM 分析意图并规划行动' },
{ id: 'action', label: '工具调用', icon: '🔧', desc: '执行外部工具或 API' },
{ id: 'observation', label: '观察结果', icon: '👁️', desc: '获取工具执行的返回结果' },
{ id: 'update', label: '更新上下文', icon: '📝', desc: '将结果追加到记忆中' }
]
2026-01-15 20:10:19 +08:00
const currentStepIndex = ref(-1)
const isAutoPlaying = ref(false)
const iteration = ref(1)
const contextTokens = ref(120)
const cacheHitRate = ref(0)
const autoPlayInterval = ref(null)
// Simulation data
const currentScenario = computed(() => {
const scenarios = [
{ input: "查询北京天气", action: "WeatherAPI.get('Beijing')", result: "晴, 25°C", response: "北京今天晴,气温25度。" },
{ input: "计算 123 * 456", action: "Calculator.mul(123, 456)", result: "56088", response: "结果是 56088。" },
{ input: "搜索最新的 AI 新闻", action: "Search.query('AI news')", result: "Found 5 articles...", response: "最近的 AI 新闻包括..." }
]
return scenarios[(iteration.value - 1) % scenarios.length]
})
2026-01-15 20:10:19 +08:00
const currentStep = computed(() => {
if (currentStepIndex.value === -1) return null
return steps[currentStepIndex.value]
})
2026-01-15 20:10:19 +08:00
const progress = computed(() => {
if (currentStepIndex.value === -1) return 0
return ((currentStepIndex.value + 1) / steps.length) * 100
})
2026-01-15 20:10:19 +08:00
const nextStep = () => {
if (currentStepIndex.value < steps.length - 1) {
currentStepIndex.value++
// Update metrics based on step
if (steps[currentStepIndex.value].id === 'context') {
contextTokens.value += 50
} else if (steps[currentStepIndex.value].id === 'update') {
contextTokens.value += 30
cacheHitRate.value = Math.min(95, cacheHitRate.value + 15)
}
} else {
// Loop finished, prepare next iteration
currentStepIndex.value = -1
iteration.value++
setTimeout(() => {
if (isAutoPlaying.value) nextStep()
}, 500)
}
}
2026-01-15 20:10:19 +08:00
const toggleAutoPlay = () => {
isAutoPlaying.value = !isAutoPlaying.value
if (isAutoPlaying.value) {
if (currentStepIndex.value === steps.length - 1) {
currentStepIndex.value = -1
iteration.value++
}
runAutoPlay()
} else {
clearTimeout(autoPlayInterval.value)
}
}
2026-01-15 20:10:19 +08:00
const runAutoPlay = () => {
if (!isAutoPlaying.value) return
nextStep()
// Determine delay based on current step
const delay = currentStepIndex.value === -1 ? 500 : 1500
autoPlayInterval.value = setTimeout(() => {
runAutoPlay()
}, delay)
}
2026-01-15 20:10:19 +08:00
const reset = () => {
isAutoPlaying.value = false
clearTimeout(autoPlayInterval.value)
currentStepIndex.value = -1
iteration.value = 1
contextTokens.value = 120
cacheHitRate.value = 0
}
2026-01-15 20:10:19 +08:00
onUnmounted(() => {
clearTimeout(autoPlayInterval.value)
})
</script>
2026-01-15 20:10:19 +08:00
<template>
<div class="agent-context-flow">
<!-- Control Panel -->
<div class="control-panel">
<div class="controls-left">
<button
class="action-btn primary"
@click="toggleAutoPlay"
>
{{ isAutoPlaying ? '⏸ 暂停' : '▶ 自动运行' }}
</button>
<button
class="action-btn secondary"
@click="nextStep"
:disabled="isAutoPlaying || currentStepIndex === steps.length - 1"
>
下一步
</button>
<button
class="action-btn text"
@click="reset"
>
重置
</button>
</div>
<div class="status-indicator">
<span class="status-dot" :class="{ active: isAutoPlaying }"></span>
{{ isAutoPlaying ? '运行中' : '等待中' }}
2026-01-15 20:10:19 +08:00
</div>
</div>
<!-- Visualization Area -->
<div class="visualization-area">
<!-- Central Flow Diagram -->
<div class="flow-container">
<div
v-for="(step, index) in steps"
:key="step.id"
class="flow-step"
:class="{
active: index === currentStepIndex,
completed: index < currentStepIndex,
pending: index > currentStepIndex
}"
>
<div class="step-connector" v-if="index > 0"></div>
<div class="step-node">
<div class="step-icon">{{ step.icon }}</div>
<div class="step-label">{{ step.label }}</div>
</div>
2026-01-15 20:10:19 +08:00
</div>
</div>
<!-- Detail View -->
<div class="detail-view">
<transition name="fade" mode="out-in">
<div v-if="currentStep" :key="currentStep.id" class="step-detail">
<div class="detail-header">
<h3>{{ currentStep.icon }} {{ currentStep.label }}</h3>
<p>{{ currentStep.desc }}</p>
</div>
<div class="detail-content">
<div class="scenario-info" v-if="currentStep.id === 'input'">
<strong>输入:</strong> {{ currentScenario.input }}
</div>
<div class="scenario-info" v-else-if="currentStep.id === 'action'">
<strong>执行:</strong> <code>{{ currentScenario.action }}</code>
</div>
<div class="scenario-info" v-else-if="currentStep.id === 'observation'">
<strong>结果:</strong> {{ currentScenario.result }}
</div>
<div class="scenario-info" v-else-if="currentStep.id === 'update'">
上下文已更新准备下一轮对话
</div>
<div class="scenario-info" v-else>
正在处理...
</div>
</div>
</div>
<div v-else class="step-detail placeholder">
<div class="empty-state">
<span class="empty-icon">👋</span>
<p>点击"自动运行""下一步"开始 Agent 流程演示</p>
</div>
</div>
</transition>
2026-01-15 20:10:19 +08:00
</div>
</div>
<!-- Metrics/Info Section -->
<div class="metrics-panel">
<div class="metric-item">
<div class="metric-label">迭代轮次</div>
<div class="metric-value">#{{ iteration }}</div>
</div>
<div class="metric-item">
<div class="metric-label">上下文长度</div>
<div class="metric-value">{{ contextTokens }} tokens</div>
</div>
<div class="metric-item">
<div class="metric-label">KV 缓存命中</div>
<div class="metric-value highlight">{{ cacheHitRate }}%</div>
</div>
<div class="progress-bar-container">
<div class="progress-bar" :style="{ width: `${progress}%` }"></div>
2026-01-15 20:10:19 +08:00
</div>
</div>
</div>
</template>
<style scoped>
.agent-context-flow {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
overflow: hidden;
margin: 1rem 0;
display: flex;
flex-direction: column;
2026-01-15 20:10:19 +08:00
}
/* Control Panel */
.control-panel {
2026-01-15 20:10:19 +08:00
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: var(--vp-c-bg);
border-bottom: 1px solid var(--vp-c-divider);
2026-01-15 20:10:19 +08:00
}
.controls-left {
display: flex;
gap: 0.5rem;
}
.action-btn {
padding: 0.4rem 0.8rem;
2026-01-15 20:10:19 +08:00
border-radius: 6px;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
2026-01-15 20:10:19 +08:00
}
.action-btn.primary {
background-color: var(--vp-c-brand);
color: white;
2026-01-15 20:10:19 +08:00
}
.action-btn.primary:hover {
background-color: var(--vp-c-brand-dark);
2026-01-15 20:10:19 +08:00
}
.action-btn.secondary {
background-color: var(--vp-c-bg-mute);
color: var(--vp-c-text-1);
border-color: var(--vp-c-divider);
2026-01-15 20:10:19 +08:00
}
.action-btn.secondary:hover:not(:disabled) {
background-color: var(--vp-c-bg-soft);
border-color: var(--vp-c-brand);
2026-01-15 20:10:19 +08:00
}
.action-btn.secondary:disabled {
2026-01-15 20:10:19 +08:00
opacity: 0.5;
cursor: not-allowed;
2026-01-15 20:10:19 +08:00
}
.action-btn.text {
background: none;
color: var(--vp-c-text-2);
2026-01-15 20:10:19 +08:00
}
.action-btn.text:hover {
color: var(--vp-c-text-1);
2026-01-15 20:10:19 +08:00
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
2026-01-15 20:10:19 +08:00
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--vp-c-divider);
transition: background-color 0.3s;
2026-01-15 20:10:19 +08:00
}
.status-dot.active {
background-color: var(--vp-c-green);
box-shadow: 0 0 4px var(--vp-c-green);
2026-01-15 20:10:19 +08:00
}
/* Visualization Area */
.visualization-area {
padding: 2rem 1rem;
background-color: var(--vp-c-bg-alt);
min-height: 300px;
display: flex;
flex-direction: column;
gap: 2rem;
align-items: center;
2026-01-15 20:10:19 +08:00
}
.flow-container {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
width: 100%;
max-width: 800px;
2026-01-15 20:10:19 +08:00
}
.flow-step {
display: flex;
align-items: center;
position: relative;
2026-01-15 20:10:19 +08:00
flex: 1;
min-width: 80px;
2026-01-15 20:10:19 +08:00
}
.step-node {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
z-index: 2;
width: 100%;
2026-01-15 20:10:19 +08:00
}
.step-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2026-01-15 20:10:19 +08:00
}
.step-label {
font-size: 0.75rem;
color: var(--vp-c-text-2);
font-weight: 500;
transition: color 0.3s;
2026-01-15 20:10:19 +08:00
text-align: center;
}
.step-connector {
position: absolute;
top: 20px;
left: -50%;
width: 100%;
height: 2px;
background-color: var(--vp-c-divider);
z-index: 1;
transform: translateY(-50%);
transition: background-color 0.5s ease;
2026-01-15 20:10:19 +08:00
}
/* Active State */
.flow-step.active .step-icon {
background-color: var(--vp-c-brand);
2026-01-15 20:10:19 +08:00
border-color: var(--vp-c-brand);
color: white;
transform: scale(1.1);
box-shadow: 0 0 10px var(--vp-c-brand-dimm);
2026-01-15 20:10:19 +08:00
}
.flow-step.active .step-label {
color: var(--vp-c-brand);
2026-01-15 20:10:19 +08:00
font-weight: bold;
}
/* Completed State */
.flow-step.completed .step-icon {
background-color: var(--vp-c-brand-dimm);
border-color: var(--vp-c-brand);
color: var(--vp-c-brand-dark);
}
.flow-step.completed .step-connector {
background-color: var(--vp-c-brand);
2026-01-15 20:10:19 +08:00
}
/* Detail View */
.detail-view {
width: 100%;
max-width: 500px;
min-height: 120px;
background-color: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
position: relative;
2026-01-15 20:10:19 +08:00
}
.detail-header h3 {
margin: 0;
font-size: 1.1rem;
color: var(--vp-c-text-1);
2026-01-15 20:10:19 +08:00
display: flex;
align-items: center;
gap: 0.5rem;
2026-01-15 20:10:19 +08:00
}
.detail-header p {
margin: 0.25rem 0 0.75rem;
font-size: 0.9rem;
color: var(--vp-c-text-2);
2026-01-15 20:10:19 +08:00
}
.detail-content {
padding-top: 0.75rem;
border-top: 1px solid var(--vp-c-divider);
2026-01-15 20:10:19 +08:00
}
.scenario-info code {
background-color: var(--vp-c-bg-mute);
padding: 0.2rem 0.4rem;
2026-01-15 20:10:19 +08:00
border-radius: 4px;
font-family: var(--vp-font-mono);
2026-01-15 20:10:19 +08:00
font-size: 0.85rem;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
2026-01-15 20:10:19 +08:00
color: var(--vp-c-text-3);
text-align: center;
padding: 1rem;
2026-01-15 20:10:19 +08:00
}
.empty-icon {
font-size: 2rem;
margin-bottom: 0.5rem;
2026-01-15 20:10:19 +08:00
}
/* Transitions */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
2026-01-15 20:10:19 +08:00
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(10px);
2026-01-15 20:10:19 +08:00
}
/* Metrics Panel */
.metrics-panel {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
padding: 1rem;
background-color: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
position: relative;
2026-01-15 20:10:19 +08:00
}
.metric-item {
text-align: center;
2026-01-15 20:10:19 +08:00
}
.metric-label {
font-size: 0.8rem;
2026-01-15 20:10:19 +08:00
color: var(--vp-c-text-2);
margin-bottom: 0.2rem;
2026-01-15 20:10:19 +08:00
}
.metric-value {
font-size: 1.1rem;
2026-01-15 20:10:19 +08:00
font-weight: bold;
color: var(--vp-c-text-1);
font-family: var(--vp-font-mono);
2026-01-15 20:10:19 +08:00
}
.metric-value.highlight {
color: var(--vp-c-brand);
2026-01-15 20:10:19 +08:00
}
.progress-bar-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: transparent;
2026-01-15 20:10:19 +08:00
}
.progress-bar {
height: 100%;
background-color: var(--vp-c-brand);
transition: width 0.3s linear;
2026-01-15 20:10:19 +08:00
}
@media (max-width: 640px) {
.flow-container {
flex-direction: column;
gap: 1rem;
}
.step-connector {
width: 2px;
height: 20px;
top: -20px;
left: 50%;
transform: translateX(-50%);
}
.flow-step {
width: 100%;
min-width: unset;
}
.controls-left span {
display: none;
}
2026-01-15 20:10:19 +08:00
}
</style>