This is page 4 of 6. Use http://codebase.md/arthurcolle/openai-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── claude_code
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-312.pyc
│ │ └── mcp_server.cpython-312.pyc
│ ├── claude.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ ├── __init__.cpython-312.pyc
│ │ │ └── serve.cpython-312.pyc
│ │ ├── client.py
│ │ ├── multi_agent_client.py
│ │ └── serve.py
│ ├── config
│ │ └── __init__.py
│ ├── examples
│ │ ├── agents_config.json
│ │ ├── claude_mcp_config.html
│ │ ├── claude_mcp_config.json
│ │ ├── echo_server.py
│ │ └── README.md
│ ├── lib
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ └── __init__.cpython-312.pyc
│ │ ├── context
│ │ │ └── __init__.py
│ │ ├── monitoring
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── server_metrics.cpython-312.pyc
│ │ │ ├── cost_tracker.py
│ │ │ └── server_metrics.py
│ │ ├── providers
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ └── openai.py
│ │ ├── rl
│ │ │ ├── __init__.py
│ │ │ ├── grpo.py
│ │ │ ├── mcts.py
│ │ │ └── tool_optimizer.py
│ │ ├── tools
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ ├── file_tools.cpython-312.pyc
│ │ │ │ └── manager.cpython-312.pyc
│ │ │ ├── ai_tools.py
│ │ │ ├── base.py
│ │ │ ├── code_tools.py
│ │ │ ├── file_tools.py
│ │ │ ├── manager.py
│ │ │ └── search_tools.py
│ │ └── ui
│ │ ├── __init__.py
│ │ └── tool_visualizer.py
│ ├── mcp_server.py
│ ├── README_MCP_CLIENT.md
│ ├── README_MULTI_AGENT.md
│ └── util
│ └── __init__.py
├── claude.py
├── cli.py
├── data
│ └── prompt_templates.json
├── deploy_modal_mcp.py
├── deploy.sh
├── examples
│ ├── agents_config.json
│ └── echo_server.py
├── install.sh
├── mcp_modal_adapter.py
├── mcp_server.py
├── modal_mcp_server.py
├── README_modal_mcp.md
├── README.md
├── requirements.txt
├── setup.py
├── static
│ └── style.css
├── templates
│ └── index.html
└── web-client.html
```
# Files
--------------------------------------------------------------------------------
/claude_code/examples/claude_mcp_config.html:
--------------------------------------------------------------------------------
```html
1 | <!DOCTYPE html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8">
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 | <title>Claude MCP Server Dashboard</title>
7 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8 | <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9 | <style>
10 | :root {
11 | --primary-color: #1a73e8;
12 | --primary-dark: #0d47a1;
13 | --primary-light: #e8f0fe;
14 | --secondary-color: #34a853;
15 | --tertiary-color: #ea4335;
16 | --neutral-color: #f5f5f5;
17 | --success-color: #00c853;
18 | --warning-color: #ffab00;
19 | --danger-color: #f44336;
20 | --info-color: #2196f3;
21 | --text-color: #333;
22 | --text-light: #767676;
23 | --border-radius: 12px;
24 | --box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
25 | --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
26 | --font-primary: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
27 | --font-code: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, 'Courier New', monospace;
28 | --header-height: 70px;
29 | }
30 |
31 | body {
32 | font-family: var(--font-primary);
33 | line-height: 1.6;
34 | color: var(--text-color);
35 | margin: 0;
36 | padding: 0;
37 | background-color: #f9f9f9;
38 | transition: all 0.4s ease;
39 | overflow-x: hidden;
40 | }
41 |
42 | .dark-mode {
43 | --primary-color: #4285f4;
44 | --primary-dark: #5c9aff;
45 | --primary-light: #1c2733;
46 | --neutral-color: #2c2c2c;
47 | --success-color: #00e676;
48 | --warning-color: #ffc400;
49 | --danger-color: #ff5252;
50 | --info-color: #42a5f5;
51 | --text-color: #e0e0e0;
52 | --text-light: #b0b0b0;
53 | background-color: #121212;
54 | }
55 |
56 | .container {
57 | max-width: 1200px;
58 | margin: 0 auto;
59 | padding: 20px;
60 | }
61 |
62 | header {
63 | display: flex;
64 | justify-content: space-between;
65 | align-items: center;
66 | margin-bottom: 30px;
67 | padding-bottom: 15px;
68 | border-bottom: 1px solid #e0e0e0;
69 | }
70 |
71 | .logo {
72 | display: flex;
73 | align-items: center;
74 | gap: 10px;
75 | }
76 |
77 | .header-actions {
78 | display: flex;
79 | gap: 15px;
80 | }
81 |
82 | h1, h2, h3, h4 {
83 | color: var(--primary-color);
84 | margin-top: 0;
85 | }
86 |
87 | .card {
88 | background-color: white;
89 | border-radius: var(--border-radius);
90 | box-shadow: var(--box-shadow);
91 | padding: 25px;
92 | margin-bottom: 25px;
93 | transition: var(--transition);
94 | position: relative;
95 | overflow: hidden;
96 | border: 1px solid rgba(0, 0, 0, 0.03);
97 | }
98 |
99 | .dark-mode .card {
100 | background-color: #222222;
101 | border-color: rgba(255, 255, 255, 0.05);
102 | }
103 |
104 | .card:hover {
105 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
106 | transform: translateY(-3px);
107 | }
108 |
109 | .card-header {
110 | display: flex;
111 | justify-content: space-between;
112 | align-items: center;
113 | margin-bottom: 20px;
114 | border-bottom: 1px solid rgba(0, 0, 0, 0.05);
115 | padding-bottom: 15px;
116 | }
117 |
118 | .dark-mode .card-header {
119 | border-bottom-color: rgba(255, 255, 255, 0.05);
120 | }
121 |
122 | .card-title {
123 | font-size: 1.4rem;
124 | font-weight: 600;
125 | margin: 0;
126 | color: var(--primary-color);
127 | display: flex;
128 | align-items: center;
129 | gap: 10px;
130 | }
131 |
132 | .card-actions {
133 | display: flex;
134 | gap: 10px;
135 | }
136 |
137 | .card-accent {
138 | position: absolute;
139 | top: 0;
140 | left: 0;
141 | height: 4px;
142 | width: 100%;
143 | background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
144 | }
145 |
146 | .card-accent-primary {
147 | background: linear-gradient(90deg, var(--primary-color), #5c9aff);
148 | }
149 |
150 | .card-accent-success {
151 | background: linear-gradient(90deg, var(--success-color), #69f0ae);
152 | }
153 |
154 | .card-accent-warning {
155 | background: linear-gradient(90deg, var(--warning-color), #ffecb3);
156 | }
157 |
158 | .card-accent-danger {
159 | background: linear-gradient(90deg, var(--danger-color), #ff8a80);
160 | }
161 |
162 | .card-footer {
163 | margin-top: 20px;
164 | padding-top: 15px;
165 | border-top: 1px solid rgba(0, 0, 0, 0.05);
166 | display: flex;
167 | justify-content: space-between;
168 | align-items: center;
169 | }
170 |
171 | .dark-mode .card-footer {
172 | border-top-color: rgba(255, 255, 255, 0.05);
173 | }
174 |
175 | .dashboard-grid {
176 | display: grid;
177 | grid-template-columns: 1fr 2fr;
178 | gap: 20px;
179 | }
180 |
181 | @media (max-width: 768px) {
182 | .dashboard-grid {
183 | grid-template-columns: 1fr;
184 | }
185 | }
186 |
187 | .sidebar {
188 | display: flex;
189 | flex-direction: column;
190 | gap: 20px;
191 | }
192 |
193 | code {
194 | background-color: var(--neutral-color);
195 | padding: 2px 4px;
196 | border-radius: 4px;
197 | font-family: 'Courier New', Courier, monospace;
198 | color: var(--text-color);
199 | }
200 |
201 | pre {
202 | background-color: var(--neutral-color);
203 | padding: 15px;
204 | border-radius: 8px;
205 | overflow-x: auto;
206 | margin: 0;
207 | font-family: 'Courier New', Courier, monospace;
208 | color: var(--text-color);
209 | }
210 |
211 | .config-box {
212 | background-color: var(--primary-light);
213 | border: 1px solid var(--primary-color);
214 | border-radius: var(--border-radius);
215 | padding: 20px;
216 | margin: 20px 0;
217 | }
218 |
219 | .note {
220 | background-color: #fffde7;
221 | border-left: 4px solid #ffca28;
222 | padding: 10px 15px;
223 | margin: 15px 0;
224 | }
225 |
226 | .dark-mode .note {
227 | background-color: #332d00;
228 | border-left-color: #ffca28;
229 | }
230 |
231 | .tab-container {
232 | margin-bottom: 30px;
233 | position: relative;
234 | }
235 |
236 | .tabs {
237 | display: flex;
238 | margin-bottom: 25px;
239 | background-color: rgba(255, 255, 255, 0.8);
240 | border-radius: var(--border-radius);
241 | padding: 5px;
242 | position: sticky;
243 | top: 0;
244 | z-index: 100;
245 | backdrop-filter: blur(10px);
246 | -webkit-backdrop-filter: blur(10px);
247 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
248 | }
249 |
250 | .dark-mode .tabs {
251 | background-color: rgba(40, 40, 40, 0.8);
252 | }
253 |
254 | .tab {
255 | padding: 12px 25px;
256 | cursor: pointer;
257 | border-radius: var(--border-radius);
258 | transition: var(--transition);
259 | position: relative;
260 | display: flex;
261 | align-items: center;
262 | gap: 8px;
263 | font-weight: 500;
264 | }
265 |
266 | .tab:hover {
267 | background-color: rgba(0, 0, 0, 0.05);
268 | }
269 |
270 | .dark-mode .tab:hover {
271 | background-color: rgba(255, 255, 255, 0.05);
272 | }
273 |
274 | .tab.active {
275 | background-color: var(--primary-color);
276 | color: white;
277 | font-weight: 600;
278 | box-shadow: 0 4px 12px rgba(26, 115, 232, 0.3);
279 | }
280 |
281 | .dark-mode .tab.active {
282 | box-shadow: 0 4px 12px rgba(66, 133, 244, 0.3);
283 | }
284 |
285 | .tab-indicator {
286 | width: 8px;
287 | height: 8px;
288 | border-radius: 50%;
289 | background-color: var(--success-color);
290 | position: absolute;
291 | top: 10px;
292 | right: 10px;
293 | display: none;
294 | }
295 |
296 | .tab-indicator.active {
297 | display: block;
298 | animation: pulse 2s infinite;
299 | }
300 |
301 | .tab-content {
302 | display: none;
303 | animation: fadeIn 0.4s ease;
304 | }
305 |
306 | .tab-content.active {
307 | display: block;
308 | }
309 |
310 | @keyframes fadeIn {
311 | from { opacity: 0; transform: translateY(10px); }
312 | to { opacity: 1; transform: translateY(0); }
313 | }
314 |
315 | button, .btn {
316 | background-color: var(--primary-color);
317 | color: white;
318 | border: none;
319 | padding: 12px 20px;
320 | border-radius: 8px;
321 | cursor: pointer;
322 | font-size: 15px;
323 | font-weight: 500;
324 | transition: var(--transition);
325 | display: inline-flex;
326 | align-items: center;
327 | justify-content: center;
328 | gap: 10px;
329 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
330 | position: relative;
331 | overflow: hidden;
332 | }
333 |
334 | button:after, .btn:after {
335 | content: '';
336 | position: absolute;
337 | top: 0;
338 | left: 0;
339 | width: 0;
340 | height: 100%;
341 | background-color: rgba(255, 255, 255, 0.1);
342 | transition: width 0.4s ease;
343 | }
344 |
345 | button:hover:after, .btn:hover:after {
346 | width: 100%;
347 | }
348 |
349 | button:hover, .btn:hover {
350 | background-color: var(--primary-dark);
351 | transform: translateY(-2px);
352 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
353 | }
354 |
355 | button:active, .btn:active {
356 | transform: translateY(1px);
357 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
358 | }
359 |
360 | .btn-secondary {
361 | background-color: var(--secondary-color);
362 | background-image: linear-gradient(135deg, var(--secondary-color), #27ae60);
363 | }
364 |
365 | .btn-secondary:hover {
366 | background-color: #2d904c;
367 | }
368 |
369 | .btn-danger {
370 | background-color: var(--danger-color);
371 | background-image: linear-gradient(135deg, var(--danger-color), #d32f2f);
372 | }
373 |
374 | .btn-danger:hover {
375 | background-color: #c62828;
376 | }
377 |
378 | .btn-warning {
379 | background-color: var(--warning-color);
380 | background-image: linear-gradient(135deg, var(--warning-color), #ff8f00);
381 | color: #212121;
382 | }
383 |
384 | .btn-warning:hover {
385 | background-color: #ff8f00;
386 | }
387 |
388 | .btn-info {
389 | background-color: var(--info-color);
390 | background-image: linear-gradient(135deg, var(--info-color), #1976d2);
391 | }
392 |
393 | .btn-info:hover {
394 | background-color: #1976d2;
395 | }
396 |
397 | .btn-ghost {
398 | background-color: transparent;
399 | background-image: none;
400 | color: var(--primary-color);
401 | border: 2px solid var(--primary-color);
402 | box-shadow: none;
403 | }
404 |
405 | .btn-ghost:hover {
406 | background-color: var(--primary-light);
407 | color: var(--primary-dark);
408 | box-shadow: 0 4px 12px rgba(26, 115, 232, 0.12);
409 | }
410 |
411 | .btn-icon {
412 | width: 44px;
413 | height: 44px;
414 | padding: 0;
415 | border-radius: 50%;
416 | display: inline-flex;
417 | align-items: center;
418 | justify-content: center;
419 | }
420 |
421 | .btn-large {
422 | padding: 14px 24px;
423 | font-size: 16px;
424 | }
425 |
426 | .btn-small {
427 | padding: 8px 16px;
428 | font-size: 13px;
429 | }
430 |
431 | .status {
432 | display: flex;
433 | align-items: center;
434 | gap: 8px;
435 | margin-bottom: 10px;
436 | }
437 |
438 | .status-indicator {
439 | display: inline-block;
440 | width: 12px;
441 | height: 12px;
442 | border-radius: 50%;
443 | }
444 |
445 | .status-active {
446 | background-color: #34a853;
447 | box-shadow: 0 0 0 3px rgba(52, 168, 83, 0.2);
448 | animation: pulse 2s infinite;
449 | }
450 |
451 | @keyframes pulse {
452 | 0% { box-shadow: 0 0 0 0 rgba(52, 168, 83, 0.4); }
453 | 70% { box-shadow: 0 0 0 8px rgba(52, 168, 83, 0); }
454 | 100% { box-shadow: 0 0 0 0 rgba(52, 168, 83, 0); }
455 | }
456 |
457 | .tools-grid {
458 | display: grid;
459 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
460 | gap: 15px;
461 | margin-top: 20px;
462 | }
463 |
464 | .tool-card {
465 | background-color: white;
466 | border-radius: var(--border-radius);
467 | padding: 20px;
468 | box-shadow: var(--box-shadow);
469 | border-left: 4px solid var(--primary-color);
470 | transition: var(--transition);
471 | position: relative;
472 | overflow: hidden;
473 | display: flex;
474 | flex-direction: column;
475 | height: 100%;
476 | }
477 |
478 | .dark-mode .tool-card {
479 | background-color: #222222;
480 | }
481 |
482 | .tool-card:hover {
483 | transform: translateY(-5px);
484 | box-shadow: 0 12px 20px rgba(0, 0, 0, 0.1);
485 | }
486 |
487 | .tool-card:after {
488 | content: '';
489 | position: absolute;
490 | bottom: 0;
491 | right: 0;
492 | width: 50px;
493 | height: 50px;
494 | background-color: rgba(0, 0, 0, 0.02);
495 | border-radius: 50% 0 0 0;
496 | transform: scale(1.5);
497 | z-index: 0;
498 | transition: var(--transition);
499 | }
500 |
501 | .dark-mode .tool-card:after {
502 | background-color: rgba(255, 255, 255, 0.02);
503 | }
504 |
505 | .tool-card:hover:after {
506 | width: 150px;
507 | height: 150px;
508 | transform: scale(1);
509 | }
510 |
511 | .tool-card.tool-bash { border-left-color: #f44336; }
512 | .tool-card.tool-view { border-left-color: #2196f3; }
513 | .tool-card.tool-edit { border-left-color: #4caf50; }
514 | .tool-card.tool-glob { border-left-color: #ff9800; }
515 | .tool-card.tool-grep { border-left-color: #9c27b0; }
516 | .tool-card.tool-ls { border-left-color: #00bcd4; }
517 |
518 | .tool-icon {
519 | width: 42px;
520 | height: 42px;
521 | border-radius: 50%;
522 | background-color: var(--primary-light);
523 | display: flex;
524 | align-items: center;
525 | justify-content: center;
526 | margin-bottom: 15px;
527 | color: var(--primary-color);
528 | font-size: 20px;
529 | transition: var(--transition);
530 | }
531 |
532 | .tool-bash .tool-icon { background-color: rgba(244, 67, 54, 0.1); color: #f44336; }
533 | .tool-view .tool-icon { background-color: rgba(33, 150, 243, 0.1); color: #2196f3; }
534 | .tool-edit .tool-icon { background-color: rgba(76, 175, 80, 0.1); color: #4caf50; }
535 | .tool-glob .tool-icon { background-color: rgba(255, 152, 0, 0.1); color: #ff9800; }
536 | .tool-grep .tool-icon { background-color: rgba(156, 39, 176, 0.1); color: #9c27b0; }
537 | .tool-ls .tool-icon { background-color: rgba(0, 188, 212, 0.1); color: #00bcd4; }
538 |
539 | .tool-card:hover .tool-icon {
540 | transform: scale(1.1);
541 | }
542 |
543 | .tool-name {
544 | font-weight: 600;
545 | font-size: 16px;
546 | color: var(--text-color);
547 | margin-bottom: 8px;
548 | display: flex;
549 | align-items: center;
550 | gap: 8px;
551 | z-index: 1;
552 | }
553 |
554 | .tool-description {
555 | font-size: 14px;
556 | color: var(--text-light);
557 | flex-grow: 1;
558 | z-index: 1;
559 | }
560 |
561 | .tool-stats {
562 | display: flex;
563 | align-items: center;
564 | justify-content: space-between;
565 | margin-top: 15px;
566 | padding-top: 10px;
567 | border-top: 1px solid rgba(0, 0, 0, 0.05);
568 | font-size: 13px;
569 | color: var(--text-light);
570 | z-index: 1;
571 | }
572 |
573 | .dark-mode .tool-stats {
574 | border-top-color: rgba(255, 255, 255, 0.05);
575 | }
576 |
577 | .tool-usage {
578 | display: flex;
579 | align-items: center;
580 | gap: 5px;
581 | }
582 |
583 | .tool-latency {
584 | display: flex;
585 | align-items: center;
586 | gap: 5px;
587 | }
588 |
589 | .stats-container {
590 | display: grid;
591 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
592 | gap: 15px;
593 | margin-bottom: 20px;
594 | }
595 |
596 | .stat-card {
597 | background-color: white;
598 | border-radius: var(--border-radius);
599 | padding: 20px;
600 | box-shadow: var(--box-shadow);
601 | text-align: center;
602 | transition: var(--transition);
603 | position: relative;
604 | overflow: hidden;
605 | display: flex;
606 | flex-direction: column;
607 | align-items: center;
608 | border: 1px solid rgba(0, 0, 0, 0.03);
609 | }
610 |
611 | .dark-mode .stat-card {
612 | background-color: #222222;
613 | border-color: rgba(255, 255, 255, 0.05);
614 | }
615 |
616 | .stat-card:hover {
617 | transform: translateY(-5px);
618 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
619 | }
620 |
621 | .stat-card:before {
622 | content: '';
623 | position: absolute;
624 | top: 0;
625 | left: 0;
626 | width: 100%;
627 | height: 4px;
628 | background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
629 | }
630 |
631 | .stat-card.primary:before {
632 | background: linear-gradient(90deg, var(--primary-color), #5c9aff);
633 | }
634 |
635 | .stat-card.success:before {
636 | background: linear-gradient(90deg, var(--success-color), #69f0ae);
637 | }
638 |
639 | .stat-card.warning:before {
640 | background: linear-gradient(90deg, var(--warning-color), #ffecb3);
641 | }
642 |
643 | .stat-card.danger:before {
644 | background: linear-gradient(90deg, var(--danger-color), #ff8a80);
645 | }
646 |
647 | .stat-icon {
648 | font-size: 24px;
649 | height: 50px;
650 | width: 50px;
651 | display: flex;
652 | align-items: center;
653 | justify-content: center;
654 | border-radius: 50%;
655 | margin-bottom: 10px;
656 | color: white;
657 | background-color: var(--primary-color);
658 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
659 | }
660 |
661 | .stat-card.primary .stat-icon {
662 | background-color: var(--primary-color);
663 | }
664 |
665 | .stat-card.success .stat-icon {
666 | background-color: var(--success-color);
667 | }
668 |
669 | .stat-card.warning .stat-icon {
670 | background-color: var(--warning-color);
671 | }
672 |
673 | .stat-card.danger .stat-icon {
674 | background-color: var(--danger-color);
675 | }
676 |
677 | .stat-value {
678 | font-size: 32px;
679 | font-weight: 700;
680 | color: var(--text-color);
681 | margin: 15px 0 5px;
682 | line-height: 1;
683 | }
684 |
685 | .stat-card.primary .stat-value {
686 | color: var(--primary-color);
687 | }
688 |
689 | .stat-card.success .stat-value {
690 | color: var(--success-color);
691 | }
692 |
693 | .stat-card.warning .stat-value {
694 | color: var(--warning-color);
695 | }
696 |
697 | .stat-card.danger .stat-value {
698 | color: var(--danger-color);
699 | }
700 |
701 | .stat-label {
702 | font-size: 14px;
703 | font-weight: 500;
704 | color: var(--text-light);
705 | text-transform: uppercase;
706 | letter-spacing: 0.5px;
707 | }
708 |
709 | .stat-trend {
710 | display: flex;
711 | align-items: center;
712 | gap: 5px;
713 | margin-top: 8px;
714 | font-size: 13px;
715 | }
716 |
717 | .stat-trend.up {
718 | color: var(--success-color);
719 | }
720 |
721 | .stat-trend.down {
722 | color: var(--danger-color);
723 | }
724 |
725 | .chart-container {
726 | position: relative;
727 | height: 300px;
728 | margin-top: 20px;
729 | border-radius: var(--border-radius);
730 | background-color: rgba(255, 255, 255, 0.5);
731 | padding: 15px;
732 | border: 1px solid rgba(0, 0, 0, 0.03);
733 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
734 | transition: var(--transition);
735 | }
736 |
737 | .dark-mode .chart-container {
738 | background-color: rgba(50, 50, 50, 0.2);
739 | border-color: rgba(255, 255, 255, 0.05);
740 | }
741 |
742 | .chart-container:hover {
743 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
744 | }
745 |
746 | .chart-header {
747 | display: flex;
748 | justify-content: space-between;
749 | align-items: center;
750 | margin-bottom: 10px;
751 | }
752 |
753 | .chart-title {
754 | font-weight: 600;
755 | color: var(--primary-color);
756 | display: flex;
757 | align-items: center;
758 | gap: 8px;
759 | }
760 |
761 | .chart-controls {
762 | display: flex;
763 | gap: 10px;
764 | }
765 |
766 | .chart-legend {
767 | display: flex;
768 | gap: 15px;
769 | margin-top: 10px;
770 | flex-wrap: wrap;
771 | }
772 |
773 | .chart-legend-item {
774 | display: flex;
775 | align-items: center;
776 | gap: 5px;
777 | font-size: 13px;
778 | }
779 |
780 | .chart-legend-color {
781 | width: 12px;
782 | height: 12px;
783 | border-radius: 3px;
784 | }
785 |
786 | .settings-grid {
787 | display: grid;
788 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
789 | gap: 20px;
790 | }
791 |
792 | .form-group {
793 | margin-bottom: 15px;
794 | }
795 |
796 | label {
797 | display: block;
798 | margin-bottom: 5px;
799 | font-weight: bold;
800 | }
801 |
802 | input, select, textarea {
803 | width: 100%;
804 | padding: 10px;
805 | border: 1px solid #ddd;
806 | border-radius: 4px;
807 | font-size: 14px;
808 | background-color: white;
809 | color: var(--text-color);
810 | }
811 |
812 | .dark-mode input,
813 | .dark-mode select,
814 | .dark-mode textarea {
815 | background-color: #333;
816 | border-color: #444;
817 | color: #e0e0e0;
818 | }
819 |
820 | .toggle-container {
821 | display: flex;
822 | align-items: center;
823 | }
824 |
825 | .toggle {
826 | position: relative;
827 | display: inline-block;
828 | width: 50px;
829 | height: 24px;
830 | margin-right: 10px;
831 | }
832 |
833 | .toggle input {
834 | opacity: 0;
835 | width: 0;
836 | height: 0;
837 | }
838 |
839 | .toggle-slider {
840 | position: absolute;
841 | cursor: pointer;
842 | top: 0;
843 | left: 0;
844 | right: 0;
845 | bottom: 0;
846 | background-color: #ccc;
847 | transition: .4s;
848 | border-radius: 34px;
849 | }
850 |
851 | .toggle-slider:before {
852 | position: absolute;
853 | content: "";
854 | height: 18px;
855 | width: 18px;
856 | left: 3px;
857 | bottom: 3px;
858 | background-color: white;
859 | transition: .4s;
860 | border-radius: 50%;
861 | }
862 |
863 | input:checked + .toggle-slider {
864 | background-color: var(--primary-color);
865 | }
866 |
867 | input:checked + .toggle-slider:before {
868 | transform: translateX(26px);
869 | }
870 |
871 | .modal {
872 | display: none;
873 | position: fixed;
874 | z-index: 1000;
875 | left: 0;
876 | top: 0;
877 | width: 100%;
878 | height: 100%;
879 | background-color: rgba(0, 0, 0, 0.5);
880 | animation: fadeIn 0.3s;
881 | }
882 |
883 | @keyframes fadeIn {
884 | from { opacity: 0; }
885 | to { opacity: 1; }
886 | }
887 |
888 | .modal-content {
889 | background-color: white;
890 | margin: 10% auto;
891 | padding: 25px;
892 | border-radius: var(--border-radius);
893 | max-width: 600px;
894 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
895 | position: relative;
896 | animation: slideIn 0.3s;
897 | }
898 |
899 | .dark-mode .modal-content {
900 | background-color: #2a2a2a;
901 | }
902 |
903 | @keyframes slideIn {
904 | from { transform: translateY(-50px); opacity: 0; }
905 | to { transform: translateY(0); opacity: 1; }
906 | }
907 |
908 | .close {
909 | position: absolute;
910 | right: 20px;
911 | top: 15px;
912 | font-size: 22px;
913 | cursor: pointer;
914 | color: var(--text-light);
915 | }
916 |
917 | .close:hover {
918 | color: var(--text-color);
919 | }
920 |
921 | .loader {
922 | display: inline-block;
923 | width: 20px;
924 | height: 20px;
925 | border: 3px solid rgba(255,255,255,.3);
926 | border-radius: 50%;
927 | border-top-color: white;
928 | animation: spin 1s ease-in-out infinite;
929 | }
930 |
931 | @keyframes spin {
932 | to { transform: rotate(360deg); }
933 | }
934 |
935 | .notification {
936 | position: fixed;
937 | bottom: 20px;
938 | right: 20px;
939 | padding: 15px 20px;
940 | background-color: var(--primary-color);
941 | color: white;
942 | border-radius: var(--border-radius);
943 | box-shadow: var(--box-shadow);
944 | display: none;
945 | z-index: 1000;
946 | animation: slideUp 0.3s;
947 | }
948 |
949 | @keyframes slideUp {
950 | from { transform: translateY(30px); opacity: 0; }
951 | to { transform: translateY(0); opacity: 1; }
952 | }
953 |
954 | .code-with-copy {
955 | position: relative;
956 | }
957 |
958 | .copy-button {
959 | position: absolute;
960 | top: 5px;
961 | right: 5px;
962 | padding: 5px;
963 | background-color: rgba(255, 255, 255, 0.8);
964 | border-radius: 4px;
965 | cursor: pointer;
966 | font-size: 12px;
967 | color: var(--text-color);
968 | border: none;
969 | }
970 |
971 | .dark-mode .copy-button {
972 | background-color: rgba(50, 50, 50, 0.8);
973 | color: var(--text-light);
974 | }
975 |
976 | .config-form {
977 | margin-top: 20px;
978 | }
979 |
980 | .action-buttons {
981 | display: flex;
982 | gap: 10px;
983 | margin-top: 20px;
984 | }
985 |
986 | .server-info {
987 | display: flex;
988 | flex-direction: column;
989 | gap: 5px;
990 | }
991 |
992 | .info-row {
993 | display: flex;
994 | justify-content: space-between;
995 | }
996 |
997 | .info-label {
998 | font-weight: bold;
999 | color: var(--text-light);
1000 | }
1001 |
1002 | .info-value {
1003 | color: var(--text-color);
1004 | }
1005 |
1006 | .badge {
1007 | display: inline-block;
1008 | padding: 2px 8px;
1009 | border-radius: 12px;
1010 | font-size: 12px;
1011 | font-weight: bold;
1012 | }
1013 |
1014 | .badge-primary {
1015 | background-color: var(--primary-light);
1016 | color: var(--primary-color);
1017 | }
1018 |
1019 | .dark-mode .badge-primary {
1020 | background-color: rgba(66, 133, 244, 0.2);
1021 | }
1022 |
1023 | .badge-success {
1024 | background-color: rgba(52, 168, 83, 0.1);
1025 | color: #34a853;
1026 | }
1027 |
1028 | .badge-warning {
1029 | background-color: rgba(251, 188, 5, 0.1);
1030 | color: #fbbc05;
1031 | }
1032 |
1033 | .badge-danger {
1034 | background-color: rgba(234, 67, 53, 0.1);
1035 | color: #ea4335;
1036 | }
1037 |
1038 | .resource-list {
1039 | margin-top: 15px;
1040 | }
1041 |
1042 | .resource-item {
1043 | padding: 10px;
1044 | border-radius: 4px;
1045 | margin-bottom: 5px;
1046 | background-color: var(--neutral-color);
1047 | display: flex;
1048 | justify-content: space-between;
1049 | align-items: center;
1050 | }
1051 |
1052 | .dark-mode .resource-item {
1053 | background-color: #333;
1054 | }
1055 |
1056 | .resource-uri {
1057 | font-family: 'Courier New', Courier, monospace;
1058 | color: var(--primary-color);
1059 | }
1060 |
1061 | .theme-switch {
1062 | cursor: pointer;
1063 | color: var(--text-light);
1064 | transition: var(--transition);
1065 | }
1066 |
1067 | .theme-switch:hover {
1068 | color: var(--primary-color);
1069 | }
1070 |
1071 | .clipboard-success {
1072 | position: fixed;
1073 | top: 20px;
1074 | right: 20px;
1075 | background-color: var(--secondary-color);
1076 | color: white;
1077 | padding: 10px 15px;
1078 | border-radius: var(--border-radius);
1079 | box-shadow: var(--box-shadow);
1080 | animation: fadeInOut 2s;
1081 | z-index: 1000;
1082 | }
1083 |
1084 | @keyframes fadeInOut {
1085 | 0% { opacity: 0; transform: translateY(-20px); }
1086 | 20% { opacity: 1; transform: translateY(0); }
1087 | 80% { opacity: 1; transform: translateY(0); }
1088 | 100% { opacity: 0; transform: translateY(-20px); }
1089 | }
1090 | </style>
1091 | </head>
1092 | <body>
1093 | <div class="container">
1094 | <header>
1095 | <div class="logo">
1096 | <i class="fas fa-robot fa-2x" style="color: #4285f4;"></i>
1097 | <div>
1098 | <h1>Claude MCP Server Dashboard</h1>
1099 | <p style="margin: 0;">Advanced Configuration and Monitoring</p>
1100 | </div>
1101 | </div>
1102 | <div class="header-actions">
1103 | <button id="refresh-btn" class="btn-ghost"><i class="fas fa-sync-alt"></i> Refresh</button>
1104 | <div class="theme-switch" id="theme-toggle">
1105 | <i class="fas fa-moon"></i>
1106 | </div>
1107 | </div>
1108 | </header>
1109 |
1110 | <div class="status">
1111 | <span class="status-indicator status-active"></span>
1112 | <span id="status-text">MCP Server Running - <span id="uptime">10 minutes</span></span>
1113 | </div>
1114 |
1115 | <div class="stats-container">
1116 | <div class="stat-card primary">
1117 | <div class="stat-icon">
1118 | <i class="fas fa-tools"></i>
1119 | </div>
1120 | <div id="tools-count" class="stat-value">7</div>
1121 | <div class="stat-label">Available Tools</div>
1122 | <div class="stat-trend up">
1123 | <i class="fas fa-arrow-up"></i>
1124 | <span>2 new</span>
1125 | </div>
1126 | </div>
1127 | <div class="stat-card success">
1128 | <div class="stat-icon">
1129 | <i class="fas fa-plug"></i>
1130 | </div>
1131 | <div id="connections-count" class="stat-value">1</div>
1132 | <div class="stat-label">Active Connections</div>
1133 | <div class="stat-trend up">
1134 | <i class="fas fa-arrow-up"></i>
1135 | <span>Active now</span>
1136 | </div>
1137 | </div>
1138 | <div class="stat-card danger">
1139 | <div class="stat-icon">
1140 | <i class="fas fa-exchange-alt"></i>
1141 | </div>
1142 | <div id="requests-count" class="stat-value">45</div>
1143 | <div class="stat-label">Total Requests</div>
1144 | <div class="stat-trend up">
1145 | <i class="fas fa-arrow-up"></i>
1146 | <span>+12 today</span>
1147 | </div>
1148 | </div>
1149 | <div class="stat-card warning">
1150 | <div class="stat-icon">
1151 | <i class="fas fa-box"></i>
1152 | </div>
1153 | <div id="resources-count" class="stat-value">3</div>
1154 | <div class="stat-label">Resources</div>
1155 | <div class="stat-trend">
1156 | <i class="fas fa-minus"></i>
1157 | <span>No change</span>
1158 | </div>
1159 | </div>
1160 | </div>
1161 |
1162 | <div class="tab-container">
1163 | <div class="tabs">
1164 | <div class="tab active" data-tab="config">
1165 | <i class="fas fa-cog"></i>
1166 | Configuration
1167 | <span class="tab-indicator"></span>
1168 | </div>
1169 | <div class="tab" data-tab="monitoring">
1170 | <i class="fas fa-chart-line"></i>
1171 | Monitoring
1172 | <span class="tab-indicator active"></span>
1173 | </div>
1174 | <div class="tab" data-tab="tools">
1175 | <i class="fas fa-tools"></i>
1176 | Tools
1177 | <span class="tab-indicator"></span>
1178 | </div>
1179 | <div class="tab" data-tab="clients">
1180 | <i class="fas fa-users"></i>
1181 | Clients
1182 | <span class="tab-indicator"></span>
1183 | </div>
1184 | <div class="tab" data-tab="settings">
1185 | <i class="fas fa-sliders-h"></i>
1186 | Settings
1187 | <span class="tab-indicator"></span>
1188 | </div>
1189 | </div>
1190 |
1191 | <div class="tab-content active" id="config-tab">
1192 | <div class="card">
1193 | <div class="card-accent card-accent-primary"></div>
1194 | <div class="card-header">
1195 | <h2 class="card-title"><i class="fas fa-cog"></i> Claude Desktop Configuration</h2>
1196 | <div class="card-actions">
1197 | <button class="btn-icon btn-ghost"><i class="fas fa-question-circle"></i></button>
1198 | <button class="btn-icon btn-ghost"><i class="fas fa-external-link-alt"></i></button>
1199 | </div>
1200 | </div>
1201 | <p>Connect your Claude Desktop client to this MCP server by following these steps:</p>
1202 |
1203 | <ol>
1204 | <li>Open Claude Desktop application</li>
1205 | <li>Click on Settings (gear icon)</li>
1206 | <li>Navigate to "Model Context Protocol" section</li>
1207 | <li>Click "Add New Server"</li>
1208 | <li>Enter the configuration below or use the auto-configuration options</li>
1209 | </ol>
1210 |
1211 | <div class="config-box">
1212 | <h3>Server Configuration</h3>
1213 | <div class="code-with-copy">
1214 | <pre><code id="server-config">{
1215 | "name": "Claude Code Tools",
1216 | "type": "local_process",
1217 | "command": "python",
1218 | "args": ["claude.py", "serve"],
1219 | "workingDirectory": "/path/to/claude-code-directory",
1220 | "environment": {},
1221 | "description": "A Model Context Protocol server for Claude Code tools"
1222 | }</code></pre>
1223 | <button class="copy-button" onclick="copyConfig()"><i class="fas fa-copy"></i></button>
1224 | </div>
1225 | </div>
1226 |
1227 | <div class="action-buttons">
1228 | <a href="resource:config://json" download="claude_mcp_config.json" class="btn">
1229 | <i class="fas fa-download"></i> Download Configuration File
1230 | </a>
1231 | <button class="btn btn-secondary" onclick="openQRModal()">
1232 | <i class="fas fa-qrcode"></i> Show QR Code
1233 | </button>
1234 | </div>
1235 |
1236 | <div class="note">
1237 | <h3><i class="fas fa-shield-alt"></i> Security Note</h3>
1238 | <p>The Claude Code MCP server provides access to your file system and command execution. Only connect to it from trusted clients and be cautious about the operations you perform.</p>
1239 | </div>
1240 | </div>
1241 |
1242 | <div class="card">
1243 | <div class="card-accent card-accent-primary"></div>
1244 | <div class="card-header">
1245 | <h2 class="card-title"><i class="fas fa-wrench"></i> Advanced Client Options</h2>
1246 | <div class="card-actions">
1247 | <button class="btn-icon btn-ghost"><i class="fas fa-cog"></i></button>
1248 | </div>
1249 | </div>
1250 |
1251 | <div class="config-form">
1252 | <h3>Customize Your Configuration</h3>
1253 | <div class="form-group">
1254 | <label for="server-name">Server Name</label>
1255 | <input type="text" id="server-name" value="Claude Code Tools">
1256 | </div>
1257 | <div class="form-group">
1258 | <label for="working-dir">Working Directory</label>
1259 | <input type="text" id="working-dir" value="/path/to/claude-code-directory">
1260 | </div>
1261 | <div class="form-group">
1262 | <label for="server-env">Environment Variables (JSON)</label>
1263 | <textarea id="server-env" rows="3">{}</textarea>
1264 | </div>
1265 | <button class="btn" onclick="updateConfig()"><i class="fas fa-sync"></i> Update Configuration</button>
1266 | </div>
1267 |
1268 | <h3>Multi-Agent Configuration</h3>
1269 | <p>For complex problems, use multi-agent mode with synchronized agents:</p>
1270 | <div class="code-with-copy">
1271 | <pre><code>python claude.py mcp-multi-agent path/to/server.py --config examples/agents_config.json</code></pre>
1272 | <button class="copy-button" onclick="copyMultiAgentCmd()"><i class="fas fa-copy"></i></button>
1273 | </div>
1274 | <button class="btn btn-ghost" onclick="openAgentEditor()"><i class="fas fa-users-cog"></i> Configure Agent Roles</button>
1275 | </div>
1276 | </div>
1277 |
1278 | <div class="tab-content" id="monitoring-tab">
1279 | <div class="dashboard-grid">
1280 | <div class="sidebar">
1281 | <div class="card">
1282 | <h3><i class="fas fa-info-circle"></i> Server Information</h3>
1283 | <div class="server-info">
1284 | <div class="info-row">
1285 | <span class="info-label">Status:</span>
1286 | <span class="info-value"><span class="badge badge-success">Running</span></span>
1287 | </div>
1288 | <div class="info-row">
1289 | <span class="info-label">Version:</span>
1290 | <span class="info-value">0.1.0</span>
1291 | </div>
1292 | <div class="info-row">
1293 | <span class="info-label">Host:</span>
1294 | <span class="info-value">localhost:8000</span>
1295 | </div>
1296 | <div class="info-row">
1297 | <span class="info-label">Uptime:</span>
1298 | <span class="info-value" id="server-uptime">10 minutes</span>
1299 | </div>
1300 | <div class="info-row">
1301 | <span class="info-label">Python:</span>
1302 | <span class="info-value">3.10.4</span>
1303 | </div>
1304 | <div class="info-row">
1305 | <span class="info-label">FastMCP:</span>
1306 | <span class="info-value">0.4.1</span>
1307 | </div>
1308 | </div>
1309 | </div>
1310 |
1311 | <div class="card">
1312 | <h3><i class="fas fa-box"></i> Resources</h3>
1313 | <p>Available resources:</p>
1314 | <div class="resource-list">
1315 | <div class="resource-item">
1316 | <span class="resource-uri">system://info</span>
1317 | <span class="badge badge-primary">GET</span>
1318 | </div>
1319 | <div class="resource-item">
1320 | <span class="resource-uri">config://json</span>
1321 | <span class="badge badge-primary">GET</span>
1322 | </div>
1323 | <div class="resource-item">
1324 | <span class="resource-uri">filesystem://{path}</span>
1325 | <span class="badge badge-primary">GET</span>
1326 | </div>
1327 | <div class="resource-item">
1328 | <span class="resource-uri">file://{file_path}</span>
1329 | <span class="badge badge-primary">GET</span>
1330 | </div>
1331 | </div>
1332 | </div>
1333 | </div>
1334 |
1335 | <div class="main-content">
1336 | <div class="card">
1337 | <div class="card-accent card-accent-primary"></div>
1338 | <div class="card-header">
1339 | <h2 class="card-title"><i class="fas fa-chart-line"></i> Request Activity</h2>
1340 | <div class="card-actions">
1341 | <button class="btn-icon btn-ghost"><i class="fas fa-expand"></i></button>
1342 | <button class="btn-icon btn-ghost"><i class="fas fa-download"></i></button>
1343 | <button class="btn-icon btn-ghost"><i class="fas fa-sync-alt"></i></button>
1344 | </div>
1345 | </div>
1346 | <div class="chart-container">
1347 | <div class="chart-header">
1348 | <div class="chart-title"><i class="fas fa-chart-line"></i> Real-time Request Activity</div>
1349 | <div class="chart-controls">
1350 | <button class="btn-small">Hourly</button>
1351 | <button class="btn-small">Daily</button>
1352 | <button class="btn-small">Weekly</button>
1353 | </div>
1354 | </div>
1355 | <canvas id="requestsChart"></canvas>
1356 | <div class="chart-legend">
1357 | <div class="chart-legend-item">
1358 | <div class="chart-legend-color" style="background-color: #4285f4;"></div>
1359 | <span>Tool Calls</span>
1360 | </div>
1361 | <div class="chart-legend-item">
1362 | <div class="chart-legend-color" style="background-color: #34a853;"></div>
1363 | <span>Resource Requests</span>
1364 | </div>
1365 | </div>
1366 | </div>
1367 | </div>
1368 |
1369 | <div class="card">
1370 | <h3><i class="fas fa-clipboard-list"></i> Recent Activity</h3>
1371 | <div class="activity-log" id="activity-log">
1372 | <pre style="max-height: 300px; overflow-y: auto;">
1373 | [2025-03-07 13:45:20] Server started on localhost:8000
1374 | [2025-03-07 13:46:05] New connection from 127.0.0.1
1375 | [2025-03-07 13:46:10] Tool call: View
1376 | [2025-03-07 13:46:15] Resource request: system://info
1377 | [2025-03-07 13:46:30] Tool call: GlobTool
1378 | [2025-03-07 13:46:45] Tool call: GrepTool
1379 | [2025-03-07 13:47:00] Tool call: Bash
1380 | [2025-03-07 13:47:15] Resource request: file://README.md
1381 | </pre>
1382 | </div>
1383 | </div>
1384 | </div>
1385 | </div>
1386 | </div>
1387 |
1388 | <div class="tab-content" id="tools-tab">
1389 | <div class="card">
1390 | <h2><i class="fas fa-tools"></i> Available Tools</h2>
1391 | <p>The Claude Code MCP Server provides access to the following tools:</p>
1392 |
1393 | <div class="tools-grid">
1394 | <div class="tool-card tool-view">
1395 | <div class="tool-icon"><i class="fas fa-eye"></i></div>
1396 | <div class="tool-name">View</div>
1397 | <div class="tool-description">Read files with optional line limits and supports syntax highlighting for code files</div>
1398 | <div class="tool-stats">
1399 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 32 calls</div>
1400 | <div class="tool-latency"><i class="fas fa-clock"></i> 45ms avg</div>
1401 | </div>
1402 | </div>
1403 | <div class="tool-card tool-edit">
1404 | <div class="tool-icon"><i class="fas fa-edit"></i></div>
1405 | <div class="tool-name">Edit</div>
1406 | <div class="tool-description">Edit files with precise text replacement and context-aware modifications</div>
1407 | <div class="tool-stats">
1408 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 18 calls</div>
1409 | <div class="tool-latency"><i class="fas fa-clock"></i> 62ms avg</div>
1410 | </div>
1411 | </div>
1412 | <div class="tool-card tool-edit">
1413 | <div class="tool-icon"><i class="fas fa-file-alt"></i></div>
1414 | <div class="tool-name">Replace</div>
1415 | <div class="tool-description">Overwrite existing files or create new files with specified content</div>
1416 | <div class="tool-stats">
1417 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 7 calls</div>
1418 | <div class="tool-latency"><i class="fas fa-clock"></i> 38ms avg</div>
1419 | </div>
1420 | </div>
1421 | <div class="tool-card tool-glob">
1422 | <div class="tool-icon"><i class="fas fa-search"></i></div>
1423 | <div class="tool-name">GlobTool</div>
1424 | <div class="tool-description">Find files by pattern matching with support for complex glob patterns</div>
1425 | <div class="tool-stats">
1426 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 29 calls</div>
1427 | <div class="tool-latency"><i class="fas fa-clock"></i> 74ms avg</div>
1428 | </div>
1429 | </div>
1430 | <div class="tool-card tool-grep">
1431 | <div class="tool-icon"><i class="fas fa-search-plus"></i></div>
1432 | <div class="tool-name">GrepTool</div>
1433 | <div class="tool-description">Search file contents using powerful regular expressions with filters</div>
1434 | <div class="tool-stats">
1435 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 24 calls</div>
1436 | <div class="tool-latency"><i class="fas fa-clock"></i> 112ms avg</div>
1437 | </div>
1438 | </div>
1439 | <div class="tool-card tool-ls">
1440 | <div class="tool-icon"><i class="fas fa-folder-open"></i></div>
1441 | <div class="tool-name">LS</div>
1442 | <div class="tool-description">List directory contents with optional filtering and ignore patterns</div>
1443 | <div class="tool-stats">
1444 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 41 calls</div>
1445 | <div class="tool-latency"><i class="fas fa-clock"></i> 28ms avg</div>
1446 | </div>
1447 | </div>
1448 | <div class="tool-card tool-bash">
1449 | <div class="tool-icon"><i class="fas fa-terminal"></i></div>
1450 | <div class="tool-name">Bash</div>
1451 | <div class="tool-description">Execute shell commands with persistent state and timeout options</div>
1452 | <div class="tool-stats">
1453 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 15 calls</div>
1454 | <div class="tool-latency"><i class="fas fa-clock"></i> 350ms avg</div>
1455 | </div>
1456 | </div>
1457 | <div class="tool-card">
1458 | <div class="tool-icon"><i class="fas fa-cog"></i></div>
1459 | <div class="tool-name">GetConfiguration</div>
1460 | <div class="tool-description">Get Claude Desktop configuration for easy setup and customization</div>
1461 | <div class="tool-stats">
1462 | <div class="tool-usage"><i class="fas fa-chart-bar"></i> 3 calls</div>
1463 | <div class="tool-latency"><i class="fas fa-clock"></i> 18ms avg</div>
1464 | </div>
1465 | </div>
1466 | </div>
1467 |
1468 | <div class="chart-container" style="margin-top: 30px;">
1469 | <h3><i class="fas fa-chart-pie"></i> Tool Usage</h3>
1470 | <canvas id="toolUsageChart"></canvas>
1471 | </div>
1472 | </div>
1473 | </div>
1474 |
1475 | <div class="tab-content" id="clients-tab">
1476 | <div class="card">
1477 | <h2><i class="fas fa-laptop-code"></i> Client Configuration</h2>
1478 |
1479 | <h3>Using Claude Code as an MCP Client</h3>
1480 | <p>Claude Code can act as a client to connect to other MCP servers:</p>
1481 | <div class="code-with-copy">
1482 | <pre><code>python claude.py mcp-client path/to/server.py</code></pre>
1483 | <button class="copy-button" onclick="copyClientCmd()"><i class="fas fa-copy"></i></button>
1484 | </div>
1485 |
1486 | <h3>Specify a Claude Model</h3>
1487 | <div class="code-with-copy">
1488 | <pre><code>python claude.py mcp-client path/to/server.py --model claude-3-5-sonnet-20241022</code></pre>
1489 | <button class="copy-button" onclick="copyModelCmd()"><i class="fas fa-copy"></i></button>
1490 | </div>
1491 |
1492 | <h3>Example with Echo Server</h3>
1493 | <div class="note">
1494 | <p>Run these commands in separate terminals:</p>
1495 | <div class="code-with-copy">
1496 | <pre><code># Terminal 1: Start the server
1497 | python examples/echo_server.py
1498 |
1499 | # Terminal 2: Connect with the client
1500 | python claude.py mcp-client examples/echo_server.py</code></pre>
1501 | <button class="copy-button" onclick="copyEchoExample()"><i class="fas fa-copy"></i></button>
1502 | </div>
1503 | </div>
1504 | </div>
1505 |
1506 | <div class="card">
1507 | <h2><i class="fas fa-users"></i> Multi-Agent Mode</h2>
1508 |
1509 | <p>For complex tasks, the multi-agent mode allows multiple specialized agents to collaborate:</p>
1510 |
1511 | <h3>Quick Start</h3>
1512 | <div class="code-with-copy">
1513 | <pre><code>python claude.py mcp-multi-agent examples/echo_server.py --config examples/agents_config.json</code></pre>
1514 | <button class="copy-button" onclick="copyMultiAgentExample()"><i class="fas fa-copy"></i></button>
1515 | </div>
1516 |
1517 | <h3>Agent Configuration</h3>
1518 | <p>The <code>agents_config.json</code> file contains these specialized roles:</p>
1519 | <ul>
1520 | <li><strong>Researcher:</strong> Finds information and analyzes data</li>
1521 | <li><strong>Coder:</strong> Writes and debugs code</li>
1522 | <li><strong>Critic:</strong> Evaluates solutions and suggests improvements</li>
1523 | </ul>
1524 |
1525 | <h3>Multi-Agent Commands</h3>
1526 | <ul>
1527 | <li><code>/agents</code> - List all active agents</li>
1528 | <li><code>/talk <agent> <message></code> - Send a direct message to agent</li>
1529 | <li><code>/history</code> - Show message history</li>
1530 | <li><code>/help</code> - Show multi-agent help</li>
1531 | </ul>
1532 | </div>
1533 | </div>
1534 |
1535 | <div class="tab-content" id="settings-tab">
1536 | <div class="card">
1537 | <h2><i class="fas fa-sliders-h"></i> Server Settings</h2>
1538 |
1539 | <div class="settings-grid">
1540 | <div>
1541 | <h3>General</h3>
1542 | <div class="form-group">
1543 | <label for="server-port">Server Port</label>
1544 | <input type="number" id="server-port" value="8000">
1545 | </div>
1546 | <div class="form-group">
1547 | <label for="server-host">Server Host</label>
1548 | <input type="text" id="server-host" value="localhost">
1549 | </div>
1550 | <div class="form-group toggle-container">
1551 | <label class="toggle">
1552 | <input type="checkbox" id="dev-mode" checked>
1553 | <span class="toggle-slider"></span>
1554 | </label>
1555 | <span>Development Mode</span>
1556 | </div>
1557 | </div>
1558 |
1559 | <div>
1560 | <h3>Advanced</h3>
1561 | <div class="form-group">
1562 | <label for="log-level">Log Level</label>
1563 | <select id="log-level">
1564 | <option>INFO</option>
1565 | <option>DEBUG</option>
1566 | <option>WARNING</option>
1567 | <option>ERROR</option>
1568 | </select>
1569 | </div>
1570 | <div class="form-group toggle-container">
1571 | <label class="toggle">
1572 | <input type="checkbox" id="enable-metrics" checked>
1573 | <span class="toggle-slider"></span>
1574 | </label>
1575 | <span>Enable Metrics Collection</span>
1576 | </div>
1577 | <div class="form-group toggle-container">
1578 | <label class="toggle">
1579 | <input type="checkbox" id="auto-reload">
1580 | <span class="toggle-slider"></span>
1581 | </label>
1582 | <span>Auto-reload on File Changes</span>
1583 | </div>
1584 | </div>
1585 | </div>
1586 |
1587 | <div class="action-buttons">
1588 | <button class="btn" onclick="saveSettings()"><i class="fas fa-save"></i> Save Settings</button>
1589 | <button class="btn btn-secondary" onclick="restartServer()"><i class="fas fa-redo"></i> Restart Server</button>
1590 | <button class="btn btn-danger" onclick="resetSettings()"><i class="fas fa-trash-alt"></i> Reset to Defaults</button>
1591 | </div>
1592 |
1593 | <h3 style="margin-top: 30px;"><i class="fas fa-chart-line"></i> Metrics Management</h3>
1594 | <p>Manage server metrics data collection and storage.</p>
1595 | <div class="action-buttons">
1596 | <button class="btn btn-danger" onclick="resetServerMetrics()"><i class="fas fa-eraser"></i> Reset All Metrics</button>
1597 | </div>
1598 | </div>
1599 | </div>
1600 | </div>
1601 | </div>
1602 |
1603 | <!-- QR Code Modal -->
1604 | <div id="qr-modal" class="modal">
1605 | <div class="modal-content">
1606 | <span class="close" onclick="closeModal('qr-modal')">×</span>
1607 | <h2>Scan QR Code to Configure</h2>
1608 | <p>Use your Claude Desktop app to scan this QR code and automatically configure the connection:</p>
1609 | <div style="text-align: center; margin: 20px 0;">
1610 | <img id="qr-code" src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://example.com/config" alt="QR Code">
1611 | </div>
1612 | <p class="note">Note: This QR code contains the server configuration details needed to connect.</p>
1613 | </div>
1614 | </div>
1615 |
1616 | <!-- Agent Editor Modal -->
1617 | <div id="agent-editor-modal" class="modal">
1618 | <div class="modal-content" style="max-width: 800px;">
1619 | <span class="close" onclick="closeModal('agent-editor-modal')">×</span>
1620 | <h2>Multi-Agent Configuration Editor</h2>
1621 | <p>Customize the roles and capabilities of your agents:</p>
1622 |
1623 | <div id="agent-config-editor" style="margin: 20px 0;">
1624 | <div class="form-group">
1625 | <label for="agent-config-json">Agent Configuration (JSON)</label>
1626 | <textarea id="agent-config-json" rows="15" style="font-family: monospace; white-space: pre;">[
1627 | {
1628 | "name": "Researcher",
1629 | "role": "research specialist",
1630 | "model": "claude-3-5-sonnet-20241022",
1631 | "system_prompt": "You are a research specialist participating in a multi-agent conversation. Your primary role is to find information, analyze data, and provide well-researched answers. You should use tools to gather information and verify facts. Always cite your sources when possible."
1632 | },
1633 | {
1634 | "name": "Coder",
1635 | "role": "programming expert",
1636 | "model": "claude-3-5-sonnet-20241022",
1637 | "system_prompt": "You are a coding expert participating in a multi-agent conversation. Your primary role is to write, debug, and explain code. You should use tools to test your code and provide working solutions. Always prioritize clean, maintainable code with proper error handling. You can collaborate with other agents to solve complex problems."
1638 | },
1639 | {
1640 | "name": "Critic",
1641 | "role": "critical thinker",
1642 | "model": "claude-3-5-sonnet-20241022",
1643 | "system_prompt": "You are a critical thinker participating in a multi-agent conversation. Your primary role is to evaluate proposals, find potential issues, and suggest improvements. You should question assumptions, point out flaws, and help refine ideas. Be constructive in your criticism and suggest alternatives rather than just pointing out problems."
1644 | }
1645 | ]</textarea>
1646 | </div>
1647 | <div class="action-buttons">
1648 | <button class="btn" onclick="saveAgentConfig()"><i class="fas fa-save"></i> Save Configuration</button>
1649 | <button class="btn btn-secondary" onclick="addNewAgent()"><i class="fas fa-plus"></i> Add Agent</button>
1650 | <button class="btn btn-ghost" onclick="validateAgentConfig()"><i class="fas fa-check"></i> Validate</button>
1651 | </div>
1652 | </div>
1653 | </div>
1654 | </div>
1655 |
1656 | <div id="notification" class="notification"></div>
1657 |
1658 | <script>
1659 | // Initialize charts when the page loads
1660 | document.addEventListener('DOMContentLoaded', function() {
1661 | initializeCharts();
1662 | initializeConfig();
1663 | setupTabNavigation();
1664 | setupThemeToggle();
1665 |
1666 | // Start live updates
1667 | updateStats();
1668 | setInterval(updateStats, 5000);
1669 |
1670 | // Set up refresh button
1671 | document.getElementById('refresh-btn').addEventListener('click', function() {
1672 | updateStats();
1673 | showNotification('Dashboard refreshed!');
1674 | });
1675 | });
1676 |
1677 | // Tab navigation
1678 | function setupTabNavigation() {
1679 | const tabs = document.querySelectorAll('.tab');
1680 | tabs.forEach(tab => {
1681 | tab.addEventListener('click', () => {
1682 | // Remove active class from all tabs and content
1683 | document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
1684 | document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
1685 |
1686 | // Add active class to current tab and content
1687 | tab.classList.add('active');
1688 | const tabId = `${tab.dataset.tab}-tab`;
1689 | document.getElementById(tabId).classList.add('active');
1690 | });
1691 | });
1692 | }
1693 |
1694 | // Dark mode toggle
1695 | function setupThemeToggle() {
1696 | const themeToggle = document.getElementById('theme-toggle');
1697 | const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
1698 |
1699 | // Set initial theme based on user preference
1700 | if (prefersDarkMode) {
1701 | document.body.classList.add('dark-mode');
1702 | themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
1703 | }
1704 |
1705 | themeToggle.addEventListener('click', () => {
1706 | document.body.classList.toggle('dark-mode');
1707 | if (document.body.classList.contains('dark-mode')) {
1708 | themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
1709 | } else {
1710 | themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
1711 | }
1712 | });
1713 | }
1714 |
1715 | // Copy configuration to clipboard
1716 | function copyConfig() {
1717 | const configText = document.getElementById('server-config').textContent;
1718 | navigator.clipboard.writeText(configText)
1719 | .then(() => showNotification('Configuration copied to clipboard!'))
1720 | .catch(err => console.error('Failed to copy: ', err));
1721 | }
1722 |
1723 | // Copy other commands
1724 | function copyMultiAgentCmd() {
1725 | navigator.clipboard.writeText('python claude.py mcp-multi-agent path/to/server.py --config examples/agents_config.json')
1726 | .then(() => showNotification('Command copied to clipboard!'));
1727 | }
1728 |
1729 | function copyClientCmd() {
1730 | navigator.clipboard.writeText('python claude.py mcp-client path/to/server.py')
1731 | .then(() => showNotification('Command copied to clipboard!'));
1732 | }
1733 |
1734 | function copyModelCmd() {
1735 | navigator.clipboard.writeText('python claude.py mcp-client path/to/server.py --model claude-3-5-sonnet-20241022')
1736 | .then(() => showNotification('Command copied to clipboard!'));
1737 | }
1738 |
1739 | function copyEchoExample() {
1740 | navigator.clipboard.writeText('# Terminal 1: Start the server\npython examples/echo_server.py\n\n# Terminal 2: Connect with the client\npython claude.py mcp-client examples/echo_server.py')
1741 | .then(() => showNotification('Example copied to clipboard!'));
1742 | }
1743 |
1744 | function copyMultiAgentExample() {
1745 | navigator.clipboard.writeText('python claude.py mcp-multi-agent examples/echo_server.py --config examples/agents_config.json')
1746 | .then(() => showNotification('Example copied to clipboard!'));
1747 | }
1748 |
1749 | // Show notification
1750 | function showNotification(message) {
1751 | const notification = document.getElementById('notification');
1752 | notification.textContent = message;
1753 | notification.style.display = 'block';
1754 |
1755 | setTimeout(() => {
1756 | notification.style.display = 'none';
1757 | }, 3000);
1758 | }
1759 |
1760 | // Modal handlers
1761 | function openQRModal() {
1762 | document.getElementById('qr-modal').style.display = 'block';
1763 | // In a real implementation, this would generate a QR code with the actual config
1764 | const config = encodeURIComponent(JSON.stringify({
1765 | name: "Claude Code Tools",
1766 | type: "local_process",
1767 | command: "python",
1768 | args: ["claude.py", "serve"],
1769 | workingDirectory: "/path/to/claude-code-directory"
1770 | }));
1771 | document.getElementById('qr-code').src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${config}`;
1772 | }
1773 |
1774 | function openAgentEditor() {
1775 | document.getElementById('agent-editor-modal').style.display = 'block';
1776 | }
1777 |
1778 | function closeModal(modalId) {
1779 | document.getElementById(modalId).style.display = 'none';
1780 | }
1781 |
1782 | // Update configuration
1783 | function updateConfig() {
1784 | const serverName = document.getElementById('server-name').value;
1785 | const workingDir = document.getElementById('working-dir').value;
1786 | let serverEnv = {};
1787 |
1788 | try {
1789 | serverEnv = JSON.parse(document.getElementById('server-env').value);
1790 | } catch (e) {
1791 | showNotification('Invalid JSON in environment variables');
1792 | return;
1793 | }
1794 |
1795 | const config = {
1796 | name: serverName,
1797 | type: "local_process",
1798 | command: "python",
1799 | args: ["claude.py", "serve"],
1800 | workingDirectory: workingDir,
1801 | environment: serverEnv,
1802 | description: "A Model Context Protocol server for Claude Code tools"
1803 | };
1804 |
1805 | document.getElementById('server-config').textContent = JSON.stringify(config, null, 2);
1806 | showNotification('Configuration updated!');
1807 | }
1808 |
1809 | // Initialize configuration
1810 | function initializeConfig() {
1811 | // Fetch actual configuration
1812 | fetch('resource:config://json')
1813 | .then(response => response.json())
1814 | .then(config => {
1815 | document.getElementById('server-config').textContent = JSON.stringify(config, null, 2);
1816 | document.getElementById('working-dir').value = config.workingDirectory || '/path/to/claude-code-directory';
1817 | document.getElementById('server-name').value = config.name || 'Claude Code Tools';
1818 | document.getElementById('server-env').value = JSON.stringify(config.environment || {}, null, 2);
1819 | })
1820 | .catch(error => {
1821 | console.error('Error fetching configuration:', error);
1822 | });
1823 |
1824 | // Fetch metrics data
1825 | fetch('resource:metrics://json')
1826 | .then(response => response.json())
1827 | .then(metricsData => {
1828 | // Update the stats
1829 | document.getElementById('uptime').textContent = metricsData.uptime;
1830 | document.getElementById('server-uptime').textContent = metricsData.uptime;
1831 | document.getElementById('tools-count').textContent = Object.keys(metricsData.tool_usage || {}).length;
1832 | document.getElementById('connections-count').textContent = metricsData.active_connections || 0;
1833 | document.getElementById('requests-count').textContent = (
1834 | Object.values(metricsData.tool_usage || {}).reduce((a, b) => a + b, 0) +
1835 | Object.values(metricsData.resource_usage || {}).reduce((a, b) => a + b, 0)
1836 | );
1837 | document.getElementById('resources-count').textContent = Object.keys(metricsData.resource_usage || {}).length;
1838 |
1839 | // Update activity log
1840 | if (metricsData.recent_activity && metricsData.recent_activity.length > 0) {
1841 | const activityLog = document.getElementById('activity-log');
1842 | let logContent = '';
1843 |
1844 | metricsData.recent_activity.forEach(event => {
1845 | const time = event.formatted_time;
1846 | if (event.type === 'tool') {
1847 | logContent += `[${time}] Tool call: ${event.name}\n`;
1848 | } else if (event.type === 'resource') {
1849 | logContent += `[${time}] Resource request: ${event.uri}\n`;
1850 | } else if (event.type === 'connection') {
1851 | const action = event.action === 'connect' ? 'connected' : 'disconnected';
1852 | logContent += `[${time}] Client ${event.client_id.substring(0, 8)} ${action}\n`;
1853 | } else if (event.type === 'error') {
1854 | logContent += `[${time}] Error (${event.error_type}): ${event.message}\n`;
1855 | }
1856 | });
1857 |
1858 | activityLog.querySelector('pre').textContent = logContent;
1859 | }
1860 |
1861 | // Update chart data if the charts are initialized
1862 | if (window.toolUsageChart && metricsData.tool_usage) {
1863 | const toolLabels = Object.keys(metricsData.tool_usage);
1864 | const toolData = Object.values(metricsData.tool_usage);
1865 |
1866 | window.toolUsageChart.data.labels = toolLabels;
1867 | window.toolUsageChart.data.datasets[0].data = toolData;
1868 | window.toolUsageChart.update();
1869 | }
1870 |
1871 | if (window.requestsChart && metricsData.time_series) {
1872 | // Update the time series data
1873 | if (metricsData.time_series.tool_calls) {
1874 | const labels = metricsData.time_series.tool_calls.map(d => d.formatted_time);
1875 | const toolCallData = metricsData.time_series.tool_calls.map(d => d.value);
1876 | const resourceData = metricsData.time_series.resource_calls.map(d => d.value);
1877 |
1878 | window.requestsChart.data.labels = labels;
1879 | window.requestsChart.data.datasets[0].data = toolCallData;
1880 | window.requestsChart.data.datasets[1].data = resourceData;
1881 | window.requestsChart.update();
1882 | }
1883 | }
1884 | })
1885 | .catch(error => {
1886 | console.error('Error fetching metrics:', error);
1887 | });
1888 | }
1889 |
1890 | // Save agent configuration
1891 | function saveAgentConfig() {
1892 | try {
1893 | const config = JSON.parse(document.getElementById('agent-config-json').value);
1894 | // In a real implementation, this would save the config to a file
1895 | showNotification('Agent configuration saved!');
1896 | } catch (e) {
1897 | showNotification('Invalid JSON configuration');
1898 | }
1899 | }
1900 |
1901 | // Add new agent
1902 | function addNewAgent() {
1903 | try {
1904 | let config = JSON.parse(document.getElementById('agent-config-json').value);
1905 | config.push({
1906 | name: "New Agent",
1907 | role: "assistant",
1908 | model: "claude-3-5-sonnet-20241022",
1909 | system_prompt: "You are a helpful AI assistant participating in a multi-agent conversation."
1910 | });
1911 | document.getElementById('agent-config-json').value = JSON.stringify(config, null, 2);
1912 | showNotification('New agent added!');
1913 | } catch (e) {
1914 | showNotification('Invalid JSON configuration');
1915 | }
1916 | }
1917 |
1918 | // Validate agent configuration
1919 | function validateAgentConfig() {
1920 | try {
1921 | const config = JSON.parse(document.getElementById('agent-config-json').value);
1922 | if (!Array.isArray(config)) {
1923 | throw new Error('Configuration must be an array');
1924 | }
1925 | for (const agent of config) {
1926 | if (!agent.name || !agent.role || !agent.model || !agent.system_prompt) {
1927 | throw new Error('Each agent must have name, role, model, and system_prompt');
1928 | }
1929 | }
1930 | showNotification('Configuration is valid!');
1931 | } catch (e) {
1932 | showNotification('Invalid configuration: ' + e.message);
1933 | }
1934 | }
1935 |
1936 | // Settings handlers
1937 | function saveSettings() {
1938 | // In a real implementation, this would save settings to the server
1939 | showNotification('Settings saved successfully!');
1940 | }
1941 |
1942 | function restartServer() {
1943 | // In a real implementation, this would restart the server
1944 | showNotification('Server restarting...');
1945 | setTimeout(() => {
1946 | showNotification('Server restarted successfully!');
1947 | }, 2000);
1948 | }
1949 |
1950 | function resetSettings() {
1951 | // Reset settings to defaults
1952 | document.getElementById('server-port').value = '8000';
1953 | document.getElementById('server-host').value = 'localhost';
1954 | document.getElementById('dev-mode').checked = true;
1955 | document.getElementById('log-level').value = 'INFO';
1956 | document.getElementById('enable-metrics').checked = true;
1957 | document.getElementById('auto-reload').checked = false;
1958 | showNotification('Settings reset to defaults');
1959 | }
1960 |
1961 | // Reset server metrics
1962 | function resetServerMetrics() {
1963 | if (confirm('Are you sure you want to reset all server metrics? This action cannot be undone.')) {
1964 | // Use the metrics reset tool
1965 | fetch('resource:metrics://json', { method: 'GET' })
1966 | .then(response => {
1967 | // We don't actually reset here, but in a real implementation
1968 | // this would use the ResetServerMetrics tool
1969 | showNotification('Server metrics have been reset!');
1970 | // Refresh the dashboard
1971 | updateStats();
1972 | })
1973 | .catch(error => {
1974 | console.error('Error resetting metrics:', error);
1975 | showNotification('Error resetting metrics');
1976 | });
1977 | }
1978 | }
1979 |
1980 | // Initialize charts
1981 | function initializeCharts() {
1982 | // Request activity chart
1983 | const requestsCtx = document.getElementById('requestsChart').getContext('2d');
1984 | window.requestsChart = new Chart(requestsCtx, {
1985 | type: 'line',
1986 | data: {
1987 | labels: ['10m ago', '9m ago', '8m ago', '7m ago', '6m ago', '5m ago', '4m ago', '3m ago', '2m ago', '1m ago', 'Now'],
1988 | datasets: [{
1989 | label: 'Tool Calls',
1990 | data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1991 | borderColor: '#4285f4',
1992 | backgroundColor: 'rgba(66, 133, 244, 0.1)',
1993 | tension: 0.4,
1994 | fill: true
1995 | }, {
1996 | label: 'Resource Requests',
1997 | data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1998 | borderColor: '#34a853',
1999 | backgroundColor: 'rgba(52, 168, 83, 0.1)',
2000 | tension: 0.4,
2001 | fill: true
2002 | }]
2003 | },
2004 | options: {
2005 | responsive: true,
2006 | maintainAspectRatio: false,
2007 | plugins: {
2008 | legend: {
2009 | position: 'top',
2010 | },
2011 | title: {
2012 | display: false
2013 | }
2014 | },
2015 | scales: {
2016 | y: {
2017 | beginAtZero: true
2018 | }
2019 | }
2020 | }
2021 | });
2022 |
2023 | // Tool usage chart
2024 | const toolsCtx = document.getElementById('toolUsageChart').getContext('2d');
2025 | window.toolUsageChart = new Chart(toolsCtx, {
2026 | type: 'doughnut',
2027 | data: {
2028 | labels: ['View', 'GlobTool', 'GrepTool', 'Bash', 'LS', 'Edit', 'Replace', 'GetConfiguration'],
2029 | datasets: [{
2030 | data: [0, 0, 0, 0, 0, 0, 0, 0],
2031 | backgroundColor: [
2032 | '#4285f4', '#ea4335', '#34a853', '#fbbc05',
2033 | '#ff6d01', '#46bdc6', '#7baaf7', '#b366f6',
2034 | '#9c27b0', '#673ab7', '#3f51b5', '#2196f3',
2035 | '#03a9f4', '#00bcd4', '#009688', '#4caf50'
2036 | ],
2037 | borderWidth: 1
2038 | }]
2039 | },
2040 | options: {
2041 | responsive: true,
2042 | maintainAspectRatio: false,
2043 | plugins: {
2044 | legend: {
2045 | position: 'right',
2046 | }
2047 | }
2048 | }
2049 | });
2050 | }
2051 |
2052 | // Update stats periodically
2053 | function updateStats() {
2054 | // Fetch real-time metrics data from the server
2055 | fetch('resource:metrics://json')
2056 | .then(response => response.json())
2057 | .then(metricsData => {
2058 | // Update the stats
2059 | document.getElementById('uptime').textContent = metricsData.uptime;
2060 | document.getElementById('server-uptime').textContent = metricsData.uptime;
2061 | document.getElementById('tools-count').textContent = Object.keys(metricsData.tool_usage || {}).length;
2062 | document.getElementById('connections-count').textContent = metricsData.active_connections || 0;
2063 | document.getElementById('requests-count').textContent = (
2064 | Object.values(metricsData.tool_usage || {}).reduce((a, b) => a + b, 0) +
2065 | Object.values(metricsData.resource_usage || {}).reduce((a, b) => a + b, 0)
2066 | );
2067 | document.getElementById('resources-count').textContent = Object.keys(metricsData.resource_usage || {}).length;
2068 |
2069 | // Update activity log
2070 | if (metricsData.recent_activity && metricsData.recent_activity.length > 0) {
2071 | const activityLog = document.getElementById('activity-log');
2072 | let logContent = '';
2073 |
2074 | metricsData.recent_activity.forEach(event => {
2075 | const time = event.formatted_time;
2076 | if (event.type === 'tool') {
2077 | logContent += `[${time}] Tool call: ${event.name}\n`;
2078 | } else if (event.type === 'resource') {
2079 | logContent += `[${time}] Resource request: ${event.uri}\n`;
2080 | } else if (event.type === 'connection') {
2081 | const action = event.action === 'connect' ? 'connected' : 'disconnected';
2082 | logContent += `[${time}] Client ${event.client_id.substring(0, 8)} ${action}\n`;
2083 | } else if (event.type === 'error') {
2084 | logContent += `[${time}] Error (${event.error_type}): ${event.message}\n`;
2085 | }
2086 | });
2087 |
2088 | activityLog.querySelector('pre').textContent = logContent;
2089 | }
2090 |
2091 | // Update chart data if the charts are initialized
2092 | if (window.toolUsageChart && metricsData.tool_usage) {
2093 | const toolLabels = Object.keys(metricsData.tool_usage);
2094 | const toolData = Object.values(metricsData.tool_usage);
2095 |
2096 | window.toolUsageChart.data.labels = toolLabels;
2097 | window.toolUsageChart.data.datasets[0].data = toolData;
2098 | window.toolUsageChart.update();
2099 | }
2100 |
2101 | if (window.requestsChart && metricsData.time_series) {
2102 | // Update the time series data
2103 | if (metricsData.time_series.tool_calls) {
2104 | const labels = metricsData.time_series.tool_calls.map(d => d.formatted_time);
2105 | const toolCallData = metricsData.time_series.tool_calls.map(d => d.value);
2106 | const resourceData = metricsData.time_series.resource_calls.map(d => d.value);
2107 |
2108 | window.requestsChart.data.labels = labels;
2109 | window.requestsChart.data.datasets[0].data = toolCallData;
2110 | window.requestsChart.data.datasets[1].data = resourceData;
2111 | window.requestsChart.update();
2112 | }
2113 | }
2114 | })
2115 | .catch(error => {
2116 | console.error('Error fetching metrics:', error);
2117 | });
2118 | }
2119 |
2120 | // Update server uptime - no longer needed as it's part of updateStats
2121 | function updateUptime() {
2122 | // This is now handled by updateStats which fetches the actual uptime from the server
2123 | updateStats();
2124 | }
2125 | </script>
2126 | </body>
2127 | </html>
```
--------------------------------------------------------------------------------
/claude_code/lib/rl/tool_optimizer.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Advanced tool selection optimization for Claude Code Python.
3 |
4 | This module implements a specialized reinforcement learning system for optimizing
5 | tool selection based on user queries and context. It uses advanced RL techniques
6 | combined with neural models to learn which tools work best for different types of
7 | queries over time, featuring transfer learning, meta-learning, and causal reasoning.
8 | """
9 |
10 | import numpy as np
11 | import os
12 | import json
13 | import time
14 | import math
15 | import random
16 | from typing import Dict, List, Any, Optional, Tuple, Callable, Union, Set
17 | from dataclasses import dataclass, field
18 | import torch
19 | import torch.nn as nn
20 | import torch.nn.functional as F
21 | from collections import deque, defaultdict
22 |
23 | try:
24 | from sentence_transformers import SentenceTransformer
25 | HAVE_SENTENCE_TRANSFORMERS = True
26 | except ImportError:
27 | HAVE_SENTENCE_TRANSFORMERS = False
28 |
29 | try:
30 | import networkx as nx
31 | HAVE_NETWORKX = True
32 | except ImportError:
33 | HAVE_NETWORKX = False
34 |
35 | try:
36 | import faiss
37 | HAVE_FAISS = True
38 | except ImportError:
39 | HAVE_FAISS = False
40 |
41 | from .grpo import ToolSelectionGRPO
42 |
43 | # Advanced streaming and reflection capabilities
44 | class StreamingReflectionEngine:
45 | """Engine for real-time streaming of thoughts, self-correction, and reflection."""
46 |
47 | def __init__(self, embedding_dim: int = 768, reflection_buffer_size: int = 1000):
48 | """Initialize the streaming reflection engine.
49 |
50 | Args:
51 | embedding_dim: Dimension of embeddings
52 | reflection_buffer_size: Size of reflection buffer
53 | """
54 | self.embedding_dim = embedding_dim
55 | self.reflection_buffer_size = reflection_buffer_size
56 |
57 | # Reflection memory buffer
58 | self.reflection_buffer = deque(maxlen=reflection_buffer_size)
59 |
60 | # Working memory for current thought stream
61 | self.working_memory = []
62 |
63 | # Long-term memory for learned reflections
64 | self.reflection_patterns = {}
65 |
66 | # Reflection critic neural network
67 | self.reflection_critic = nn.Sequential(
68 | nn.Linear(embedding_dim, embedding_dim),
69 | nn.LayerNorm(embedding_dim),
70 | nn.ReLU(),
71 | nn.Linear(embedding_dim, embedding_dim // 2),
72 | nn.LayerNorm(embedding_dim // 2),
73 | nn.ReLU(),
74 | nn.Linear(embedding_dim // 2, 3) # 3 outputs: continue, revise, complete
75 | )
76 |
77 | # Thought revision network
78 | self.thought_reviser = nn.Transformer(
79 | d_model=embedding_dim,
80 | nhead=8,
81 | num_encoder_layers=3,
82 | num_decoder_layers=3,
83 | dim_feedforward=embedding_dim * 4,
84 | dropout=0.1
85 | )
86 |
87 | # Self-correction performance metrics
88 | self.correction_metrics = {
89 | "total_corrections": 0,
90 | "helpful_corrections": 0,
91 | "correction_depth": [],
92 | "avg_correction_time": 0.0,
93 | "total_correction_time": 0.0
94 | }
95 |
96 | # Learning rate for reflection updates
97 | self.reflection_lr = 0.001
98 |
99 | # Optimizer for reflection models
100 | self.optimizer = torch.optim.Adam(
101 | list(self.reflection_critic.parameters()) +
102 | list(self.thought_reviser.parameters()),
103 | lr=self.reflection_lr
104 | )
105 |
106 | def start_reflection_stream(self, query_embedding: np.ndarray, context: Dict[str, Any]) -> str:
107 | """Start a new reflection stream for a query.
108 |
109 | Args:
110 | query_embedding: Embedding of the query
111 | context: Additional context
112 |
113 | Returns:
114 | Stream ID for this reflection session
115 | """
116 | stream_id = f"reflection_{int(time.time())}_{random.randint(0, 10000)}"
117 |
118 | # Initialize working memory for this stream
119 | self.working_memory = [
120 | {
121 | "type": "query",
122 | "embedding": torch.FloatTensor(query_embedding),
123 | "timestamp": time.time(),
124 | "context": context,
125 | "stream_id": stream_id
126 | }
127 | ]
128 |
129 | return stream_id
130 |
131 | def add_thought(self,
132 | stream_id: str,
133 | thought_embedding: np.ndarray,
134 | thought_text: str,
135 | thought_type: str = "reasoning") -> Dict[str, Any]:
136 | """Add a thought to the reflection stream and get feedback.
137 |
138 | Args:
139 | stream_id: ID of the reflection stream
140 | thought_embedding: Embedding of the thought
141 | thought_text: Text of the thought
142 | thought_type: Type of thought (reasoning, plan, action, etc.)
143 |
144 | Returns:
145 | Feedback on the thought
146 | """
147 | # Convert to tensor
148 | thought_tensor = torch.FloatTensor(thought_embedding)
149 |
150 | # Create thought record
151 | thought = {
152 | "type": thought_type,
153 | "embedding": thought_tensor,
154 | "text": thought_text,
155 | "timestamp": time.time(),
156 | "stream_id": stream_id,
157 | "depth": len(self.working_memory)
158 | }
159 |
160 | # Add to working memory
161 | self.working_memory.append(thought)
162 |
163 | # Get reflection feedback
164 | feedback = self._reflect_on_thought(thought)
165 |
166 | # Store in reflection buffer
167 | self.reflection_buffer.append({
168 | "thought": thought,
169 | "feedback": feedback
170 | })
171 |
172 | return feedback
173 |
174 | def _reflect_on_thought(self, thought: Dict[str, Any]) -> Dict[str, Any]:
175 | """Generate reflection on a thought.
176 |
177 | Args:
178 | thought: The thought to reflect on
179 |
180 | Returns:
181 | Reflection feedback
182 | """
183 | # Get thought embedding
184 | thought_embedding = thought["embedding"]
185 |
186 | # Get critic prediction
187 | with torch.no_grad():
188 | critic_output = self.reflection_critic(thought_embedding.unsqueeze(0))
189 | action_probs = F.softmax(critic_output, dim=1).squeeze(0)
190 |
191 | # Actions: [continue, revise, complete]
192 | action_idx = torch.argmax(action_probs).item()
193 | action_confidence = action_probs[action_idx].item()
194 |
195 | actions = ["continue", "revise", "complete"]
196 | action = actions[action_idx]
197 |
198 | # Check if similar to patterns we've seen before
199 | pattern_matches = []
200 | if len(self.working_memory) >= 3:
201 | # Get sequence of last 3 thoughts
202 | sequence = [t["embedding"] for t in self.working_memory[-3:]]
203 | sequence_tensor = torch.stack(sequence)
204 |
205 | # Compare to known patterns
206 | for pattern_name, pattern_data in self.reflection_patterns.items():
207 | if len(pattern_data["sequence"]) == 3:
208 | # Compute similarity
209 | pattern_tensor = torch.stack(pattern_data["sequence"])
210 | similarity = F.cosine_similarity(
211 | sequence_tensor.mean(dim=0).unsqueeze(0),
212 | pattern_tensor.mean(dim=0).unsqueeze(0)
213 | ).item()
214 |
215 | if similarity > 0.7: # High similarity threshold
216 | pattern_matches.append({
217 | "pattern": pattern_name,
218 | "similarity": similarity,
219 | "outcome": pattern_data["outcome"]
220 | })
221 |
222 | # Check for circular reasoning
223 | is_circular = False
224 | if len(self.working_memory) >= 5:
225 | recent_thoughts = [t["embedding"] for t in self.working_memory[-5:]]
226 |
227 | # Check if latest thought is very similar to any of the previous 4
228 | latest = recent_thoughts[-1]
229 | for prev in recent_thoughts[:-1]:
230 | similarity = F.cosine_similarity(latest.unsqueeze(0), prev.unsqueeze(0)).item()
231 | if similarity > 0.85: # Very high similarity threshold
232 | is_circular = True
233 | break
234 |
235 | # Generate revision suggestion if needed
236 | revision_suggestion = None
237 | if action == "revise" or is_circular:
238 | revision_suggestion = self._generate_revision(thought)
239 |
240 | # Create feedback
241 | feedback = {
242 | "action": action,
243 | "confidence": action_confidence,
244 | "is_circular": is_circular,
245 | "pattern_matches": pattern_matches,
246 | "revision_suggestion": revision_suggestion,
247 | "timestamp": time.time()
248 | }
249 |
250 | return feedback
251 |
252 | def _generate_revision(self, thought: Dict[str, Any]) -> Dict[str, Any]:
253 | """Generate a revision for a thought.
254 |
255 | Args:
256 | thought: The thought to revise
257 |
258 | Returns:
259 | Revision suggestion
260 | """
261 | # If we have fewer than 2 thoughts, can't generate meaningful revision
262 | if len(self.working_memory) < 2:
263 | return {
264 | "type": "general",
265 | "embedding": thought["embedding"].detach().numpy(),
266 | "message": "Consider providing more specific reasoning"
267 | }
268 |
269 | # Get context from previous thoughts
270 | context_embeddings = torch.stack([t["embedding"] for t in self.working_memory[:-1]])
271 |
272 | # Create source and target sequences for transformer
273 | src = context_embeddings.unsqueeze(1) # [seq_len, batch_size, embedding_dim]
274 | tgt = thought["embedding"].unsqueeze(0).unsqueeze(1) # [1, batch_size, embedding_dim]
275 |
276 | # Generate revision using transformer
277 | with torch.no_grad():
278 | # Create attention mask
279 | src_mask = torch.zeros(src.shape[0], src.shape[0]).bool()
280 |
281 | # Revised thought
282 | revised_embedding = self.thought_reviser(
283 | src,
284 | tgt,
285 | src_mask=src_mask,
286 | tgt_mask=torch.zeros(1, 1).bool()
287 | )
288 |
289 | # Extract the output embedding
290 | revised_embedding = revised_embedding[0, 0]
291 |
292 | # Look for insights from reflection buffer
293 | insights = []
294 |
295 | # Find similar thoughts from reflection buffer
296 | for entry in self.reflection_buffer:
297 | past_thought = entry["thought"]
298 |
299 | # Skip if from current stream
300 | if past_thought.get("stream_id") == thought.get("stream_id"):
301 | continue
302 |
303 | # Compute similarity
304 | similarity = F.cosine_similarity(
305 | thought["embedding"].unsqueeze(0),
306 | past_thought["embedding"].unsqueeze(0)
307 | ).item()
308 |
309 | if similarity > 0.6: # Significant similarity
310 | insights.append({
311 | "type": "similar_thought",
312 | "similarity": similarity,
313 | "feedback": entry["feedback"]
314 | })
315 |
316 | # Create revision suggestion
317 | revision = {
318 | "type": "specific",
319 | "embedding": revised_embedding.detach().numpy(),
320 | "insights": insights[:3], # Top 3 insights
321 | "message": "Consider revising this thought for more clarity and precision"
322 | }
323 |
324 | return revision
325 |
326 | def complete_reflection(self, stream_id: str,
327 | outcome: Dict[str, Any],
328 | success: bool) -> Dict[str, Any]:
329 | """Complete a reflection stream and learn from it.
330 |
331 | Args:
332 | stream_id: ID of the reflection stream
333 | outcome: Outcome of the actions taken based on reflections
334 | success: Whether the outcome was successful
335 |
336 | Returns:
337 | Reflection summary and metrics
338 | """
339 | # Filter working memory for this stream
340 | stream_thoughts = [t for t in self.working_memory if t.get("stream_id") == stream_id]
341 |
342 | if not stream_thoughts:
343 | return {"status": "error", "message": "Stream not found"}
344 |
345 | # Count corrections
346 | corrections = sum(1 for t in stream_thoughts if t.get("type") == "correction")
347 |
348 | # Update metrics
349 | self.correction_metrics["total_corrections"] += corrections
350 | if success:
351 | self.correction_metrics["helpful_corrections"] += corrections
352 |
353 | if corrections > 0:
354 | self.correction_metrics["correction_depth"].append(len(stream_thoughts))
355 |
356 | # Learn from this reflection session
357 | self._learn_from_reflection(stream_thoughts, outcome, success)
358 |
359 | # Extract and store useful thought patterns
360 | if success and len(stream_thoughts) >= 3:
361 | self._extract_thought_patterns(stream_thoughts, outcome)
362 |
363 | # Compute summary stats
364 | duration = time.time() - stream_thoughts[0]["timestamp"]
365 | avg_thought_time = duration / len(stream_thoughts)
366 |
367 | # Generate summary
368 | summary = {
369 | "stream_id": stream_id,
370 | "num_thoughts": len(stream_thoughts),
371 | "num_corrections": corrections,
372 | "duration": duration,
373 | "avg_thought_time": avg_thought_time,
374 | "success": success,
375 | "outcome_summary": outcome.get("summary", "No summary provided")
376 | }
377 |
378 | return summary
379 |
380 | def _learn_from_reflection(self, thoughts: List[Dict[str, Any]],
381 | outcome: Dict[str, Any],
382 | success: bool) -> None:
383 | """Learn from a completed reflection stream.
384 |
385 | Args:
386 | thoughts: List of thoughts in the stream
387 | outcome: Outcome of the actions
388 | success: Whether the outcome was successful
389 | """
390 | if not thoughts:
391 | return
392 |
393 | # Skip if too few thoughts to learn from
394 | if len(thoughts) < 3:
395 | return
396 |
397 | # Create training examples for reflection critic
398 | examples = []
399 |
400 | for i in range(1, len(thoughts) - 1):
401 | # Current thought
402 | thought_embedding = thoughts[i]["embedding"]
403 |
404 | # Determine correct action label based on what happened
405 | # 0: continue, 1: revise, 2: complete
406 | if i == len(thoughts) - 2:
407 | # Second-to-last thought should have led to completion
408 | label = 2
409 | elif thoughts[i+1].get("type") == "correction":
410 | # This thought was followed by a correction, should have been revised
411 | label = 1
412 | else:
413 | # This thought was good to continue from
414 | label = 0
415 |
416 | # Create example
417 | examples.append((thought_embedding, label))
418 |
419 | # Skip training if too few examples
420 | if not examples:
421 | return
422 |
423 | # Update reflection critic with these examples
424 | self.optimizer.zero_grad()
425 |
426 | critic_loss = 0.0
427 | for embedding, label in examples:
428 | # Forward pass
429 | logits = self.reflection_critic(embedding.unsqueeze(0))
430 |
431 | # Compute loss
432 | target = torch.tensor([label], device=embedding.device)
433 | loss = F.cross_entropy(logits, target)
434 |
435 | critic_loss += loss
436 |
437 | # Scale loss by number of examples
438 | critic_loss /= len(examples)
439 |
440 | # Backpropagation
441 | critic_loss.backward()
442 |
443 | # Update parameters
444 | self.optimizer.step()
445 |
446 | def _extract_thought_patterns(self, thoughts: List[Dict[str, Any]],
447 | outcome: Dict[str, Any]) -> None:
448 | """Extract useful thought patterns from successful reflection streams.
449 |
450 | Args:
451 | thoughts: List of thoughts in the stream
452 | outcome: Outcome information
453 | """
454 | # Need at least 3 thoughts to form a meaningful pattern
455 | if len(thoughts) < 3:
456 | return
457 |
458 | # Generate a name for this pattern
459 | pattern_id = f"pattern_{len(self.reflection_patterns) + 1}"
460 |
461 | # Extract sequences of 3 consecutive thoughts
462 | for i in range(len(thoughts) - 2):
463 | sequence = thoughts[i:i+3]
464 |
465 | # Skip if any thought is a correction - we want clean sequences
466 | if any(t.get("type") == "correction" for t in sequence):
467 | continue
468 |
469 | # Get embeddings for the sequence
470 | sequence_embeddings = [t["embedding"] for t in sequence]
471 |
472 | # Store the pattern
473 | self.reflection_patterns[f"{pattern_id}_{i}"] = {
474 | "sequence": sequence_embeddings,
475 | "outcome": {
476 | "success": outcome.get("success", True),
477 | "context": outcome.get("context", {}),
478 | "summary": outcome.get("summary", "")
479 | },
480 | "timestamp": time.time()
481 | }
482 |
483 | # Limit number of patterns to prevent memory issues
484 | if len(self.reflection_patterns) > 100:
485 | # Remove oldest pattern
486 | oldest_key = min(self.reflection_patterns.keys(),
487 | key=lambda k: self.reflection_patterns[k]["timestamp"])
488 | del self.reflection_patterns[oldest_key]
489 |
490 |
491 | # Active Learning and Self-Improvement
492 | class ActiveLearningSystem:
493 | """Active learning system that identifies knowledge gaps and seeks targeted improvement."""
494 |
495 | def __init__(self, embedding_dim: int = 768, exploration_rate: float = 0.2):
496 | """Initialize the active learning system.
497 |
498 | Args:
499 | embedding_dim: Dimension of embeddings
500 | exploration_rate: Rate of exploration vs. exploitation
501 | """
502 | self.embedding_dim = embedding_dim
503 | self.exploration_rate = exploration_rate
504 |
505 | # Knowledge graph for tracking what's known/unknown
506 | self.knowledge_graph = nx.DiGraph() if HAVE_NETWORKX else None
507 |
508 | # Uncertainty estimation model
509 | self.uncertainty_estimator = nn.Sequential(
510 | nn.Linear(embedding_dim, embedding_dim),
511 | nn.LayerNorm(embedding_dim),
512 | nn.ReLU(),
513 | nn.Linear(embedding_dim, embedding_dim // 2),
514 | nn.LayerNorm(embedding_dim // 2),
515 | nn.ReLU(),
516 | nn.Linear(embedding_dim // 2, 2) # [confidence, uncertainty]
517 | )
518 |
519 | # Knowledge boundaries
520 | self.knowledge_centroids = []
521 | self.knowledge_radius = {}
522 |
523 | # Learning curriculum
524 | self.learning_targets = []
525 | self.learning_progress = {}
526 |
527 | # Exploration history
528 | self.exploration_history = []
529 |
530 | # Coreset for diversity
531 | self.coreset = []
532 | self.coreset_embeddings = []
533 |
534 | # Faiss index for fast nearest neighbor search
535 | self.index = None
536 | if HAVE_FAISS:
537 | self.index = faiss.IndexFlatL2(embedding_dim)
538 |
539 | # Optimizer for uncertainty estimator
540 | self.optimizer = torch.optim.Adam(self.uncertainty_estimator.parameters(), lr=0.001)
541 |
542 | def estimate_uncertainty(self, query_embedding: np.ndarray) -> Dict[str, float]:
543 | """Estimate uncertainty for a query or state.
544 |
545 | Args:
546 | query_embedding: Embedding to evaluate
547 |
548 | Returns:
549 | Dictionary with confidence and uncertainty scores
550 | """
551 | # Convert to tensor
552 | query_tensor = torch.FloatTensor(query_embedding)
553 |
554 | # Get uncertainty estimate
555 | with torch.no_grad():
556 | estimate = self.uncertainty_estimator(query_tensor.unsqueeze(0))
557 | confidence, uncertainty = F.softmax(estimate, dim=1).squeeze(0).tolist()
558 |
559 | # Compute distance-based uncertainty if we have knowledge centroids
560 | distance_uncertainty = 0.0
561 | if self.knowledge_centroids:
562 | # Convert to numpy for distance calculation
563 | centroid_array = np.vstack(self.knowledge_centroids)
564 | query_array = query_embedding.reshape(1, -1)
565 |
566 | # Compute distances to all centroids
567 | distances = np.linalg.norm(centroid_array - query_array, axis=1)
568 |
569 | # Get distance to nearest centroid
570 | min_dist = np.min(distances)
571 | min_idx = np.argmin(distances)
572 | nearest_centroid = self.knowledge_centroids[min_idx]
573 |
574 | # Radius of knowledge around this centroid
575 | radius = self.knowledge_radius.get(tuple(nearest_centroid), 1.0)
576 |
577 | # Normalize distance by radius to get uncertainty
578 | distance_uncertainty = min(1.0, min_dist / radius)
579 |
580 | # Combine model and distance uncertainty
581 | combined_uncertainty = 0.7 * uncertainty + 0.3 * distance_uncertainty
582 | combined_confidence = 1.0 - combined_uncertainty
583 |
584 | return {
585 | "confidence": combined_confidence,
586 | "uncertainty": combined_uncertainty,
587 | "model_confidence": confidence,
588 | "model_uncertainty": uncertainty,
589 | "distance_uncertainty": distance_uncertainty
590 | }
591 |
592 | def should_explore(self, query_embedding: np.ndarray, context: Dict[str, Any]) -> bool:
593 | """Determine if we should explore to gather new knowledge for this query.
594 |
595 | Args:
596 | query_embedding: Query embedding
597 | context: Additional context
598 |
599 | Returns:
600 | Whether to explore
601 | """
602 | # Estimate uncertainty
603 | uncertainty_info = self.estimate_uncertainty(query_embedding)
604 |
605 | # Always explore if uncertainty is very high
606 | if uncertainty_info["uncertainty"] > 0.8:
607 | return True
608 |
609 | # Use epsilon-greedy strategy with adaptive exploration
610 | # Higher uncertainty means more likely to explore
611 | adaptive_rate = self.exploration_rate * (0.5 + uncertainty_info["uncertainty"])
612 |
613 | # Apply epsilon-greedy
614 | return random.random() < adaptive_rate
615 |
616 | def add_knowledge(self, query_embedding: np.ndarray,
617 | related_info: Dict[str, Any],
618 | confidence: float) -> None:
619 | """Add knowledge to the system.
620 |
621 | Args:
622 | query_embedding: Query embedding
623 | related_info: Related information (e.g., tool used, outcome)
624 | confidence: Confidence in this knowledge
625 | """
626 | # Add to knowledge graph
627 | if self.knowledge_graph is not None:
628 | # Create node for this query
629 | query_key = f"query_{len(self.knowledge_graph.nodes)}"
630 | self.knowledge_graph.add_node(query_key,
631 | embedding=query_embedding,
632 | confidence=confidence,
633 | timestamp=time.time())
634 |
635 | # Add related information as connected nodes
636 | for key, value in related_info.items():
637 | info_key = f"{key}_{len(self.knowledge_graph.nodes)}"
638 | self.knowledge_graph.add_node(info_key, value=value)
639 | self.knowledge_graph.add_edge(query_key, info_key, relation=key)
640 |
641 | # Update knowledge centroids
642 | self._update_knowledge_boundaries(query_embedding, confidence)
643 |
644 | # Update coreset for diversity
645 | self._update_coreset(query_embedding, related_info)
646 |
647 | def _update_knowledge_boundaries(self, embedding: np.ndarray, confidence: float) -> None:
648 | """Update knowledge boundaries with new information.
649 |
650 | Args:
651 | embedding: Embedding of new knowledge
652 | confidence: Confidence in this knowledge
653 | """
654 | # If no centroids yet, add this as the first one
655 | if not self.knowledge_centroids:
656 | self.knowledge_centroids.append(embedding)
657 | self.knowledge_radius[tuple(embedding)] = 1.0
658 | return
659 |
660 | # Find closest centroid
661 | centroid_array = np.vstack(self.knowledge_centroids)
662 | query_array = embedding.reshape(1, -1)
663 |
664 | distances = np.linalg.norm(centroid_array - query_array, axis=1)
665 | min_dist = np.min(distances)
666 | min_idx = np.argmin(distances)
667 | nearest_centroid = self.knowledge_centroids[min_idx]
668 | nearest_centroid_tuple = tuple(nearest_centroid)
669 |
670 | # Get current radius
671 | current_radius = self.knowledge_radius.get(nearest_centroid_tuple, 1.0)
672 |
673 | # If within current radius, update radius based on confidence
674 | if min_dist < current_radius:
675 | # Higher confidence shrinks radius (more precise knowledge)
676 | # Lower confidence expands radius (more uncertainty)
677 | new_radius = current_radius * (1.0 - 0.1 * confidence)
678 | self.knowledge_radius[nearest_centroid_tuple] = new_radius
679 | else:
680 | # Outside known areas, add as new centroid
681 | if len(self.knowledge_centroids) < 100: # Limit number of centroids
682 | self.knowledge_centroids.append(embedding)
683 | self.knowledge_radius[tuple(embedding)] = 1.0
684 |
685 | # Otherwise, merge with nearest
686 | else:
687 | # Update nearest centroid with weighted average
688 | updated_centroid = 0.8 * nearest_centroid + 0.2 * embedding
689 |
690 | # Update centroid list
691 | self.knowledge_centroids[min_idx] = updated_centroid
692 |
693 | # Update radius dict
694 | self.knowledge_radius[tuple(updated_centroid)] = current_radius
695 | del self.knowledge_radius[nearest_centroid_tuple]
696 |
697 | def _update_coreset(self, embedding: np.ndarray, info: Dict[str, Any]) -> None:
698 | """Update coreset of diverse examples.
699 |
700 | Args:
701 | embedding: New example embedding
702 | info: Related information
703 | """
704 | # Skip if no Faiss
705 | if self.index is None:
706 | return
707 |
708 | # If coreset is empty, add first example
709 | if not self.coreset_embeddings:
710 | self.coreset.append(info)
711 | self.coreset_embeddings.append(embedding)
712 | self.index.add(np.vstack([embedding]))
713 | return
714 |
715 | # Check if this example is sufficiently different from existing examples
716 | # Convert to correct shape for Faiss
717 | query = embedding.reshape(1, -1).astype('float32')
718 |
719 | # Search for nearest neighbors
720 | distances, indices = self.index.search(query, 1)
721 |
722 | # If sufficiently different, add to coreset
723 | if distances[0][0] > 0.5: # Distance threshold
724 | if len(self.coreset) < 100: # Limit coreset size
725 | self.coreset.append(info)
726 | self.coreset_embeddings.append(embedding)
727 | self.index.add(query)
728 | else:
729 | # Replace most similar item
730 | _, indices = self.index.search(query, len(self.coreset))
731 | most_similar_idx = indices[0][-1]
732 |
733 | # Remove from index (need to rebuild index)
734 | self.coreset[most_similar_idx] = info
735 | self.coreset_embeddings[most_similar_idx] = embedding
736 |
737 | # Rebuild index
738 | self.index = faiss.IndexFlatL2(self.embedding_dim)
739 | self.index.add(np.vstack(self.coreset_embeddings).astype('float32'))
740 |
741 | def identify_knowledge_gaps(self) -> List[Dict[str, Any]]:
742 | """Identify knowledge gaps for active learning.
743 |
744 | Returns:
745 | List of knowledge gap areas to explore
746 | """
747 | gaps = []
748 |
749 | # Skip if no knowledge graph
750 | if self.knowledge_graph is None:
751 | return gaps
752 |
753 | # Find areas with low confidence
754 | low_confidence_nodes = [
755 | (node, data) for node, data in self.knowledge_graph.nodes(data=True)
756 | if "confidence" in data and data["confidence"] < 0.5
757 | ]
758 |
759 | # Group by embedding similarity
760 | clusters = {}
761 | for node, data in low_confidence_nodes:
762 | if "embedding" not in data:
763 | continue
764 |
765 | # Find or create cluster
766 | assigned = False
767 | for cluster_id, cluster_data in clusters.items():
768 | centroid = cluster_data["centroid"]
769 |
770 | # Compute similarity
771 | similarity = np.dot(data["embedding"], centroid) / (
772 | np.linalg.norm(data["embedding"]) * np.linalg.norm(centroid)
773 | )
774 |
775 | if similarity > 0.7: # High similarity threshold
776 | # Add to cluster
777 | cluster_data["nodes"].append((node, data))
778 |
779 | # Update centroid
780 | new_centroid = (centroid * len(cluster_data["nodes"]) + data["embedding"]) / (
781 | len(cluster_data["nodes"]) + 1
782 | )
783 | cluster_data["centroid"] = new_centroid
784 |
785 | assigned = True
786 | break
787 |
788 | if not assigned:
789 | # Create new cluster
790 | cluster_id = f"cluster_{len(clusters)}"
791 | clusters[cluster_id] = {
792 | "centroid": data["embedding"],
793 | "nodes": [(node, data)]
794 | }
795 |
796 | # Convert clusters to knowledge gaps
797 | for cluster_id, cluster_data in clusters.items():
798 | if len(cluster_data["nodes"]) >= 2: # Only consider significant clusters
799 | related_info = {}
800 |
801 | # Collect information about this cluster
802 | for node, data in cluster_data["nodes"]:
803 | # Get connected nodes
804 | if self.knowledge_graph.has_node(node):
805 | for _, neighbor, edge_data in self.knowledge_graph.out_edges(node, data=True):
806 | neighbor_data = self.knowledge_graph.nodes[neighbor]
807 | if "value" in neighbor_data:
808 | relation = edge_data.get("relation", "related")
809 | related_info[relation] = neighbor_data["value"]
810 |
811 | # Create gap description
812 | gap = {
813 | "id": cluster_id,
814 | "centroid": cluster_data["centroid"],
815 | "num_instances": len(cluster_data["nodes"]),
816 | "related_info": related_info,
817 | "confidence": np.mean([d["confidence"] for _, d in cluster_data["nodes"] if "confidence" in d])
818 | }
819 |
820 | gaps.append(gap)
821 |
822 | # Sort gaps by confidence (ascending) and size (descending)
823 | gaps.sort(key=lambda x: (x["confidence"], -x["num_instances"]))
824 |
825 | return gaps
826 |
827 | def generate_exploration_query(self, gap: Dict[str, Any]) -> Dict[str, Any]:
828 | """Generate an exploration query for a knowledge gap.
829 |
830 | Args:
831 | gap: Knowledge gap information
832 |
833 | Returns:
834 | Exploration query
835 | """
836 | # Create query from gap centroid
837 | centroid = gap["centroid"]
838 |
839 | # Find nearest examples in coreset for additional context
840 | similar_examples = []
841 | if self.coreset_embeddings and len(self.coreset) > 0:
842 | # Convert centroid to correct shape
843 | query = centroid.reshape(1, -1).astype('float32')
844 |
845 | # Find nearest neighbors
846 | if self.index is not None:
847 | distances, indices = self.index.search(query, min(3, len(self.coreset)))
848 |
849 | # Add nearest examples
850 | for i, idx in enumerate(indices[0]):
851 | if idx < len(self.coreset):
852 | similar_examples.append({
853 | "example": self.coreset[idx],
854 | "distance": distances[0][i]
855 | })
856 |
857 | # Generate exploration query
858 | exploration = {
859 | "embedding": centroid,
860 | "gap_id": gap["id"],
861 | "related_info": gap["related_info"],
862 | "confidence": gap["confidence"],
863 | "similar_examples": similar_examples,
864 | "timestamp": time.time()
865 | }
866 |
867 | return exploration
868 |
869 | def update_from_exploration(self,
870 | gap_id: str,
871 | query_embedding: np.ndarray,
872 | result: Dict[str, Any],
873 | success: bool) -> None:
874 | """Update knowledge from exploration results.
875 |
876 | Args:
877 | gap_id: ID of the knowledge gap
878 | query_embedding: Embedding of the exploration query
879 | result: Result of the exploration
880 | success: Whether the exploration was successful
881 | """
882 | # Add to exploration history
883 | self.exploration_history.append({
884 | "gap_id": gap_id,
885 | "embedding": query_embedding,
886 | "result": result,
887 | "success": success,
888 | "timestamp": time.time()
889 | })
890 |
891 | # Update knowledge with exploration results
892 | self.add_knowledge(
893 | query_embedding=query_embedding,
894 | related_info=result,
895 | confidence=0.8 if success else 0.3
896 | )
897 |
898 | # Update uncertainty model from this exploration
899 | self._update_uncertainty_model(query_embedding, result, success)
900 |
901 | def _update_uncertainty_model(self,
902 | query_embedding: np.ndarray,
903 | result: Dict[str, Any],
904 | success: bool) -> None:
905 | """Update uncertainty estimation model.
906 |
907 | Args:
908 | query_embedding: Query embedding
909 | result: Exploration result
910 | success: Whether exploration was successful
911 | """
912 | # Convert to tensor
913 | query_tensor = torch.FloatTensor(query_embedding)
914 |
915 | # Target values for training
916 | # If success, low uncertainty (high confidence)
917 | # If failure, high uncertainty (low confidence)
918 | if success:
919 | target = torch.tensor([[0.9, 0.1]]) # [confidence, uncertainty]
920 | else:
921 | target = torch.tensor([[0.2, 0.8]]) # [confidence, uncertainty]
922 |
923 | # Update model
924 | self.optimizer.zero_grad()
925 |
926 | # Forward pass
927 | prediction = self.uncertainty_estimator(query_tensor.unsqueeze(0))
928 | prediction = F.softmax(prediction, dim=1)
929 |
930 | # Compute loss
931 | loss = F.mse_loss(prediction, target)
932 |
933 | # Backpropagation
934 | loss.backward()
935 |
936 | # Update parameters
937 | self.optimizer.step()
938 |
939 |
940 | # Multi-task learning system
941 | class MultiTaskLearningSystem:
942 | """System for learning across multiple task types with shared knowledge and specialized adapters."""
943 |
944 | def __init__(self, embedding_dim: int = 768, num_tasks: int = 5):
945 | """Initialize the multi-task learning system.
946 |
947 | Args:
948 | embedding_dim: Dimension of embeddings
949 | num_tasks: Number of task types to support
950 | """
951 | self.embedding_dim = embedding_dim
952 | self.num_tasks = num_tasks
953 |
954 | # Task type registry
955 | self.task_types = {}
956 |
957 | # Shared embedding model (backbone)
958 | self.shared_model = nn.Sequential(
959 | nn.Linear(embedding_dim, embedding_dim),
960 | nn.LayerNorm(embedding_dim),
961 | nn.ReLU(),
962 | nn.Linear(embedding_dim, embedding_dim),
963 | nn.LayerNorm(embedding_dim)
964 | )
965 |
966 | # Task-specific adapter modules
967 | self.task_adapters = nn.ModuleDict()
968 |
969 | # Task projectors (for returning to original space)
970 | self.task_projectors = nn.ModuleDict()
971 |
972 | # Task-specific optimizers
973 | self.task_optimizers = {}
974 |
975 | # Multi-task performance metrics
976 | self.task_metrics = {}
977 |
978 | # Shared optimizer
979 | self.shared_optimizer = torch.optim.Adam(self.shared_model.parameters(), lr=0.001)
980 |
981 | def register_task_type(self, task_name: str,
982 | initial_examples: List[Tuple[np.ndarray, np.ndarray]] = None) -> None:
983 | """Register a new task type.
984 |
985 | Args:
986 | task_name: Name of the task
987 | initial_examples: Optional initial examples (input, output embeddings)
988 | """
989 | if task_name in self.task_types:
990 | return
991 |
992 | # Register task
993 | self.task_types[task_name] = {
994 | "examples": [],
995 | "difficulty": 0.5, # Initial difficulty estimate
996 | "performance": 0.0, # Initial performance estimate
997 | "timestamp": time.time()
998 | }
999 |
1000 | # Create task adapter
1001 | self.task_adapters[task_name] = nn.Sequential(
1002 | nn.Linear(self.embedding_dim, self.embedding_dim // 2),
1003 | nn.LayerNorm(self.embedding_dim // 2),
1004 | nn.ReLU(),
1005 | nn.Linear(self.embedding_dim // 2, self.embedding_dim)
1006 | )
1007 |
1008 | # Create projector back to original space
1009 | self.task_projectors[task_name] = nn.Linear(self.embedding_dim, self.embedding_dim)
1010 |
1011 | # Create optimizer
1012 | self.task_optimizers[task_name] = torch.optim.Adam(
1013 | list(self.task_adapters[task_name].parameters()) +
1014 | list(self.task_projectors[task_name].parameters()),
1015 | lr=0.001
1016 | )
1017 |
1018 | # Initialize metrics
1019 | self.task_metrics[task_name] = {
1020 | "examples_seen": 0,
1021 | "loss_history": [],
1022 | "accuracy_history": [],
1023 | "last_improvement": time.time()
1024 | }
1025 |
1026 | # Add initial examples if provided
1027 | if initial_examples:
1028 | for input_emb, output_emb in initial_examples:
1029 | self.add_task_example(task_name, input_emb, output_emb)
1030 |
1031 | def add_task_example(self, task_name: str,
1032 | input_embedding: np.ndarray,
1033 | output_embedding: np.ndarray) -> None:
1034 | """Add an example for a specific task.
1035 |
1036 | Args:
1037 | task_name: Name of the task
1038 | input_embedding: Input embedding
1039 | output_embedding: Target output embedding
1040 | """
1041 | if task_name not in self.task_types:
1042 | self.register_task_type(task_name)
1043 |
1044 | # Convert to tensors
1045 | input_tensor = torch.FloatTensor(input_embedding)
1046 | output_tensor = torch.FloatTensor(output_embedding)
1047 |
1048 | # Add to examples
1049 | self.task_types[task_name]["examples"].append((input_tensor, output_tensor))
1050 |
1051 | # Update metrics
1052 | self.task_metrics[task_name]["examples_seen"] += 1
1053 |
1054 | # Limit number of examples stored
1055 | if len(self.task_types[task_name]["examples"]) > 100:
1056 | self.task_types[task_name]["examples"].pop(0)
1057 |
1058 | # Update model with this example
1059 | self._update_model_with_example(task_name, input_tensor, output_tensor)
1060 |
1061 | def _update_model_with_example(self, task_name: str,
1062 | input_tensor: torch.Tensor,
1063 | output_tensor: torch.Tensor) -> None:
1064 | """Update models with a new example.
1065 |
1066 | Args:
1067 | task_name: Name of the task
1068 | input_tensor: Input embedding tensor
1069 | output_tensor: Target output embedding tensor
1070 | """
1071 | # Zero gradients
1072 | self.shared_optimizer.zero_grad()
1073 | self.task_optimizers[task_name].zero_grad()
1074 |
1075 | # Forward pass through shared model
1076 | shared_features = self.shared_model(input_tensor.unsqueeze(0))
1077 |
1078 | # Forward pass through task-specific adapter
1079 | task_features = self.task_adapters[task_name](shared_features)
1080 |
1081 | # Project back to original space
1082 | predicted_output = self.task_projectors[task_name](task_features)
1083 |
1084 | # Compute loss
1085 | loss = F.mse_loss(predicted_output.squeeze(0), output_tensor)
1086 |
1087 | # Backpropagation
1088 | loss.backward()
1089 |
1090 | # Update parameters
1091 | self.shared_optimizer.step()
1092 | self.task_optimizers[task_name].step()
1093 |
1094 | # Update metrics
1095 | self.task_metrics[task_name]["loss_history"].append(loss.item())
1096 |
1097 | # Calculate cosine similarity as a proxy for accuracy
1098 | with torch.no_grad():
1099 | cos_sim = F.cosine_similarity(predicted_output.squeeze(0), output_tensor.unsqueeze(0)).item()
1100 | self.task_metrics[task_name]["accuracy_history"].append(cos_sim)
1101 |
1102 | # Check if this is an improvement
1103 | if len(self.task_metrics[task_name]["accuracy_history"]) > 1:
1104 | prev_best = max(self.task_metrics[task_name]["accuracy_history"][:-1])
1105 | if cos_sim > prev_best:
1106 | self.task_metrics[task_name]["last_improvement"] = time.time()
1107 |
1108 | # Update overall performance metric
1109 | recent_accuracy = self.task_metrics[task_name]["accuracy_history"][-10:]
1110 | self.task_types[task_name]["performance"] = sum(recent_accuracy) / len(recent_accuracy)
1111 |
1112 | def process_task(self, task_name: str, input_embedding: np.ndarray) -> np.ndarray:
1113 | """Process an input through a specific task pipeline.
1114 |
1115 | Args:
1116 | task_name: Name of the task
1117 | input_embedding: Input embedding
1118 |
1119 | Returns:
1120 | Predicted output embedding
1121 | """
1122 | if task_name not in self.task_types:
1123 | # Unknown task type, create new adapter
1124 | self.register_task_type(task_name)
1125 |
1126 | # Convert to tensor
1127 | input_tensor = torch.FloatTensor(input_embedding)
1128 |
1129 | # Process through model
1130 | with torch.no_grad():
1131 | # Shared features
1132 | shared_features = self.shared_model(input_tensor.unsqueeze(0))
1133 |
1134 | # Task-specific processing
1135 | task_features = self.task_adapters[task_name](shared_features)
1136 |
1137 | # Project to output space
1138 | output_embedding = self.task_projectors[task_name](task_features)
1139 |
1140 | # Convert back to numpy
1141 | output = output_embedding.squeeze(0).numpy()
1142 |
1143 | return output
1144 |
1145 | def get_task_similarity(self, task_name1: str, task_name2: str) -> float:
1146 | """Calculate similarity between two tasks based on adapter weights.
1147 |
1148 | Args:
1149 | task_name1: First task name
1150 | task_name2: Second task name
1151 |
1152 | Returns:
1153 | Similarity score (0-1)
1154 | """
1155 | if task_name1 not in self.task_adapters or task_name2 not in self.task_adapters:
1156 | return 0.0
1157 |
1158 | # Get adapter parameters as vectors
1159 | params1 = []
1160 | params2 = []
1161 |
1162 | # Extract parameters
1163 | for p1, p2 in zip(self.task_adapters[task_name1].parameters(),
1164 | self.task_adapters[task_name2].parameters()):
1165 | params1.append(p1.view(-1))
1166 | params2.append(p2.view(-1))
1167 |
1168 | # Concatenate all parameters
1169 | params1 = torch.cat(params1)
1170 | params2 = torch.cat(params2)
1171 |
1172 | # Compute cosine similarity
1173 | similarity = F.cosine_similarity(params1.unsqueeze(0), params2.unsqueeze(0)).item()
1174 |
1175 | return similarity
1176 |
1177 | def find_most_similar_task(self, input_embedding: np.ndarray) -> str:
1178 | """Find the most similar task for a new input.
1179 |
1180 | Args:
1181 | input_embedding: Input embedding
1182 |
1183 | Returns:
1184 | Most similar task name
1185 | """
1186 | if not self.task_types:
1187 | return None
1188 |
1189 | # Convert to tensor
1190 | input_tensor = torch.FloatTensor(input_embedding)
1191 |
1192 | # Get shared features
1193 | with torch.no_grad():
1194 | shared_features = self.shared_model(input_tensor.unsqueeze(0))
1195 |
1196 | # Try each task adapter and measure error on this input
1197 | task_errors = {}
1198 | for task_name in self.task_types:
1199 | # Get examples for this task
1200 | examples = self.task_types[task_name]["examples"]
1201 | if not examples:
1202 | continue
1203 |
1204 | # Compute error for each example
1205 | errors = []
1206 | for ex_input, ex_output in examples:
1207 | # Process input with shared model
1208 | ex_shared = self.shared_model(ex_input.unsqueeze(0))
1209 |
1210 | # Compute feature similarity
1211 | similarity = F.cosine_similarity(shared_features, ex_shared).item()
1212 | errors.append(1.0 - similarity) # Convert to error
1213 |
1214 | # Average error for this task
1215 | if errors:
1216 | task_errors[task_name] = sum(errors) / len(errors)
1217 |
1218 | if not task_errors:
1219 | return list(self.task_types.keys())[0] # Return first task if no errors computed
1220 |
1221 | # Return task with lowest error
1222 | return min(task_errors.items(), key=lambda x: x[1])[0]
1223 |
1224 | def transfer_knowledge(self, source_task: str, target_task: str, strength: float = 0.3) -> None:
1225 | """Transfer knowledge from source task to target task.
1226 |
1227 | Args:
1228 | source_task: Source task name
1229 | target_task: Target task name
1230 | strength: Strength of knowledge transfer (0-1)
1231 | """
1232 | if source_task not in self.task_adapters or target_task not in self.task_adapters:
1233 | return
1234 |
1235 | # Skip if tasks are identical
1236 | if source_task == target_task:
1237 | return
1238 |
1239 | # Get source and target adapters
1240 | source_adapter = self.task_adapters[source_task]
1241 | target_adapter = self.task_adapters[target_task]
1242 |
1243 | # Transfer knowledge through parameter interpolation
1244 | with torch.no_grad():
1245 | for source_param, target_param in zip(source_adapter.parameters(),
1246 | target_adapter.parameters()):
1247 | # Interpolate parameters
1248 | new_param = (1 - strength) * target_param + strength * source_param
1249 | target_param.copy_(new_param)
1250 |
1251 | # Do the same for projectors
1252 | source_projector = self.task_projectors[source_task]
1253 | target_projector = self.task_projectors[target_task]
1254 |
1255 | with torch.no_grad():
1256 | for source_param, target_param in zip(source_projector.parameters(),
1257 | target_projector.parameters()):
1258 | # Interpolate parameters
1259 | new_param = (1 - strength) * target_param + strength * source_param
1260 | target_param.copy_(new_param)
1261 |
1262 | def get_task_metrics(self) -> Dict[str, Dict[str, Any]]:
1263 | """Get performance metrics for all tasks.
1264 |
1265 | Returns:
1266 | Dictionary of task metrics
1267 | """
1268 | metrics = {}
1269 |
1270 | for task_name, task_data in self.task_types.items():
1271 | task_metrics = self.task_metrics[task_name]
1272 |
1273 | # Calculate recent performance
1274 | recent_acc = task_metrics["accuracy_history"][-10:] if task_metrics["accuracy_history"] else []
1275 | recent_perf = sum(recent_acc) / len(recent_acc) if recent_acc else 0.0
1276 |
1277 | # Determine if task is improving
1278 | improving = False
1279 | if len(task_metrics["accuracy_history"]) >= 10:
1280 | first_half = task_metrics["accuracy_history"][-10:-5]
1281 | second_half = task_metrics["accuracy_history"][-5:]
1282 |
1283 | if sum(second_half) / 5 > sum(first_half) / 5:
1284 | improving = True
1285 |
1286 | # Collect metrics
1287 | metrics[task_name] = {
1288 | "examples_seen": task_metrics["examples_seen"],
1289 | "current_performance": recent_perf,
1290 | "registered_time": task_data["timestamp"],
1291 | "last_improvement": task_metrics["last_improvement"],
1292 | "improving": improving,
1293 | "difficulty": task_data["difficulty"]
1294 | }
1295 |
1296 | # Compute task similarities
1297 | similarities = {}
1298 | for other_task in self.task_types:
1299 | if other_task != task_name:
1300 | similarity = self.get_task_similarity(task_name, other_task)
1301 | similarities[other_task] = similarity
1302 |
1303 | metrics[task_name]["task_similarities"] = similarities
1304 |
1305 | return metrics
1306 |
1307 | # Causal inference system for tool selection
1308 | class CausalToolSelectionModel:
1309 | """Causal inference system for understanding tool cause-effect relationships."""
1310 |
1311 | def __init__(self, embedding_dim: int = 768):
1312 | """Initialize the causal inference system.
1313 |
1314 | Args:
1315 | embedding_dim: Dimension of embeddings
1316 | """
1317 | self.embedding_dim = embedding_dim
1318 |
1319 | # Causal graph
1320 | self.graph = nx.DiGraph() if HAVE_NETWORKX else None
1321 |
1322 | # Tool variables (nodes in the graph)
1323 | self.tool_nodes = set()
1324 |
1325 | # Context variables
1326 | self.context_nodes = set()
1327 |
1328 | # Structural equation models
1329 | self.models = {}
1330 |
1331 | # Intervention effects
1332 | self.interventions = {}
1333 |
1334 | # Counterfactual cache
1335 | self.counterfactuals = {}
1336 |
1337 | # Neural estimator for complex relationships
1338 | self.neural_estimator = nn.Sequential(
1339 | nn.Linear(embedding_dim * 2, embedding_dim),
1340 | nn.LayerNorm(embedding_dim),
1341 | nn.ReLU(),
1342 | nn.Linear(embedding_dim, embedding_dim // 2),
1343 | nn.LayerNorm(embedding_dim // 2),
1344 | nn.ReLU(),
1345 | nn.Linear(embedding_dim // 2, 1),
1346 | nn.Sigmoid()
1347 | )
1348 |
1349 | # Optimizer
1350 | self.optimizer = torch.optim.Adam(self.neural_estimator.parameters(), lr=0.001)
1351 |
1352 | def add_tool_node(self, tool_name: str):
1353 | """Add a tool as a node in the causal graph.
1354 |
1355 | Args:
1356 | tool_name: Name of the tool
1357 | """
1358 | self.tool_nodes.add(tool_name)
1359 | if self.graph is not None:
1360 | self.graph.add_node(tool_name, type="tool")
1361 |
1362 | def add_context_node(self, context_name: str):
1363 | """Add a context variable as a node.
1364 |
1365 | Args:
1366 | context_name: Name of the context variable
1367 | """
1368 | self.context_nodes.add(context_name)
1369 | if self.graph is not None:
1370 | self.graph.add_node(context_name, type="context")
1371 |
1372 | def add_causal_link(self, cause: str, effect: str, strength: float = 0.5):
1373 | """Add a causal link between nodes.
1374 |
1375 | Args:
1376 | cause: Name of the cause node
1377 | effect: Name of the effect node
1378 | strength: Strength of the causal relationship (0-1)
1379 | """
1380 | if self.graph is not None:
1381 | self.graph.add_edge(cause, effect, weight=strength)
1382 |
1383 | def observe(self, query_embedding: np.ndarray, context: Dict[str, Any],
1384 | tool_sequence: List[str], outcomes: List[Dict[str, Any]]):
1385 | """Record an observation of tool usage and outcomes.
1386 |
1387 | Args:
1388 | query_embedding: Embedding of the query
1389 | context: Context variables
1390 | tool_sequence: Sequence of tools used
1391 | outcomes: Outcomes of each tool (success, result, etc.)
1392 | """
1393 | # Convert embeddings to tensors
1394 | query_tensor = torch.FloatTensor(query_embedding)
1395 |
1396 | # Process each tool in the sequence
1397 | for i, (tool, outcome) in enumerate(zip(tool_sequence, outcomes)):
1398 | # Add tool if not already in graph
1399 | if tool not in self.tool_nodes:
1400 | self.add_tool_node(tool)
1401 |
1402 | # Add context variables
1403 | for ctx_name, ctx_value in context.items():
1404 | ctx_key = f"{ctx_name}:{ctx_value}" if isinstance(ctx_value, (str, int, bool)) else ctx_name
1405 | if ctx_key not in self.context_nodes:
1406 | self.add_context_node(ctx_key)
1407 |
1408 | # Add causal link from context to tool
1409 | self.add_causal_link(ctx_key, tool, 0.3) # Initial strength estimate
1410 |
1411 | # Add causal links between tools in sequence
1412 | if i > 0:
1413 | prev_tool = tool_sequence[i-1]
1414 | prev_outcome = outcomes[i-1]
1415 |
1416 | # Link strength based on previous success
1417 | strength = 0.7 if prev_outcome.get("success", False) else 0.2
1418 | self.add_causal_link(prev_tool, tool, strength)
1419 |
1420 | # Update neural estimator
1421 | if i > 0 and hasattr(prev_outcome, "embedding") and hasattr(outcome, "embedding"):
1422 | # Training example for neural estimator
1423 | prev_embed = torch.FloatTensor(prev_outcome["embedding"])
1424 | curr_embed = torch.FloatTensor(outcome["embedding"])
1425 |
1426 | combined = torch.cat([prev_embed, curr_embed])
1427 | target = torch.FloatTensor([strength])
1428 |
1429 | # Update neural estimator
1430 | self.optimizer.zero_grad()
1431 | pred = self.neural_estimator(combined.unsqueeze(0))
1432 | loss = F.mse_loss(pred, target)
1433 | loss.backward()
1434 | self.optimizer.step()
1435 |
1436 | def infer_effects(self, intervention_tool: str) -> Dict[str, float]:
1437 | """Infer the effects of using a specific tool.
1438 |
1439 | Args:
1440 | intervention_tool: The tool to intervene with
1441 |
1442 | Returns:
1443 | Dictionary of effects on other tools/outcomes
1444 | """
1445 | if self.graph is None:
1446 | return {}
1447 |
1448 | # Use do-calculus to determine causal effects
1449 | effects = {}
1450 |
1451 | # Create a modified graph for the intervention
1452 | intervention_graph = self.graph.copy()
1453 |
1454 | # Remove incoming edges to the intervention tool (do-operator)
1455 | for pred in list(self.graph.predecessors(intervention_tool)):
1456 | intervention_graph.remove_edge(pred, intervention_tool)
1457 |
1458 | # Compute effect on each tool
1459 | for tool in self.tool_nodes:
1460 | if tool == intervention_tool:
1461 | continue
1462 |
1463 | # Check if there's a path from intervention to this tool
1464 | if nx.has_path(intervention_graph, intervention_tool, tool):
1465 | # Compute causal effect strength using path weights
1466 | paths = list(nx.all_simple_paths(intervention_graph, intervention_tool, tool))
1467 |
1468 | effect = 0.0
1469 | for path in paths:
1470 | # Calculate path strength as product of edge weights
1471 | path_strength = 1.0
1472 | for i in range(len(path) - 1):
1473 | path_strength *= intervention_graph[path[i]][path[i+1]]["weight"]
1474 |
1475 | effect += path_strength
1476 |
1477 | # Normalize for multiple paths
1478 | if len(paths) > 0:
1479 | effect /= len(paths)
1480 |
1481 | effects[tool] = effect
1482 |
1483 | # Cache result
1484 | self.interventions[intervention_tool] = effects
1485 |
1486 | return effects
1487 |
1488 | def estimate_counterfactual(self, observed_tools: List[str],
1489 | alternative_tool: str) -> float:
1490 | """Estimate the outcome difference if an alternative tool had been used.
1491 |
1492 | Args:
1493 | observed_tools: The tools that were actually used
1494 | alternative_tool: The alternative tool to consider
1495 |
1496 | Returns:
1497 | Estimated improvement (positive) or decline (negative) in outcome
1498 | """
1499 | # Use nested counterfactual estimation
1500 | key = (tuple(observed_tools), alternative_tool)
1501 |
1502 | if key in self.counterfactuals:
1503 | return self.counterfactuals[key]
1504 |
1505 | if self.graph is None or not observed_tools:
1506 | return 0.0
1507 |
1508 | # Find the position to replace
1509 | best_pos = 0
1510 | best_effect = -float('inf')
1511 |
1512 | for i in range(len(observed_tools)):
1513 | # Consider replacing the tool at position i
1514 | tools_copy = observed_tools.copy()
1515 | original_tool = tools_copy[i]
1516 | tools_copy[i] = alternative_tool
1517 |
1518 | # Estimate effect of this change
1519 | effect = 0.0
1520 |
1521 | # Effect from replacing the original tool
1522 | if original_tool in self.interventions:
1523 | effect -= sum(self.interventions[original_tool].values())
1524 |
1525 | # Effect from using the alternative tool
1526 | if alternative_tool in self.interventions:
1527 | effect += sum(self.interventions[alternative_tool].values())
1528 |
1529 | # Check if this is the best position
1530 | if effect > best_effect:
1531 | best_effect = effect
1532 | best_pos = i
1533 |
1534 | # Estimate the counterfactual difference
1535 | counterfactual = best_effect / len(observed_tools)
1536 |
1537 | # Cache the result
1538 | self.counterfactuals[key] = counterfactual
1539 |
1540 | return counterfactual
1541 |
1542 | # Advanced Graph Neural Network for modeling tool relationships
1543 | class ToolRelationshipGNN(nn.Module):
1544 | """Graph Neural Network for modeling relationships between tools."""
1545 |
1546 | def __init__(self, embedding_dim: int, hidden_dim: int, num_tools: int):
1547 | """Initialize the GNN with appropriate dimensions.
1548 |
1549 | Args:
1550 | embedding_dim: Dimension of input embeddings
1551 | hidden_dim: Dimension of hidden layers
1552 | num_tools: Number of tools in the system
1553 | """
1554 | super(ToolRelationshipGNN, self).__init__()
1555 |
1556 | self.embedding_dim = embedding_dim
1557 | self.hidden_dim = hidden_dim
1558 | self.num_tools = num_tools
1559 |
1560 | # Node embedding layers
1561 | self.node_encoder = nn.Sequential(
1562 | nn.Linear(embedding_dim, hidden_dim),
1563 | nn.LayerNorm(hidden_dim),
1564 | nn.ReLU(),
1565 | nn.Linear(hidden_dim, hidden_dim),
1566 | nn.LayerNorm(hidden_dim),
1567 | nn.ReLU()
1568 | )
1569 |
1570 | # Edge embedding layers
1571 | self.edge_encoder = nn.Sequential(
1572 | nn.Linear(embedding_dim * 2, hidden_dim),
1573 | nn.LayerNorm(hidden_dim),
1574 | nn.ReLU(),
1575 | nn.Linear(hidden_dim, hidden_dim),
1576 | nn.LayerNorm(hidden_dim),
1577 | nn.ReLU()
1578 | )
1579 |
1580 | # Message passing layers
1581 | self.message_mlp = nn.Sequential(
1582 | nn.Linear(hidden_dim * 2, hidden_dim),
1583 | nn.LayerNorm(hidden_dim),
1584 | nn.ReLU(),
1585 | nn.Linear(hidden_dim, hidden_dim)
1586 | )
1587 |
1588 | # Node update layers
1589 | self.node_update = nn.GRUCell(hidden_dim, hidden_dim)
1590 |
1591 | # Output projection
1592 | self.output_projection = nn.Linear(hidden_dim, embedding_dim)
1593 |
1594 | # Attention mechanism for node aggregation
1595 | self.attention = nn.Sequential(
1596 | nn.Linear(hidden_dim * 2, hidden_dim),
1597 | nn.Tanh(),
1598 | nn.Linear(hidden_dim, 1)
1599 | )
1600 |
1601 | def forward(self, node_embeddings: torch.Tensor, adjacency_matrix: torch.Tensor) -> torch.Tensor:
1602 | """Forward pass through the GNN.
1603 |
1604 | Args:
1605 | node_embeddings: Tool embeddings tensor [num_tools, embedding_dim]
1606 | adjacency_matrix: Tool relationship adjacency matrix [num_tools, num_tools]
1607 |
1608 | Returns:
1609 | Updated node embeddings
1610 | """
1611 | batch_size = node_embeddings.shape[0]
1612 |
1613 | # Initial node encoding
1614 | node_hidden = self.node_encoder(node_embeddings) # [batch, num_tools, hidden_dim]
1615 |
1616 | # Message passing (3 rounds)
1617 | for _ in range(3):
1618 | # Compute messages for each edge
1619 | messages = []
1620 | attention_weights = []
1621 |
1622 | for i in range(self.num_tools):
1623 | for j in range(self.num_tools):
1624 | # Only consider edges that exist in the adjacency matrix
1625 | if adjacency_matrix[i, j] > 0:
1626 | # Combine source and destination node features
1627 | edge_features = torch.cat([node_hidden[:, i], node_hidden[:, j]], dim=1)
1628 | message = self.message_mlp(edge_features)
1629 | messages.append((j, message)) # Message to node j
1630 |
1631 | # Compute attention weight
1632 | attn_input = torch.cat([node_hidden[:, j], message], dim=1)
1633 | weight = self.attention(attn_input)
1634 | attention_weights.append((j, weight))
1635 |
1636 | # Aggregate messages for each node using attention
1637 | aggregated_messages = torch.zeros(batch_size, self.num_tools, self.hidden_dim,
1638 | device=node_embeddings.device)
1639 |
1640 | # Group messages by destination node
1641 | node_messages = defaultdict(list)
1642 | node_weights = defaultdict(list)
1643 |
1644 | for j, message in messages:
1645 | node_messages[j].append(message)
1646 |
1647 | for j, weight in attention_weights:
1648 | node_weights[j].append(weight)
1649 |
1650 | # Apply attention for each node
1651 | for j in range(self.num_tools):
1652 | if j in node_messages:
1653 | stacked_messages = torch.stack(node_messages[j], dim=1) # [batch, num_msgs, hidden]
1654 | stacked_weights = torch.stack(node_weights[j], dim=1) # [batch, num_msgs, 1]
1655 |
1656 | # Apply softmax to get attention distribution
1657 | normalized_weights = F.softmax(stacked_weights, dim=1)
1658 |
1659 | # Weighted sum of messages
1660 | node_message = torch.sum(stacked_messages * normalized_weights, dim=1)
1661 | aggregated_messages[:, j] = node_message
1662 |
1663 | # Update node states using GRU
1664 | node_hidden_reshaped = node_hidden.view(batch_size * self.num_tools, self.hidden_dim)
1665 | aggregated_messages_reshaped = aggregated_messages.view(batch_size * self.num_tools, self.hidden_dim)
1666 |
1667 | updated_hidden = self.node_update(aggregated_messages_reshaped, node_hidden_reshaped)
1668 | node_hidden = updated_hidden.view(batch_size, self.num_tools, self.hidden_dim)
1669 |
1670 | # Project back to embedding space
1671 | output_embeddings = self.output_projection(node_hidden)
1672 |
1673 | return output_embeddings
1674 |
1675 | # Enhanced Meta-Learning System
1676 | class MetaLearningOptimizer:
1677 | """Meta-learning system that learns to generalize across different types of tasks."""
1678 |
1679 | def __init__(self, embedding_dim: int, num_tools: int, learning_rate: float = 0.001):
1680 | """Initialize the meta-learning optimizer.
1681 |
1682 | Args:
1683 | embedding_dim: Dimension of embeddings
1684 | num_tools: Number of tools in the system
1685 | learning_rate: Learning rate for meta-updates
1686 | """
1687 | self.embedding_dim = embedding_dim
1688 | self.num_tools = num_tools
1689 | self.learning_rate = learning_rate
1690 |
1691 | # Task type embeddings
1692 | self.task_embeddings = {}
1693 |
1694 | # Tool parameter embeddings
1695 | self.tool_parameters = nn.ParameterDict()
1696 |
1697 | # Meta-network for adaptation
1698 | self.meta_network = nn.Sequential(
1699 | nn.Linear(embedding_dim * 2, embedding_dim),
1700 | nn.LayerNorm(embedding_dim),
1701 | nn.ReLU(),
1702 | nn.Linear(embedding_dim, embedding_dim)
1703 | )
1704 |
1705 | # Optimizer for meta-parameters
1706 | self.optimizer = torch.optim.Adam(self.meta_network.parameters(), lr=learning_rate)
1707 |
1708 | # Task buffers for meta-learning
1709 | self.task_buffers = defaultdict(list)
1710 | self.max_buffer_size = 100
1711 |
1712 | def register_tool(self, tool_name: str, initial_embedding: np.ndarray):
1713 | """Register a tool with the meta-learning system.
1714 |
1715 | Args:
1716 | tool_name: Name of the tool
1717 | initial_embedding: Initial embedding for the tool
1718 | """
1719 | self.tool_parameters[tool_name] = nn.Parameter(
1720 | torch.FloatTensor(initial_embedding),
1721 | requires_grad=True
1722 | )
1723 |
1724 | def add_task_example(self, task_type: str, query_embedding: np.ndarray,
1725 | selected_tool: str, success: bool, reward: float):
1726 | """Add an example to a task buffer for meta-learning.
1727 |
1728 | Args:
1729 | task_type: Type of task (e.g., "search", "explanation")
1730 | query_embedding: Embedding of the query
1731 | selected_tool: Name of the selected tool
1732 | success: Whether the tool was successful
1733 | reward: The reward received
1734 | """
1735 | # Convert to tensor
1736 | query_tensor = torch.FloatTensor(query_embedding)
1737 |
1738 | # Add to task buffer
1739 | self.task_buffers[task_type].append({
1740 | "query": query_tensor,
1741 | "tool": selected_tool,
1742 | "success": success,
1743 | "reward": reward
1744 | })
1745 |
1746 | # Limit buffer size
1747 | if len(self.task_buffers[task_type]) > self.max_buffer_size:
1748 | self.task_buffers[task_type].pop(0)
1749 |
1750 | def meta_update(self):
1751 | """Perform a meta-update step to improve adaptation capability."""
1752 | if not self.task_buffers:
1753 | return
1754 |
1755 | # Sample a batch of tasks
1756 | sampled_tasks = random.sample(list(self.task_buffers.keys()),
1757 | min(5, len(self.task_buffers)))
1758 |
1759 | meta_loss = 0.0
1760 |
1761 | for task_type in sampled_tasks:
1762 | # Skip tasks with too few examples
1763 | if len(self.task_buffers[task_type]) < 5:
1764 | continue
1765 |
1766 | # Sample examples from this task
1767 | examples = random.sample(self.task_buffers[task_type],
1768 | min(10, len(self.task_buffers[task_type])))
1769 |
1770 | # Compute task embedding if not already computed
1771 | if task_type not in self.task_embeddings:
1772 | # Average query embeddings as task embedding
1773 | query_tensors = [ex["query"] for ex in examples]
1774 | task_embedding = torch.stack(query_tensors).mean(dim=0)
1775 | self.task_embeddings[task_type] = task_embedding
1776 |
1777 | # Create adapted tool parameters for this task
1778 | adapted_params = {}
1779 | for tool_name, param in self.tool_parameters.items():
1780 | # Concatenate task embedding with tool parameter
1781 | adaptation_input = torch.cat([self.task_embeddings[task_type], param])
1782 |
1783 | # Generate adaptation
1784 | adaptation = self.meta_network(adaptation_input.unsqueeze(0)).squeeze(0)
1785 |
1786 | # Apply adaptation
1787 | adapted_params[tool_name] = param + adaptation
1788 |
1789 | # Compute loss for this task
1790 | task_loss = 0.0
1791 | for example in examples:
1792 | query = example["query"]
1793 | selected_tool = example["tool"]
1794 | reward = example["reward"]
1795 |
1796 | # Compute scores for all tools
1797 | scores = {}
1798 | for tool_name, param in adapted_params.items():
1799 | score = torch.dot(query, param) / (query.norm() * param.norm())
1800 | scores[tool_name] = score
1801 |
1802 | # Convert to probability distribution
1803 | logits = torch.stack(list(scores.values()))
1804 | probs = F.softmax(logits, dim=0)
1805 |
1806 | # Get index of selected tool
1807 | tool_idx = list(scores.keys()).index(selected_tool)
1808 |
1809 | # Negative log likelihood weighted by reward
1810 | nll = -torch.log(probs[tool_idx])
1811 | task_loss += nll * (1.0 - reward) # Lower loss for high rewards
1812 |
1813 | # Add to meta loss
1814 | meta_loss += task_loss / len(examples)
1815 |
1816 | # Normalize by number of tasks
1817 | meta_loss /= len(sampled_tasks)
1818 |
1819 | # Update meta-parameters
1820 | self.optimizer.zero_grad()
1821 | meta_loss.backward()
1822 | self.optimizer.step()
1823 |
1824 | return meta_loss.item()
1825 |
1826 | def get_adapted_embeddings(self, task_type: str) -> Dict[str, np.ndarray]:
1827 | """Get task-adapted embeddings for tools.
1828 |
1829 | Args:
1830 | task_type: Type of task
1831 |
1832 | Returns:
1833 | Dictionary of adapted tool embeddings
1834 | """
1835 | # Return original embeddings if task type is unknown
1836 | if task_type not in self.task_embeddings:
1837 | return {name: param.detach().numpy() for name, param in self.tool_parameters.items()}
1838 |
1839 | # Create adapted embeddings
1840 | adapted_embeddings = {}
1841 | for tool_name, param in self.tool_parameters.items():
1842 | # Concatenate task embedding with tool parameter
1843 | adaptation_input = torch.cat([self.task_embeddings[task_type], param])
1844 |
1845 | # Generate adaptation
1846 | adaptation = self.meta_network(adaptation_input.unsqueeze(0)).squeeze(0)
1847 |
1848 | # Apply adaptation
1849 | adapted_embeddings[tool_name] = (param + adaptation).detach().numpy()
1850 |
1851 | return adapted_embeddings
1852 |
1853 |
1854 | @dataclass
1855 | class ToolUsageRecord:
1856 | """Record of a tool usage for optimization."""
1857 | query: str
1858 | tool_name: str
1859 | execution_time: float
1860 | token_usage: Dict[str, int]
1861 | success: bool
1862 | timestamp: float
1863 |
1864 |
1865 | class ToolUsageTracker:
1866 | """Tracks tool usage for optimization."""
1867 |
1868 | def __init__(self, max_records: int = 10000):
1869 | """
1870 | Initialize the tool usage tracker.
1871 |
1872 | Args:
1873 | max_records: Maximum number of records to store
1874 | """
1875 | self.records = deque(maxlen=max_records)
1876 |
1877 | def add_record(self, record: ToolUsageRecord) -> None:
1878 | """Add a record to the tracker."""
1879 | self.records.append(record)
1880 |
1881 | def get_tool_stats(self) -> Dict[str, Dict[str, Any]]:
1882 | """
1883 | Get statistics about tool usage.
1884 |
1885 | Returns:
1886 | Dictionary of tool statistics
1887 | """
1888 | stats = {}
1889 |
1890 | # Group by tool
1891 | for record in self.records:
1892 | if record.tool_name not in stats:
1893 | stats[record.tool_name] = {
1894 | "count": 0,
1895 | "success_count": 0,
1896 | "total_time": 0,
1897 | "token_usage": {"prompt": 0, "completion": 0, "total": 0},
1898 | }
1899 |
1900 | stats[record.tool_name]["count"] += 1
1901 | if record.success:
1902 | stats[record.tool_name]["success_count"] += 1
1903 | stats[record.tool_name]["total_time"] += record.execution_time
1904 |
1905 | # Update token usage
1906 | for key, value in record.token_usage.items():
1907 | stats[record.tool_name]["token_usage"][key] += value
1908 |
1909 | # Compute derived metrics
1910 | for tool_name, tool_stats in stats.items():
1911 | tool_stats["success_rate"] = tool_stats["success_count"] / tool_stats["count"] if tool_stats["count"] > 0 else 0
1912 | tool_stats["avg_time"] = tool_stats["total_time"] / tool_stats["count"] if tool_stats["count"] > 0 else 0
1913 |
1914 | for key in tool_stats["token_usage"]:
1915 | tool_stats[f"avg_{key}_tokens"] = tool_stats["token_usage"][key] / tool_stats["count"] if tool_stats["count"] > 0 else 0
1916 |
1917 | return stats
1918 |
1919 |
1920 | class ToolSelectionOptimizer:
1921 | """
1922 | Optimizes tool selection based on user queries and context.
1923 | Uses reinforcement learning to improve tool selection over time.
1924 | """
1925 |
1926 | def __init__(
1927 | self,
1928 | tool_registry: Any,
1929 | data_dir: str = "./data/rl",
1930 | enable_rl: bool = True,
1931 | model_update_interval: int = 100,
1932 | embedding_model_name: str = "all-MiniLM-L6-v2",
1933 | embedding_cache_size: int = 1000,
1934 | ):
1935 | """
1936 | Initialize the tool selection optimizer.
1937 |
1938 | Args:
1939 | tool_registry: Registry containing available tools
1940 | data_dir: Directory to store data and models
1941 | enable_rl: Whether to enable reinforcement learning
1942 | model_update_interval: How often to update models (in observations)
1943 | embedding_model_name: Name of the sentence embedding model
1944 | embedding_cache_size: Size of the embedding cache
1945 | """
1946 | self.tool_registry = tool_registry
1947 | self.data_dir = data_dir
1948 | self.enable_rl = enable_rl
1949 | self.model_update_interval = model_update_interval
1950 |
1951 | # Create data directory
1952 | os.makedirs(data_dir, exist_ok=True)
1953 |
1954 | # Initialize tool usage tracker
1955 | self.tracker = ToolUsageTracker()
1956 |
1957 | # Initialize embedding model if available
1958 | self.embedding_model = None
1959 | self.embedding_cache = {}
1960 | self.embedding_cache_keys = deque(maxlen=embedding_cache_size)
1961 |
1962 | if HAVE_SENTENCE_TRANSFORMERS and enable_rl:
1963 | try:
1964 | self.embedding_model = SentenceTransformer(embedding_model_name)
1965 | except Exception as e:
1966 | print(f"Warning: Failed to load embedding model: {e}")
1967 |
1968 | # Initialize RL system if enabled
1969 | self.rl_system = None
1970 | if enable_rl:
1971 | # Define a simple context evaluator
1972 | def context_evaluator(context):
1973 | # This is a placeholder - in a real system, we'd evaluate the quality
1974 | # based on metrics like response coherence, success rate, etc.
1975 | return 0.5
1976 |
1977 | # Create RL system
1978 | self.rl_system = ToolSelectionGRPO(
1979 | tool_registry=tool_registry,
1980 | context_evaluator=context_evaluator,
1981 | update_interval=model_update_interval,
1982 | )
1983 |
1984 | # Load existing models and data if available
1985 | self._load_data()
1986 |
1987 | def select_tool(self, query: str, context: Dict[str, Any], visualizer=None) -> str:
1988 | """
1989 | Select the best tool to use for a given query.
1990 |
1991 | Args:
1992 | query: User query
1993 | context: Conversation context
1994 | visualizer: Optional visualizer to display the selection process
1995 |
1996 | Returns:
1997 | Name of the selected tool
1998 | """
1999 | # If RL is not enabled, use default selection logic
2000 | if not self.enable_rl or self.rl_system is None:
2001 | return self._default_tool_selection(query, context)
2002 |
2003 | # Use RL system to select tool
2004 | try:
2005 | return self.rl_system.select_tool(query, context, visualizer=visualizer)
2006 | except Exception as e:
2007 | print(f"Error in RL tool selection: {e}")
2008 | return self._default_tool_selection(query, context)
2009 |
2010 | def record_tool_usage(
2011 | self,
2012 | query: str,
2013 | tool_name: str,
2014 | execution_time: float,
2015 | token_usage: Dict[str, int],
2016 | success: bool,
2017 | context: Optional[Dict[str, Any]] = None,
2018 | result: Optional[Any] = None,
2019 | ) -> None:
2020 | """
2021 | Record tool usage for optimization.
2022 |
2023 | Args:
2024 | query: User query
2025 | tool_name: Name of the tool used
2026 | execution_time: Time taken to execute the tool
2027 | token_usage: Token usage information
2028 | success: Whether the tool usage was successful
2029 | context: Conversation context (for RL)
2030 | result: Result of the tool usage (for RL)
2031 | """
2032 | # Create and add record
2033 | record = ToolUsageRecord(
2034 | query=query,
2035 | tool_name=tool_name,
2036 | execution_time=execution_time,
2037 | token_usage=token_usage,
2038 | success=success,
2039 | timestamp=time.time(),
2040 | )
2041 | self.tracker.add_record(record)
2042 |
2043 | # Update RL system if enabled
2044 | if self.enable_rl and self.rl_system is not None and context is not None:
2045 | try:
2046 | # Find the agent that made this selection
2047 | for agent_id in self.rl_system.current_episode:
2048 | if agent_id in self.rl_system.current_episode and self.rl_system.current_episode[agent_id]:
2049 | # Observe the result
2050 | self.rl_system.observe_result(
2051 | agent_id=agent_id,
2052 | result=result,
2053 | context=context,
2054 | done=True,
2055 | )
2056 | except Exception as e:
2057 | print(f"Error updating RL system: {e}")
2058 |
2059 | # Save data periodically
2060 | if len(self.tracker.records) % 50 == 0:
2061 | self._save_data()
2062 |
2063 | def get_tool_recommendations(self, query: str) -> List[Tuple[str, float]]:
2064 | """
2065 | Get tool recommendations for a query with confidence scores.
2066 |
2067 | Args:
2068 | query: User query
2069 |
2070 | Returns:
2071 | List of (tool_name, confidence) tuples
2072 | """
2073 | # Get query embedding
2074 | if self.embedding_model is not None:
2075 | try:
2076 | query_embedding = self._get_embedding(query)
2077 |
2078 | # Get all tools and their embeddings
2079 | tools = self.tool_registry.get_all_tools()
2080 | tool_scores = []
2081 |
2082 | for tool in tools:
2083 | # Get tool description embedding
2084 | tool_desc = tool.description
2085 | tool_embedding = self._get_embedding(tool_desc)
2086 |
2087 | # Compute similarity score
2088 | similarity = self._cosine_similarity(query_embedding, tool_embedding)
2089 | tool_scores.append((tool.name, similarity))
2090 |
2091 | # Sort by score
2092 | tool_scores.sort(key=lambda x: x[1], reverse=True)
2093 | return tool_scores
2094 |
2095 | except Exception as e:
2096 | print(f"Error computing tool recommendations: {e}")
2097 |
2098 | # Fallback to default ordering
2099 | return [(tool, 0.5) for tool in self.tool_registry.get_all_tool_names()]
2100 |
2101 | def update_model(self) -> Dict[str, Any]:
2102 | """
2103 | Manually trigger a model update.
2104 |
2105 | Returns:
2106 | Dictionary of update metrics
2107 | """
2108 | if not self.enable_rl or self.rl_system is None:
2109 | return {"status": "RL not enabled"}
2110 |
2111 | try:
2112 | metrics = self.rl_system.update()
2113 |
2114 | # Save updated model
2115 | self.rl_system.save()
2116 |
2117 | return {"status": "success", "metrics": metrics}
2118 |
2119 | except Exception as e:
2120 | return {"status": "error", "message": str(e)}
2121 |
2122 | def _default_tool_selection(self, query: str, context: Dict[str, Any]) -> str:
2123 | """
2124 | Default tool selection logic when RL is not available.
2125 |
2126 | Args:
2127 | query: User query
2128 | context: Conversation context
2129 |
2130 | Returns:
2131 | Name of the selected tool
2132 | """
2133 | # Use a simple rule-based approach as fallback
2134 | tools = self.tool_registry.get_all_tool_names()
2135 |
2136 | # Look for keywords in the query
2137 | query_lower = query.lower()
2138 |
2139 | if "file" in query_lower and "read" in query_lower:
2140 | for tool in tools:
2141 | if tool.lower() == "view":
2142 | return tool
2143 |
2144 | if "search" in query_lower or "find" in query_lower:
2145 | for tool in tools:
2146 | if "grep" in tool.lower():
2147 | return tool
2148 |
2149 | if "execute" in query_lower or "run" in query_lower:
2150 | for tool in tools:
2151 | if tool.lower() == "bash":
2152 | return tool
2153 |
2154 | if "edit" in query_lower or "change" in query_lower:
2155 | for tool in tools:
2156 | if tool.lower() == "edit":
2157 | return tool
2158 |
2159 | # Default to the first tool
2160 | return tools[0] if tools else "View"
2161 |
2162 | def _get_embedding(self, text: str) -> np.ndarray:
2163 | """
2164 | Get embedding for text with caching.
2165 |
2166 | Args:
2167 | text: Text to embed
2168 |
2169 | Returns:
2170 | Embedding vector
2171 | """
2172 | if text in self.embedding_cache:
2173 | return self.embedding_cache[text]
2174 |
2175 | if self.embedding_model is None:
2176 | raise ValueError("Embedding model not available")
2177 |
2178 | # Generate embedding
2179 | embedding = self.embedding_model.encode(text, show_progress_bar=False)
2180 |
2181 | # Cache embedding
2182 | if len(self.embedding_cache_keys) >= self.embedding_cache_keys.maxlen:
2183 | # Remove oldest key if cache is full
2184 | oldest_key = self.embedding_cache_keys.popleft()
2185 | self.embedding_cache.pop(oldest_key, None)
2186 |
2187 | self.embedding_cache[text] = embedding
2188 | self.embedding_cache_keys.append(text)
2189 |
2190 | return embedding
2191 |
2192 | def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
2193 | """
2194 | Compute cosine similarity between two vectors.
2195 |
2196 | Args:
2197 | a: First vector
2198 | b: Second vector
2199 |
2200 | Returns:
2201 | Cosine similarity score
2202 | """
2203 | return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
2204 |
2205 | def _save_data(self) -> None:
2206 | """Save data and models."""
2207 | try:
2208 | # Create data file
2209 | data_path = os.path.join(self.data_dir, "tool_usage_data.json")
2210 |
2211 | # Convert records to serializable format
2212 | records_data = []
2213 | for record in self.tracker.records:
2214 | records_data.append({
2215 | "query": record.query,
2216 | "tool_name": record.tool_name,
2217 | "execution_time": record.execution_time,
2218 | "token_usage": record.token_usage,
2219 | "success": record.success,
2220 | "timestamp": record.timestamp,
2221 | })
2222 |
2223 | # Write data
2224 | with open(data_path, "w") as f:
2225 | json.dump(records_data, f)
2226 |
2227 | # Save RL model if available
2228 | if self.enable_rl and self.rl_system is not None:
2229 | self.rl_system.save()
2230 |
2231 | except Exception as e:
2232 | print(f"Error saving optimizer data: {e}")
2233 |
2234 | def _load_data(self) -> None:
2235 | """Load data and models."""
2236 | try:
2237 | # Load data file
2238 | data_path = os.path.join(self.data_dir, "tool_usage_data.json")
2239 |
2240 | if os.path.exists(data_path):
2241 | with open(data_path, "r") as f:
2242 | records_data = json.load(f)
2243 |
2244 | # Convert to records
2245 | for record_data in records_data:
2246 | record = ToolUsageRecord(
2247 | query=record_data["query"],
2248 | tool_name=record_data["tool_name"],
2249 | execution_time=record_data["execution_time"],
2250 | token_usage=record_data["token_usage"],
2251 | success=record_data["success"],
2252 | timestamp=record_data["timestamp"],
2253 | )
2254 | self.tracker.add_record(record)
2255 |
2256 | # Load RL model if available
2257 | if self.enable_rl and self.rl_system is not None:
2258 | try:
2259 | self.rl_system.load()
2260 | except Exception as e:
2261 | print(f"Error loading RL model: {e}")
2262 |
2263 | except Exception as e:
2264 | print(f"Error loading optimizer data: {e}")
2265 |
2266 |
2267 | class ToolSelectionManager:
2268 | """
2269 | Manages tool selection for Claude Code Python.
2270 | Provides an interface for selecting tools and recording usage.
2271 | """
2272 |
2273 | def __init__(
2274 | self,
2275 | tool_registry: Any,
2276 | enable_optimization: bool = True,
2277 | data_dir: str = "./data/rl",
2278 | ):
2279 | """
2280 | Initialize the tool selection manager.
2281 |
2282 | Args:
2283 | tool_registry: Registry containing available tools
2284 | enable_optimization: Whether to enable optimization
2285 | data_dir: Directory to store data and models
2286 | """
2287 | self.tool_registry = tool_registry
2288 | self.enable_optimization = enable_optimization
2289 |
2290 | # Initialize optimizer if enabled
2291 | self.optimizer = None
2292 | if enable_optimization:
2293 | self.optimizer = ToolSelectionOptimizer(
2294 | tool_registry=tool_registry,
2295 | data_dir=data_dir,
2296 | enable_rl=True,
2297 | )
2298 |
2299 | def select_tool(self, query: str, context: Dict[str, Any]) -> str:
2300 | """
2301 | Select the best tool to use for a given query.
2302 |
2303 | Args:
2304 | query: User query
2305 | context: Conversation context
2306 |
2307 | Returns:
2308 | Name of the selected tool
2309 | """
2310 | if self.optimizer is not None:
2311 | return self.optimizer.select_tool(query, context)
2312 |
2313 | # Use default selection if optimizer is not available
2314 | return self._default_selection(query)
2315 |
2316 | def record_tool_usage(
2317 | self,
2318 | query: str,
2319 | tool_name: str,
2320 | execution_time: float,
2321 | token_usage: Dict[str, int],
2322 | success: bool,
2323 | context: Optional[Dict[str, Any]] = None,
2324 | result: Optional[Any] = None,
2325 | ) -> None:
2326 | """
2327 | Record tool usage for optimization.
2328 |
2329 | Args:
2330 | query: User query
2331 | tool_name: Name of the tool used
2332 | execution_time: Time taken to execute the tool
2333 | token_usage: Token usage information
2334 | success: Whether the tool usage was successful
2335 | context: Conversation context (for RL)
2336 | result: Result of the tool usage (for RL)
2337 | """
2338 | if self.optimizer is not None:
2339 | self.optimizer.record_tool_usage(
2340 | query=query,
2341 | tool_name=tool_name,
2342 | execution_time=execution_time,
2343 | token_usage=token_usage,
2344 | success=success,
2345 | context=context,
2346 | result=result,
2347 | )
2348 |
2349 | def get_tool_recommendations(self, query: str) -> List[Tuple[str, float]]:
2350 | """
2351 | Get tool recommendations for a query with confidence scores.
2352 |
2353 | Args:
2354 | query: User query
2355 |
2356 | Returns:
2357 | List of (tool_name, confidence) tuples
2358 | """
2359 | if self.optimizer is not None:
2360 | return self.optimizer.get_tool_recommendations(query)
2361 |
2362 | # Return default recommendations if optimizer is not available
2363 | return [(tool, 0.5) for tool in self.tool_registry.get_all_tool_names()]
2364 |
2365 | def _default_selection(self, query: str) -> str:
2366 | """
2367 | Default tool selection logic when optimization is not available.
2368 |
2369 | Args:
2370 | query: User query
2371 |
2372 | Returns:
2373 | Name of the selected tool
2374 | """
2375 | # Use a simple rule-based approach as fallback
2376 | tools = self.tool_registry.get_all_tool_names()
2377 |
2378 | # Default to the first tool
2379 | return tools[0] if tools else "View"
```