File size: 67,146 Bytes
df5964c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
# Multi-Agent AI Collaboration System
# Author: Spencer Purdy
# Description: Enterprise-grade multi-agent system with specialized AI agents collaborating
# to solve complex problems through task decomposition and parallel processing.

# Installation (uncomment for Google Colab)
# !pip install gradio langchain langchain-openai openai networkx matplotlib asyncio aiohttp pandas numpy plotly python-dotenv pydantic

import os
import json
import time
import asyncio
import hashlib
import logging
from datetime import datetime
from typing import Dict, List, Tuple, Optional, Any, Union, Set
from dataclasses import dataclass, field
from enum import Enum
import warnings
warnings.filterwarnings('ignore')

# Core libraries
import gradio as gr
import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# LangChain and AI libraries
from langchain.schema import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain.callbacks.base import BaseCallbackHandler
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.memory import ConversationBufferMemory
from pydantic import BaseModel, Field

# Async libraries
import aiohttp
from concurrent.futures import ThreadPoolExecutor

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class Config:
    """Configuration settings for the multi-agent system."""
    
    # Model settings
    DEFAULT_MODEL = "gpt-4"
    TEMPERATURE = 0.7
    MAX_TOKENS = 1500
    
    # Agent settings
    MAX_ITERATIONS = 10
    COLLABORATION_TIMEOUT = 300  # seconds
    
    # Visualization settings
    GRAPH_UPDATE_INTERVAL = 0.5  # seconds
    NODE_COLORS = {
        'Researcher': '#3498db',
        'Analyst': '#e74c3c',
        'Critic': '#f39c12',
        'Synthesizer': '#2ecc71',
        'Coordinator': '#9b59b6'
    }
    
    # Report settings
    CONFIDENCE_THRESHOLD = 0.7
    MAX_REPORT_SECTIONS = 10

class AgentRole(Enum):
    """Enumeration of agent roles in the system."""
    RESEARCHER = "Researcher"
    ANALYST = "Analyst"
    CRITIC = "Critic"
    SYNTHESIZER = "Synthesizer"
    COORDINATOR = "Coordinator"

class TaskStatus(Enum):
    """Task execution status."""
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    COMPLETED = "completed"
    FAILED = "failed"

@dataclass
class Task:
    """Represents a task to be executed by agents."""
    id: str
    description: str
    assigned_to: Optional[str] = None
    status: TaskStatus = TaskStatus.PENDING
    dependencies: List[str] = field(default_factory=list)
    result: Optional[Any] = None
    confidence: float = 0.0
    created_at: datetime = field(default_factory=datetime.now)
    completed_at: Optional[datetime] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

@dataclass
class AgentMessage:
    """Message passed between agents."""
    sender: str
    recipient: str
    content: str
    message_type: str = "task"
    metadata: Dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=datetime.now)

class AgentMemory:
    """Manages agent conversation history and context."""
    
    def __init__(self, max_messages: int = 50):
        self.messages: List[AgentMessage] = []
        self.max_messages = max_messages
        self.context: Dict[str, Any] = {}
        
    def add_message(self, message: AgentMessage):
        """Add a message to memory."""
        self.messages.append(message)
        if len(self.messages) > self.max_messages:
            self.messages.pop(0)
    
    def get_recent_messages(self, n: int = 10) -> List[AgentMessage]:
        """Get n most recent messages."""
        return self.messages[-n:]
    
    def get_messages_by_sender(self, sender: str) -> List[AgentMessage]:
        """Get all messages from a specific sender."""
        return [msg for msg in self.messages if msg.sender == sender]
    
    def update_context(self, key: str, value: Any):
        """Update context information."""
        self.context[key] = value
    
    def get_context(self, key: str) -> Any:
        """Get context information."""
        return self.context.get(key)

class BaseAgent:
    """Base class for all AI agents in the system."""
    
    def __init__(self, name: str, role: AgentRole, llm: ChatOpenAI):
        self.name = name
        self.role = role
        self.llm = llm
        self.memory = AgentMemory()
        self.active = True
        self.current_task: Optional[Task] = None
        self.completed_tasks: List[Task] = []
        
    async def process_task(self, task: Task) -> Task:
        """Process a task and return the result."""
        self.current_task = task
        task.status = TaskStatus.IN_PROGRESS
        task.assigned_to = self.name
        
        try:
            # Execute task based on agent role
            result = await self._execute_task(task)
            
            task.result = result
            task.status = TaskStatus.COMPLETED
            task.completed_at = datetime.now()
            task.confidence = self._calculate_confidence(result)
            
            self.completed_tasks.append(task)
            
        except Exception as e:
            logger.error(f"Agent {self.name} failed to process task {task.id}: {str(e)}")
            task.status = TaskStatus.FAILED
            task.result = f"Error: {str(e)}"
            task.confidence = 0.0
        
        finally:
            self.current_task = None
            
        return task
    
    async def _execute_task(self, task: Task) -> Any:
        """Execute the task - to be implemented by subclasses."""
        raise NotImplementedError("Subclasses must implement _execute_task")
    
    def _calculate_confidence(self, result: Any) -> float:
        """Calculate confidence score for the result."""
        # Basic confidence calculation - can be overridden by subclasses
        if result and isinstance(result, str) and len(result) > 50:
            return min(0.9, 0.5 + len(result) / 1000)
        return 0.5
    
    async def collaborate(self, other_agent: 'BaseAgent', message: AgentMessage):
        """Handle collaboration with another agent."""
        self.memory.add_message(message)
        
        # Process collaboration request
        response = await self._process_collaboration(message)
        
        # Send response back
        response_message = AgentMessage(
            sender=self.name,
            recipient=other_agent.name,
            content=response,
            message_type="response"
        )
        
        other_agent.memory.add_message(response_message)
        
        return response_message
    
    async def _process_collaboration(self, message: AgentMessage) -> str:
        """Process collaboration message - to be implemented by subclasses."""
        return f"Acknowledged message from {message.sender}"

class ResearcherAgent(BaseAgent):
    """Agent specialized in researching and gathering information."""
    
    def __init__(self, name: str, llm: ChatOpenAI):
        super().__init__(name, AgentRole.RESEARCHER, llm)
        self.research_sources: List[str] = []
        
    async def _execute_task(self, task: Task) -> Any:
        """Execute research task."""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(content="""You are a Research Agent specializing in gathering comprehensive information.
            Your role is to:
            1. Break down complex topics into research questions
            2. Identify key information sources and data points
            3. Provide detailed, factual information with citations where possible
            4. Flag areas requiring further investigation"""),
            HumanMessage(content=f"Research the following: {task.description}")
        ])
        
        response = await self.llm.ainvoke(prompt.format_messages())
        
        # Extract research findings
        research_result = {
            "findings": response.content,
            "sources": self._extract_sources(response.content),
            "key_points": self._extract_key_points(response.content),
            "areas_for_investigation": self._identify_gaps(response.content)
        }
        
        return research_result
    
    def _extract_sources(self, content: str) -> List[str]:
        """Extract potential sources from research content."""
        # Simplified source extraction
        sources = []
        lines = content.split('\n')
        for line in lines:
            if any(keyword in line.lower() for keyword in ['source:', 'reference:', 'based on:', 'according to']):
                sources.append(line.strip())
        return sources[:5]  # Limit to top 5 sources
    
    def _extract_key_points(self, content: str) -> List[str]:
        """Extract key points from research."""
        key_points = []
        lines = content.split('\n')
        for line in lines:
            if line.strip() and (line.strip()[0].isdigit() or line.strip().startswith('-')):
                key_points.append(line.strip())
        return key_points[:10]  # Limit to top 10 points
    
    def _identify_gaps(self, content: str) -> List[str]:
        """Identify areas needing more research."""
        gaps = []
        keywords = ['unclear', 'requires further', 'need more', 'investigate', 'unknown']
        lines = content.split('\n')
        for line in lines:
            if any(keyword in line.lower() for keyword in keywords):
                gaps.append(line.strip())
        return gaps[:5]

class AnalystAgent(BaseAgent):
    """Agent specialized in analyzing data and identifying patterns."""
    
    def __init__(self, name: str, llm: ChatOpenAI):
        super().__init__(name, AgentRole.ANALYST, llm)
        self.analysis_methods: List[str] = ["statistical", "comparative", "trend", "causal"]
        
    async def _execute_task(self, task: Task) -> Any:
        """Execute analysis task."""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(content="""You are an Analyst Agent specializing in data analysis and pattern recognition.
            Your role is to:
            1. Analyze information systematically and objectively
            2. Identify patterns, trends, and correlations
            3. Provide quantitative insights where possible
            4. Draw logical conclusions based on evidence"""),
            HumanMessage(content=f"Analyze the following: {task.description}")
        ])
        
        response = await self.llm.ainvoke(prompt.format_messages())
        
        # Structure analysis results
        analysis_result = {
            "analysis": response.content,
            "patterns": self._identify_patterns(response.content),
            "insights": self._extract_insights(response.content),
            "recommendations": self._generate_recommendations(response.content),
            "confidence_metrics": self._calculate_analysis_confidence(response.content)
        }
        
        return analysis_result
    
    def _identify_patterns(self, content: str) -> List[Dict[str, str]]:
        """Identify patterns in the analysis."""
        patterns = []
        pattern_keywords = ['pattern', 'trend', 'correlation', 'relationship', 'consistent']
        
        lines = content.split('\n')
        for line in lines:
            if any(keyword in line.lower() for keyword in pattern_keywords):
                patterns.append({
                    "description": line.strip(),
                    "type": self._classify_pattern(line)
                })
        
        return patterns[:5]
    
    def _classify_pattern(self, description: str) -> str:
        """Classify the type of pattern."""
        description_lower = description.lower()
        if 'trend' in description_lower:
            return 'trend'
        elif 'correlation' in description_lower:
            return 'correlation'
        elif 'cycle' in description_lower or 'periodic' in description_lower:
            return 'cyclical'
        else:
            return 'general'
    
    def _extract_insights(self, content: str) -> List[str]:
        """Extract key insights from analysis."""
        insights = []
        insight_keywords = ['shows', 'indicates', 'suggests', 'reveals', 'demonstrates']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in insight_keywords):
                insights.append(sentence.strip() + '.')
        
        return insights[:7]
    
    def _generate_recommendations(self, content: str) -> List[str]:
        """Generate recommendations based on analysis."""
        recommendations = []
        rec_keywords = ['recommend', 'suggest', 'should', 'consider', 'advise']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in rec_keywords):
                recommendations.append(sentence.strip() + '.')
        
        return recommendations[:5]
    
    def _calculate_analysis_confidence(self, content: str) -> Dict[str, float]:
        """Calculate confidence metrics for the analysis."""
        word_count = len(content.split())
        evidence_count = content.lower().count('evidence') + content.lower().count('data') + content.lower().count('shows')
        uncertainty_count = content.lower().count('may') + content.lower().count('might') + content.lower().count('possibly')
        
        confidence = min(0.95, 0.5 + (evidence_count * 0.1) - (uncertainty_count * 0.05) + (word_count / 1000))
        
        return {
            "overall_confidence": confidence,
            "evidence_strength": min(1.0, evidence_count / 10),
            "certainty_level": max(0.0, 1.0 - (uncertainty_count / 10))
        }

class CriticAgent(BaseAgent):
    """Agent specialized in critical evaluation and quality assurance."""
    
    def __init__(self, name: str, llm: ChatOpenAI):
        super().__init__(name, AgentRole.CRITIC, llm)
        self.evaluation_criteria = [
            "accuracy", "completeness", "logic", "evidence", 
            "clarity", "relevance", "consistency"
        ]
        
    async def _execute_task(self, task: Task) -> Any:
        """Execute critical evaluation task."""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(content="""You are a Critic Agent specializing in rigorous evaluation and quality assurance.
            Your role is to:
            1. Critically evaluate arguments and conclusions
            2. Identify weaknesses, gaps, and potential biases
            3. Verify logical consistency and evidence quality
            4. Suggest improvements and alternative perspectives
            5. Ensure high standards of analysis"""),
            HumanMessage(content=f"Critically evaluate the following: {task.description}")
        ])
        
        response = await self.llm.ainvoke(prompt.format_messages())
        
        # Structure critique results
        critique_result = {
            "evaluation": response.content,
            "strengths": self._identify_strengths(response.content),
            "weaknesses": self._identify_weaknesses(response.content),
            "gaps": self._identify_gaps(response.content),
            "improvements": self._suggest_improvements(response.content),
            "quality_score": self._calculate_quality_score(response.content)
        }
        
        return critique_result
    
    def _identify_strengths(self, content: str) -> List[str]:
        """Identify strengths in the evaluated content."""
        strengths = []
        strength_keywords = ['strong', 'excellent', 'well', 'good', 'effective', 'solid']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in strength_keywords):
                strengths.append(sentence.strip() + '.')
        
        return strengths[:5]
    
    def _identify_weaknesses(self, content: str) -> List[str]:
        """Identify weaknesses in the evaluated content."""
        weaknesses = []
        weakness_keywords = ['weak', 'lack', 'insufficient', 'poor', 'inadequate', 'missing']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in weakness_keywords):
                weaknesses.append(sentence.strip() + '.')
        
        return weaknesses[:5]
    
    def _identify_gaps(self, content: str) -> List[str]:
        """Identify gaps in the analysis."""
        gaps = []
        gap_keywords = ['gap', 'missing', 'overlook', 'fail to', 'does not address', 'ignores']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in gap_keywords):
                gaps.append(sentence.strip() + '.')
        
        return gaps[:5]
    
    def _suggest_improvements(self, content: str) -> List[str]:
        """Suggest improvements based on critique."""
        improvements = []
        improvement_keywords = ['could', 'should', 'improve', 'enhance', 'strengthen', 'add']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in improvement_keywords):
                improvements.append(sentence.strip() + '.')
        
        return improvements[:5]
    
    def _calculate_quality_score(self, content: str) -> Dict[str, float]:
        """Calculate quality scores for different criteria."""
        scores = {}
        
        for criterion in self.evaluation_criteria:
            # Simplified scoring based on keyword presence
            positive_count = content.lower().count(criterion) + content.lower().count('good') + content.lower().count('strong')
            negative_count = content.lower().count('poor') + content.lower().count('weak') + content.lower().count('lacking')
            
            score = min(1.0, max(0.0, 0.5 + (positive_count * 0.1) - (negative_count * 0.15)))
            scores[criterion] = score
        
        scores['overall'] = sum(scores.values()) / len(scores)
        
        return scores

class SynthesizerAgent(BaseAgent):
    """Agent specialized in synthesizing information and creating coherent narratives."""
    
    def __init__(self, name: str, llm: ChatOpenAI):
        super().__init__(name, AgentRole.SYNTHESIZER, llm)
        self.synthesis_strategies = ["integrate", "summarize", "reconcile", "consolidate"]
        
    async def _execute_task(self, task: Task) -> Any:
        """Execute synthesis task."""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(content="""You are a Synthesizer Agent specializing in integrating diverse information.
            Your role is to:
            1. Combine multiple perspectives into coherent narratives
            2. Resolve contradictions and find common ground
            3. Create comprehensive summaries that capture key insights
            4. Generate actionable conclusions and recommendations
            5. Ensure clarity and accessibility of complex information"""),
            HumanMessage(content=f"Synthesize the following information: {task.description}")
        ])
        
        response = await self.llm.ainvoke(prompt.format_messages())
        
        # Structure synthesis results
        synthesis_result = {
            "synthesis": response.content,
            "key_themes": self._extract_themes(response.content),
            "consensus_points": self._identify_consensus(response.content),
            "contradictions": self._identify_contradictions(response.content),
            "final_recommendations": self._generate_final_recommendations(response.content),
            "executive_summary": self._create_executive_summary(response.content)
        }
        
        return synthesis_result
    
    def _extract_themes(self, content: str) -> List[Dict[str, str]]:
        """Extract major themes from synthesis."""
        themes = []
        theme_keywords = ['theme', 'pattern', 'trend', 'common', 'recurring', 'central']
        
        paragraphs = content.split('\n\n')
        for i, paragraph in enumerate(paragraphs[:5]):  # Limit to first 5 paragraphs
            if any(keyword in paragraph.lower() for keyword in theme_keywords):
                themes.append({
                    "theme": f"Theme {i+1}",
                    "description": paragraph.strip()[:200] + "..." if len(paragraph) > 200 else paragraph.strip()
                })
        
        return themes
    
    def _identify_consensus(self, content: str) -> List[str]:
        """Identify points of consensus."""
        consensus = []
        consensus_keywords = ['agree', 'consensus', 'common', 'shared', 'unanimous', 'consistent']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in consensus_keywords):
                consensus.append(sentence.strip() + '.')
        
        return consensus[:5]
    
    def _identify_contradictions(self, content: str) -> List[str]:
        """Identify contradictions or conflicts."""
        contradictions = []
        conflict_keywords = ['however', 'contrary', 'conflict', 'disagree', 'opposing', 'contradicts']
        
        sentences = content.split('.')
        for sentence in sentences:
            if any(keyword in sentence.lower() for keyword in conflict_keywords):
                contradictions.append(sentence.strip() + '.')
        
        return contradictions[:3]
    
    def _generate_final_recommendations(self, content: str) -> List[str]:
        """Generate final synthesized recommendations."""
        recommendations = []
        
        # Look for recommendation sections
        lines = content.split('\n')
        in_recommendations = False
        
        for line in lines:
            if 'recommend' in line.lower() or 'conclusion' in line.lower():
                in_recommendations = True
            elif in_recommendations and line.strip():
                recommendations.append(line.strip())
                if len(recommendations) >= 5:
                    break
        
        return recommendations
    
    def _create_executive_summary(self, content: str) -> str:
        """Create an executive summary of the synthesis."""
        # Take first paragraph or create summary
        paragraphs = content.split('\n\n')
        if paragraphs:
            summary = paragraphs[0][:300]
            if len(paragraphs[0]) > 300:
                summary += "..."
            return summary
        return "Summary generation in progress..."

class CoordinatorAgent(BaseAgent):
    """Agent responsible for coordinating other agents and managing workflow."""
    
    def __init__(self, name: str, llm: ChatOpenAI):
        super().__init__(name, AgentRole.COORDINATOR, llm)
        self.agents: Dict[str, BaseAgent] = {}
        self.task_queue: List[Task] = []
        self.completed_tasks: List[Task] = []
        self.workflow_graph = nx.DiGraph()
        self.execution_history: List[Dict[str, Any]] = []
        
    def register_agent(self, agent: BaseAgent):
        """Register an agent with the coordinator."""
        self.agents[agent.name] = agent
        self.workflow_graph.add_node(agent.name, role=agent.role.value)
        
    async def decompose_problem(self, problem: str) -> List[Task]:
        """Decompose a complex problem into subtasks."""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(content="""You are a Coordinator Agent responsible for breaking down complex problems.
            Decompose the problem into specific subtasks that can be assigned to specialized agents:
            - Researcher: For gathering information and facts
            - Analyst: For analyzing data and identifying patterns
            - Critic: For evaluating quality and identifying issues
            - Synthesizer: For combining insights and creating summaries
            
            Create clear, actionable subtasks with dependencies."""),
            HumanMessage(content=f"Decompose this problem into subtasks: {problem}")
        ])
        
        response = await self.llm.ainvoke(prompt.format_messages())
        
        # Parse response into tasks
        tasks = self._parse_tasks(response.content, problem)
        
        return tasks
    
    def _parse_tasks(self, content: str, original_problem: str) -> List[Task]:
        """Parse LLM response into Task objects."""
        tasks = []
        lines = content.split('\n')
        
        task_id = 1
        current_role = None
        
        for line in lines:
            line = line.strip()
            if not line:
                continue
                
            # Check if line indicates a role
            for role in AgentRole:
                if role.value in line:
                    current_role = role.value
                    break
            
            # If line starts with number or dash, it's likely a task
            if (line[0].isdigit() or line.startswith('-')) and current_role:
                # Extract task description
                task_desc = line.lstrip('0123456789.-').strip()
                
                task = Task(
                    id=f"task_{task_id}",
                    description=task_desc,
                    metadata={
                        "original_problem": original_problem,
                        "suggested_role": current_role
                    }
                )
                
                tasks.append(task)
                task_id += 1
        
        # If no tasks were parsed, create default tasks
        if not tasks:
            tasks = [
                Task(id="task_1", description=f"Research background information on: {original_problem}",
                     metadata={"suggested_role": "Researcher"}),
                Task(id="task_2", description=f"Analyze key aspects of: {original_problem}",
                     metadata={"suggested_role": "Analyst"}),
                Task(id="task_3", description="Critically evaluate the research and analysis",
                     metadata={"suggested_role": "Critic"}),
                Task(id="task_4", description="Synthesize all findings into actionable insights",
                     metadata={"suggested_role": "Synthesizer"})
            ]
        
        return tasks
    
    async def execute_workflow(self, tasks: List[Task], parallel: bool = True) -> Dict[str, Any]:
        """Execute the workflow with given tasks."""
        start_time = datetime.now()
        
        # Build task dependency graph
        self._build_dependency_graph(tasks)
        
        # Execute tasks
        if parallel:
            results = await self._execute_parallel(tasks)
        else:
            results = await self._execute_sequential(tasks)
        
        # Compile final results
        end_time = datetime.now()
        execution_time = (end_time - start_time).total_seconds()
        
        workflow_result = {
            "tasks": tasks,
            "execution_time": execution_time,
            "success_rate": self._calculate_success_rate(tasks),
            "agent_contributions": self._compile_agent_contributions(tasks),
            "workflow_graph": self.workflow_graph,
            "timestamp": datetime.now()
        }
        
        self.execution_history.append(workflow_result)
        
        return workflow_result
    
    def _build_dependency_graph(self, tasks: List[Task]):
        """Build a dependency graph for tasks."""
        # For simplicity, create a linear dependency chain based on suggested roles
        role_order = ["Researcher", "Analyst", "Critic", "Synthesizer"]
        
        # Group tasks by role
        tasks_by_role = {}
        for task in tasks:
            role = task.metadata.get("suggested_role", "Researcher")
            if role not in tasks_by_role:
                tasks_by_role[role] = []
            tasks_by_role[role].append(task)
        
        # Create dependencies
        for i in range(len(role_order) - 1):
            current_role = role_order[i]
            next_role = role_order[i + 1]
            
            if current_role in tasks_by_role and next_role in tasks_by_role:
                for current_task in tasks_by_role[current_role]:
                    for next_task in tasks_by_role[next_role]:
                        next_task.dependencies.append(current_task.id)
    
    async def _execute_parallel(self, tasks: List[Task]) -> List[Task]:
        """Execute tasks in parallel where possible."""
        completed = set()
        pending = tasks.copy()
        
        while pending:
            # Find tasks that can be executed (no pending dependencies)
            ready_tasks = [
                task for task in pending
                if all(dep in completed for dep in task.dependencies)
            ]
            
            if not ready_tasks:
                # Deadlock prevention - execute first pending task
                ready_tasks = [pending[0]]
            
            # Execute ready tasks in parallel
            task_futures = []
            for task in ready_tasks:
                agent_name = self._select_agent_for_task(task)
                if agent_name and agent_name in self.agents:
                    agent = self.agents[agent_name]
                    task_futures.append(agent.process_task(task))
                    
                    # Update workflow graph
                    self.workflow_graph.add_edge(
                        self.name, agent_name,
                        task_id=task.id,
                        timestamp=datetime.now()
                    )
            
            # Wait for tasks to complete
            if task_futures:
                completed_tasks = await asyncio.gather(*task_futures)
                
                for task in completed_tasks:
                    completed.add(task.id)
                    pending.remove(task)
                    self.completed_tasks.append(task)
        
        return tasks
    
    async def _execute_sequential(self, tasks: List[Task]) -> List[Task]:
        """Execute tasks sequentially."""
        for task in tasks:
            agent_name = self._select_agent_for_task(task)
            if agent_name and agent_name in self.agents:
                agent = self.agents[agent_name]
                await agent.process_task(task)
                
                # Update workflow graph
                self.workflow_graph.add_edge(
                    self.name, agent_name,
                    task_id=task.id,
                    timestamp=datetime.now()
                )
                
                self.completed_tasks.append(task)
        
        return tasks
    
    def _select_agent_for_task(self, task: Task) -> Optional[str]:
        """Select the best agent for a given task."""
        suggested_role = task.metadata.get("suggested_role")
        
        # Find agent with matching role
        for agent_name, agent in self.agents.items():
            if agent.role.value == suggested_role:
                return agent_name
        
        # Fallback to first available agent
        return list(self.agents.keys())[0] if self.agents else None
    
    def _calculate_success_rate(self, tasks: List[Task]) -> float:
        """Calculate the success rate of task execution."""
        if not tasks:
            return 0.0
        
        successful = sum(1 for task in tasks if task.status == TaskStatus.COMPLETED)
        return successful / len(tasks)
    
    def _compile_agent_contributions(self, tasks: List[Task]) -> Dict[str, Any]:
        """Compile contributions from each agent."""
        contributions = {}
        
        for agent_name, agent in self.agents.items():
            agent_tasks = [task for task in tasks if task.assigned_to == agent_name]
            
            contributions[agent_name] = {
                "role": agent.role.value,
                "tasks_completed": len(agent_tasks),
                "average_confidence": np.mean([task.confidence for task in agent_tasks]) if agent_tasks else 0.0,
                "total_execution_time": sum(
                    (task.completed_at - task.created_at).total_seconds()
                    for task in agent_tasks
                    if task.completed_at
                )
            }
        
        return contributions

class WorkflowVisualizer:
    """Handles visualization of agent interactions and workflow."""
    
    def __init__(self):
        self.color_map = Config.NODE_COLORS
        self.layout_cache = {}
        
    def create_workflow_graph(self, workflow_graph: nx.DiGraph, 
                            active_agents: List[str] = None) -> go.Figure:
        """Create an interactive workflow visualization."""
        
        # Use hierarchical layout
        pos = self._hierarchical_layout(workflow_graph)
        
        # Create edge trace
        edge_trace = self._create_edge_trace(workflow_graph, pos)
        
        # Create node trace
        node_trace = self._create_node_trace(workflow_graph, pos, active_agents)
        
        # Create figure
        fig = go.Figure(data=[edge_trace, node_trace],
                       layout=go.Layout(
                           title='Agent Collaboration Network',
                           titlefont_size=16,
                           showlegend=False,
                           hovermode='closest',
                           margin=dict(b=20, l=5, r=5, t=40),
                           xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                           yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                           plot_bgcolor='white'
                       ))
        
        return fig
    
    def _hierarchical_layout(self, G: nx.DiGraph) -> Dict:
        """Create hierarchical layout for the graph."""
        if len(G) == 0:
            return {}
            
        # Try to use cached layout if graph structure hasn't changed
        graph_hash = hash(tuple(G.edges()))
        if graph_hash in self.layout_cache:
            return self.layout_cache[graph_hash]
        
        # Define role hierarchy
        hierarchy = {
            'Coordinator': 0,
            'Researcher': 1,
            'Analyst': 2,
            'Critic': 3,
            'Synthesizer': 4
        }
        
        # Position nodes based on hierarchy
        pos = {}
        role_counts = {}
        
        for node in G.nodes():
            role = G.nodes[node].get('role', 'Unknown')
            level = hierarchy.get(role, 5)
            
            if level not in role_counts:
                role_counts[level] = 0
            
            x = role_counts[level] * 2 - (len([n for n in G.nodes() if hierarchy.get(G.nodes[n].get('role'), 5) == level]) - 1)
            y = -level * 2
            
            pos[node] = (x, y)
            role_counts[level] += 1
        
        # Cache the layout
        self.layout_cache[graph_hash] = pos
        
        return pos
    
    def _create_edge_trace(self, G: nx.DiGraph, pos: Dict) -> go.Scatter:
        """Create edge trace for the graph."""
        edge_x = []
        edge_y = []
        
        for edge in G.edges():
            x0, y0 = pos.get(edge[0], (0, 0))
            x1, y1 = pos.get(edge[1], (0, 0))
            edge_x.extend([x0, x1, None])
            edge_y.extend([y0, y1, None])
        
        edge_trace = go.Scatter(
            x=edge_x, y=edge_y,
            line=dict(width=2, color='#888'),
            hoverinfo='none',
            mode='lines'
        )
        
        return edge_trace
    
    def _create_node_trace(self, G: nx.DiGraph, pos: Dict, 
                          active_agents: List[str] = None) -> go.Scatter:
        """Create node trace for the graph."""
        node_x = []
        node_y = []
        node_colors = []
        node_sizes = []
        node_text = []
        
        for node in G.nodes():
            x, y = pos.get(node, (0, 0))
            node_x.append(x)
            node_y.append(y)
            
            # Get node attributes
            role = G.nodes[node].get('role', 'Unknown')
            color = self.color_map.get(role, '#666')
            
            # Highlight active agents
            if active_agents and node in active_agents:
                size = 30
                color = self._brighten_color(color)
            else:
                size = 20
            
            node_colors.append(color)
            node_sizes.append(size)
            
            # Create hover text
            degree = G.degree(node)
            hover_text = f"{node}<br>Role: {role}<br>Connections: {degree}"
            node_text.append(hover_text)
        
        node_trace = go.Scatter(
            x=node_x, y=node_y,
            mode='markers+text',
            hoverinfo='text',
            text=[node for node in G.nodes()],
            textposition="top center",
            hovertext=node_text,
            marker=dict(
                showscale=False,
                color=node_colors,
                size=node_sizes,
                line=dict(color='white', width=2)
            )
        )
        
        return node_trace
    
    def _brighten_color(self, color: str) -> str:
        """Make a color brighter for highlighting."""
        # Simple brightening by mixing with white
        if color.startswith('#'):
            r = int(color[1:3], 16)
            g = int(color[3:5], 16)
            b = int(color[5:7], 16)
            
            # Mix with white (255, 255, 255)
            r = int(r + (255 - r) * 0.3)
            g = int(g + (255 - g) * 0.3)
            b = int(b + (255 - b) * 0.3)
            
            return f"#{r:02x}{g:02x}{b:02x}"
        
        return color
    
    def create_task_timeline(self, tasks: List[Task]) -> go.Figure:
        """Create a timeline visualization of task execution."""
        
        # Prepare data for timeline
        timeline_data = []
        
        for task in tasks:
            if task.created_at and task.completed_at:
                timeline_data.append({
                    'Task': task.id,
                    'Agent': task.assigned_to or 'Unassigned',
                    'Start': task.created_at,
                    'Finish': task.completed_at,
                    'Status': task.status.value,
                    'Confidence': task.confidence
                })
        
        if not timeline_data:
            # Return empty figure if no completed tasks
            fig = go.Figure()
            fig.add_annotation(
                text="No completed tasks to display",
                xref="paper", yref="paper",
                x=0.5, y=0.5,
                showarrow=False
            )
            return fig
        
        df = pd.DataFrame(timeline_data)
        
        # Create Gantt chart
        fig = px.timeline(
            df, 
            x_start="Start", 
            x_end="Finish", 
            y="Agent",
            color="Confidence",
            hover_data=["Task", "Status", "Confidence"],
            title="Task Execution Timeline",
            color_continuous_scale="Viridis"
        )
        
        fig.update_yaxis(categoryorder="total ascending")
        fig.update_layout(height=400)
        
        return fig
    
    def create_confidence_heatmap(self, agent_contributions: Dict[str, Any]) -> go.Figure:
        """Create a heatmap showing agent confidence levels."""
        
        agents = list(agent_contributions.keys())
        metrics = ['tasks_completed', 'average_confidence', 'total_execution_time']
        
        # Normalize data for heatmap
        data = []
        for metric in metrics:
            row = []
            for agent in agents:
                value = agent_contributions[agent].get(metric, 0)
                if metric == 'total_execution_time':
                    value = value / 60  # Convert to minutes
                row.append(value)
            data.append(row)
        
        # Create heatmap
        fig = go.Figure(data=go.Heatmap(
            z=data,
            x=agents,
            y=['Tasks Completed', 'Avg Confidence', 'Time (min)'],
            colorscale='Blues',
            text=np.round(data, 2),
            texttemplate='%{text}',
            textfont={"size": 10}
        ))
        
        fig.update_layout(
            title="Agent Performance Metrics",
            xaxis_title="Agents",
            yaxis_title="Metrics",
            height=300
        )
        
        return fig

class ReportGenerator:
    """Generates comprehensive reports from multi-agent collaboration."""
    
    def __init__(self):
        self.section_generators = {
            'executive_summary': self._generate_executive_summary,
            'task_analysis': self._generate_task_analysis,
            'agent_contributions': self._generate_agent_contributions,
            'key_findings': self._generate_key_findings,
            'recommendations': self._generate_recommendations,
            'confidence_analysis': self._generate_confidence_analysis
        }
        
    def generate_report(self, 
                       workflow_result: Dict[str, Any],
                       problem_statement: str,
                       include_sections: List[str] = None) -> str:
        """Generate a comprehensive report from workflow results."""
        
        if include_sections is None:
            include_sections = list(self.section_generators.keys())
        
        report_sections = []
        
        # Header
        report_sections.append(self._generate_header(problem_statement))
        
        # Generate requested sections
        for section in include_sections:
            if section in self.section_generators:
                section_content = self.section_generators[section](workflow_result, problem_statement)
                report_sections.append(section_content)
        
        # Footer
        report_sections.append(self._generate_footer(workflow_result))
        
        return "\n\n".join(report_sections)
    
    def _generate_header(self, problem_statement: str) -> str:
        """Generate report header."""
        return f"""# Multi-Agent Analysis Report

**Generated:** {datetime.now().strftime('%B %d, %Y at %I:%M %p')}

**Problem Statement:** {problem_statement}

---"""
    
    def _generate_executive_summary(self, workflow_result: Dict[str, Any], 
                                  problem_statement: str) -> str:
        """Generate executive summary section."""
        tasks = workflow_result.get('tasks', [])
        success_rate = workflow_result.get('success_rate', 0)
        execution_time = workflow_result.get('execution_time', 0)
        
        # Find synthesis results
        synthesis_task = None
        for task in tasks:
            if task.assigned_to and 'Synthesizer' in task.assigned_to:
                synthesis_task = task
                break
        
        summary = f"""## Executive Summary

The multi-agent system successfully analyzed the problem with a **{success_rate:.0%} task completion rate** in **{execution_time:.1f} seconds**.

"""
        
        if synthesis_task and synthesis_task.result:
            if isinstance(synthesis_task.result, dict) and 'executive_summary' in synthesis_task.result:
                summary += synthesis_task.result['executive_summary']
            else:
                summary += "The analysis revealed key insights across multiple dimensions of the problem."
        
        return summary
    
    def _generate_task_analysis(self, workflow_result: Dict[str, Any], 
                               problem_statement: str) -> str:
        """Generate task analysis section."""
        tasks = workflow_result.get('tasks', [])
        
        content = "## Task Analysis\n\n"
        
        # Group tasks by status
        completed_tasks = [t for t in tasks if t.status == TaskStatus.COMPLETED]
        failed_tasks = [t for t in tasks if t.status == TaskStatus.FAILED]
        
        content += f"**Total Tasks:** {len(tasks)}\n"
        content += f"**Completed:** {len(completed_tasks)}\n"
        content += f"**Failed:** {len(failed_tasks)}\n\n"
        
        # List tasks by agent
        content += "### Tasks by Agent\n\n"
        
        agents_tasks = {}
        for task in tasks:
            agent = task.assigned_to or "Unassigned"
            if agent not in agents_tasks:
                agents_tasks[agent] = []
            agents_tasks[agent].append(task)
        
        for agent, agent_tasks in agents_tasks.items():
            content += f"**{agent}:**\n"
            for task in agent_tasks:
                status_emoji = "✓" if task.status == TaskStatus.COMPLETED else "✗"
                confidence = f"({task.confidence:.0%} confidence)" if task.confidence > 0 else ""
                content += f"- {status_emoji} {task.description} {confidence}\n"
            content += "\n"
        
        return content
    
    def _generate_agent_contributions(self, workflow_result: Dict[str, Any], 
                                    problem_statement: str) -> str:
        """Generate agent contributions section."""
        contributions = workflow_result.get('agent_contributions', {})
        
        content = "## Agent Contributions\n\n"
        
        for agent, stats in contributions.items():
            role = stats.get('role', 'Unknown')
            tasks_completed = stats.get('tasks_completed', 0)
            avg_confidence = stats.get('average_confidence', 0)
            exec_time = stats.get('total_execution_time', 0)
            
            content += f"### {agent} ({role})\n"
            content += f"- Tasks Completed: {tasks_completed}\n"
            content += f"- Average Confidence: {avg_confidence:.0%}\n"
            content += f"- Total Execution Time: {exec_time:.1f}s\n\n"
        
        return content
    
    def _generate_key_findings(self, workflow_result: Dict[str, Any], 
                             problem_statement: str) -> str:
        """Generate key findings section."""
        tasks = workflow_result.get('tasks', [])
        
        content = "## Key Findings\n\n"
        
        # Extract findings from different agent types
        for task in tasks:
            if task.status == TaskStatus.COMPLETED and task.result:
                agent_role = task.metadata.get('suggested_role', '')
                
                if 'Researcher' in agent_role and isinstance(task.result, dict):
                    if 'key_points' in task.result:
                        content += "### Research Findings\n"
                        for point in task.result['key_points'][:5]:
                            content += f"- {point}\n"
                        content += "\n"
                
                elif 'Analyst' in agent_role and isinstance(task.result, dict):
                    if 'insights' in task.result:
                        content += "### Analytical Insights\n"
                        for insight in task.result['insights'][:5]:
                            content += f"- {insight}\n"
                        content += "\n"
                
                elif 'Critic' in agent_role and isinstance(task.result, dict):
                    if 'strengths' in task.result:
                        content += "### Identified Strengths\n"
                        for strength in task.result['strengths'][:3]:
                            content += f"- {strength}\n"
                        content += "\n"
                    
                    if 'gaps' in task.result:
                        content += "### Identified Gaps\n"
                        for gap in task.result['gaps'][:3]:
                            content += f"- {gap}\n"
                        content += "\n"
        
        return content
    
    def _generate_recommendations(self, workflow_result: Dict[str, Any], 
                                problem_statement: str) -> str:
        """Generate recommendations section."""
        tasks = workflow_result.get('tasks', [])
        
        content = "## Recommendations\n\n"
        
        all_recommendations = []
        
        # Collect recommendations from all agents
        for task in tasks:
            if task.status == TaskStatus.COMPLETED and task.result:
                if isinstance(task.result, dict):
                    if 'recommendations' in task.result:
                        all_recommendations.extend(task.result['recommendations'])
                    elif 'final_recommendations' in task.result:
                        all_recommendations.extend(task.result['final_recommendations'])
                    elif 'improvements' in task.result:
                        all_recommendations.extend(task.result['improvements'])
        
        # Remove duplicates and limit
        unique_recommendations = []
        seen = set()
        for rec in all_recommendations:
            rec_lower = rec.lower()
            if rec_lower not in seen:
                seen.add(rec_lower)
                unique_recommendations.append(rec)
        
        # Categorize recommendations
        high_priority = []
        medium_priority = []
        low_priority = []
        
        for rec in unique_recommendations[:10]:
            # Simple prioritization based on keywords
            if any(word in rec.lower() for word in ['critical', 'must', 'essential', 'immediately']):
                high_priority.append(rec)
            elif any(word in rec.lower() for word in ['should', 'recommend', 'important']):
                medium_priority.append(rec)
            else:
                low_priority.append(rec)
        
        if high_priority:
            content += "### High Priority\n"
            for rec in high_priority:
                content += f"- {rec}\n"
            content += "\n"
        
        if medium_priority:
            content += "### Medium Priority\n"
            for rec in medium_priority:
                content += f"- {rec}\n"
            content += "\n"
        
        if low_priority:
            content += "### Low Priority\n"
            for rec in low_priority:
                content += f"- {rec}\n"
            content += "\n"
        
        return content
    
    def _generate_confidence_analysis(self, workflow_result: Dict[str, Any], 
                                    problem_statement: str) -> str:
        """Generate confidence analysis section."""
        tasks = workflow_result.get('tasks', [])
        contributions = workflow_result.get('agent_contributions', {})
        
        content = "## Confidence Analysis\n\n"
        
        # Overall confidence
        overall_confidence = np.mean([t.confidence for t in tasks if t.confidence > 0])
        content += f"**Overall Confidence Score:** {overall_confidence:.0%}\n\n"
        
        # Confidence by agent type
        content += "### Confidence by Agent Role\n"
        
        for agent, stats in contributions.items():
            avg_conf = stats.get('average_confidence', 0)
            role = stats.get('role', 'Unknown')
            content += f"- **{role}**: {avg_conf:.0%}\n"
        
        content += "\n### Confidence Distribution\n"
        
        # Categorize confidence levels
        high_conf = [t for t in tasks if t.confidence >= 0.8]
        medium_conf = [t for t in tasks if 0.5 <= t.confidence < 0.8]
        low_conf = [t for t in tasks if t.confidence < 0.5]
        
        content += f"- High Confidence (≥80%): {len(high_conf)} tasks\n"
        content += f"- Medium Confidence (50-79%): {len(medium_conf)} tasks\n"
        content += f"- Low Confidence (<50%): {len(low_conf)} tasks\n"
        
        return content
    
    def _generate_footer(self, workflow_result: Dict[str, Any]) -> str:
        """Generate report footer."""
        execution_time = workflow_result.get('execution_time', 0)
        timestamp = workflow_result.get('timestamp', datetime.now())
        
        return f"""---

**Report Generation Details:**
- Analysis completed in {execution_time:.1f} seconds
- Report generated at {timestamp.strftime('%B %d, %Y at %I:%M %p')}
- Powered by Multi-Agent AI Collaboration System"""

# Gradio Interface Functions
def create_gradio_interface():
    """Create the main Gradio interface for the multi-agent system."""
    
    # Initialize components
    coordinator = None
    visualizer = WorkflowVisualizer()
    report_generator = ReportGenerator()
    
    # State variables
    current_workflow = None
    current_problem = ""
    
    def initialize_agents(api_key: str, model: str = "gpt-4") -> str:
        """Initialize the multi-agent system."""
        nonlocal coordinator
        
        if not api_key:
            return "Please provide an OpenAI API key to initialize the agents."
        
        try:
            # Initialize LLM
            llm = ChatOpenAI(
                api_key=api_key,
                model=model,
                temperature=Config.TEMPERATURE,
                max_tokens=Config.MAX_TOKENS
            )
            
            # Create coordinator
            coordinator = CoordinatorAgent("Coordinator", llm)
            
            # Create specialized agents
            researcher = ResearcherAgent("Researcher-1", llm)
            analyst = AnalystAgent("Analyst-1", llm)
            critic = CriticAgent("Critic-1", llm)
            synthesizer = SynthesizerAgent("Synthesizer-1", llm)
            
            # Register agents with coordinator
            coordinator.register_agent(researcher)
            coordinator.register_agent(analyst)
            coordinator.register_agent(critic)
            coordinator.register_agent(synthesizer)
            
            return f"Successfully initialized multi-agent system with {len(coordinator.agents)} agents."
            
        except Exception as e:
            logger.error(f"Error initializing agents: {str(e)}")
            return f"Error initializing agents: {str(e)}"
    
    async def analyze_problem(problem: str, execution_mode: str) -> Tuple[str, Any, Any, Any]:
        """Analyze a problem using the multi-agent system."""
        nonlocal current_workflow, current_problem
        
        if not coordinator:
            return "Please initialize the agents first.", None, None, None
        
        if not problem:
            return "Please enter a problem to analyze.", None, None, None
        
        current_problem = problem
        
        try:
            # Decompose problem into tasks
            status = "Decomposing problem into subtasks..."
            tasks = await coordinator.decompose_problem(problem)
            
            if not tasks:
                return "Failed to decompose problem into tasks.", None, None, None
            
            # Execute workflow
            status = f"Executing {len(tasks)} tasks using {execution_mode} mode..."
            parallel = execution_mode == "Parallel"
            
            current_workflow = await coordinator.execute_workflow(tasks, parallel=parallel)
            
            # Create visualizations
            workflow_graph = visualizer.create_workflow_graph(
                current_workflow['workflow_graph'],
                active_agents=list(coordinator.agents.keys())
            )
            
            timeline_chart = visualizer.create_task_timeline(tasks)
            
            confidence_heatmap = visualizer.create_confidence_heatmap(
                current_workflow['agent_contributions']
            )
            
            # Generate status summary
            success_rate = current_workflow['success_rate']
            execution_time = current_workflow['execution_time']
            
            status = f"""Analysis completed successfully!
            
- Tasks executed: {len(tasks)}
- Success rate: {success_rate:.0%}
- Execution time: {execution_time:.1f} seconds
- Agents involved: {len(coordinator.agents)}"""
            
            return status, workflow_graph, timeline_chart, confidence_heatmap
            
        except Exception as e:
            logger.error(f"Error analyzing problem: {str(e)}")
            return f"Error during analysis: {str(e)}", None, None, None
    
    def generate_report(selected_sections: List[str]) -> str:
        """Generate a report from the current workflow results."""
        
        if not current_workflow:
            return "No analysis results available. Please run an analysis first."
        
        try:
            report = report_generator.generate_report(
                current_workflow,
                current_problem,
                include_sections=selected_sections
            )
            
            return report
            
        except Exception as e:
            logger.error(f"Error generating report: {str(e)}")
            return f"Error generating report: {str(e)}"
    
    def get_agent_details(agent_name: str) -> str:
        """Get detailed information about a specific agent."""
        
        if not coordinator or agent_name not in coordinator.agents:
            return "Agent not found or system not initialized."
        
        agent = coordinator.agents[agent_name]
        
        details = f"""## Agent: {agent.name}

**Role:** {agent.role.value}

**Status:** {'Active' if agent.active else 'Inactive'}

**Completed Tasks:** {len(agent.completed_tasks)}

**Current Task:** {agent.current_task.description if agent.current_task else 'None'}

### Recent Tasks:
"""
        
        for task in agent.completed_tasks[-5:]:
            details += f"\n- **{task.id}**: {task.description}"
            details += f"\n  - Status: {task.status.value}"
            details += f"\n  - Confidence: {task.confidence:.0%}"
            details += f"\n  - Execution Time: {(task.completed_at - task.created_at).total_seconds():.1f}s\n"
        
        return details
    
    # Create custom CSS for professional styling
    custom_css = """
    .gradio-container {
        font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
    }
    
    .gr-button-primary {
        background-color: #2563eb !important;
        border-color: #2563eb !important;
    }
    
    .gr-button-primary:hover {
        background-color: #1d4ed8 !important;
        border-color: #1d4ed8 !important;
    }
    
    .container {
        max-width: 1200px;
        margin: 0 auto;
    }
    
    h1 {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        text-align: center;
        font-size: 2.5rem;
        font-weight: 800;
        margin-bottom: 1rem;
    }
    
    h2 {
        color: #1f2937;
        font-size: 1.5rem;
        font-weight: 600;
        margin-top: 2rem;
        margin-bottom: 1rem;
    }
    
    .status-box {
        background-color: #f3f4f6;
        border-radius: 8px;
        padding: 1rem;
        margin: 1rem 0;
        border-left: 4px solid #3b82f6;
    }
    """
    
    # Create Gradio interface
    with gr.Blocks(title="Multi-Agent AI Collaboration System", 
                   theme=gr.themes.Base(), 
                   css=custom_css) as interface:
        
        gr.Markdown("""
        # Multi-Agent AI Collaboration System
        
        Advanced AI system with specialized agents working together to solve complex problems through intelligent task decomposition and parallel processing.
        """)
        
        # System Configuration
        with gr.Row():
            with gr.Column(scale=3):
                api_key_input = gr.Textbox(
                    label="OpenAI API Key",
                    placeholder="sk-...",
                    type="password"
                )
            with gr.Column(scale=1):
                model_select = gr.Dropdown(
                    choices=["gpt-4", "gpt-3.5-turbo"],
                    value="gpt-4",
                    label="Model"
                )
            with gr.Column(scale=1):
                init_button = gr.Button("Initialize Agents", variant="primary")
        
        init_status = gr.Textbox(label="Initialization Status", interactive=False)
        
        # Main tabs
        with gr.Tabs():
            # Problem Analysis Tab
            with gr.TabItem("Problem Analysis"):
                gr.Markdown("### Enter a complex problem for multi-agent analysis")
                
                problem_input = gr.Textbox(
                    label="Problem Statement",
                    placeholder="Example: Analyze the potential impact of AI on healthcare delivery in the next 5 years",
                    lines=3
                )
                
                with gr.Row():
                    execution_mode = gr.Radio(
                        choices=["Sequential", "Parallel"],
                        value="Parallel",
                        label="Execution Mode"
                    )
                    analyze_button = gr.Button("Analyze Problem", variant="primary")
                
                analysis_status = gr.Textbox(
                    label="Analysis Status",
                    interactive=False,
                    lines=5
                )
                
                # Visualization outputs
                with gr.Row():
                    workflow_graph = gr.Plot(label="Agent Collaboration Network")
                
                with gr.Row():
                    timeline_chart = gr.Plot(label="Task Execution Timeline")
                    confidence_heatmap = gr.Plot(label="Agent Performance Metrics")
            
            # Agent Details Tab
            with gr.TabItem("Agent Details"):
                gr.Markdown("### View detailed information about each agent")
                
                agent_selector = gr.Dropdown(
                    choices=["Researcher-1", "Analyst-1", "Critic-1", "Synthesizer-1"],
                    label="Select Agent"
                )
                
                agent_details_button = gr.Button("Get Agent Details")
                
                agent_details_output = gr.Markdown()
            
            # Report Generation Tab
            with gr.TabItem("Report Generation"):
                gr.Markdown("### Generate comprehensive analysis report")
                
                section_selector = gr.CheckboxGroup(
                    choices=[
                        "executive_summary",
                        "task_analysis",
                        "agent_contributions",
                        "key_findings",
                        "recommendations",
                        "confidence_analysis"
                    ],
                    value=[
                        "executive_summary",
                        "key_findings",
                        "recommendations"
                    ],
                    label="Select Report Sections"
                )
                
                generate_report_button = gr.Button("Generate Report", variant="primary")
                
                report_output = gr.Markdown()
            
            # Example Problems Tab
            with gr.TabItem("Example Problems"):
                gr.Markdown("""
                ### Example Problems for Analysis
                
                Click on any example to load it into the analysis tab:
                
                1. **Business Strategy**: "Develop a comprehensive strategy for a traditional retail company to transition to e-commerce while maintaining customer loyalty"
                
                2. **Technology Assessment**: "Evaluate the potential risks and benefits of implementing blockchain technology in supply chain management"
                
                3. **Market Analysis**: "Analyze the competitive landscape for electric vehicles and identify key success factors for new entrants"
                
                4. **Policy Evaluation**: "Assess the implications of remote work policies on organizational culture and productivity"
                
                5. **Innovation Planning**: "Design an innovation framework for a healthcare organization to integrate AI-powered diagnostic tools"
                """)
                
                example_buttons = []
                example_problems = [
                    "Develop a comprehensive strategy for a traditional retail company to transition to e-commerce while maintaining customer loyalty",
                    "Evaluate the potential risks and benefits of implementing blockchain technology in supply chain management",
                    "Analyze the competitive landscape for electric vehicles and identify key success factors for new entrants",
                    "Assess the implications of remote work policies on organizational culture and productivity",
                    "Design an innovation framework for a healthcare organization to integrate AI-powered diagnostic tools"
                ]
                
                for i, problem in enumerate(example_problems):
                    btn = gr.Button(f"Load Example {i+1}", size="sm")
                    example_buttons.append(btn)
        
        # Event handlers
        init_button.click(
            fn=initialize_agents,
            inputs=[api_key_input, model_select],
            outputs=init_status
        )
        
        analyze_button.click(
            fn=lambda p, m: asyncio.run(analyze_problem(p, m)),
            inputs=[problem_input, execution_mode],
            outputs=[analysis_status, workflow_graph, timeline_chart, confidence_heatmap]
        )
        
        agent_details_button.click(
            fn=get_agent_details,
            inputs=agent_selector,
            outputs=agent_details_output
        )
        
        generate_report_button.click(
            fn=generate_report,
            inputs=section_selector,
            outputs=report_output
        )
        
        # Example button handlers
        for i, btn in enumerate(example_buttons):
            btn.click(
                fn=lambda idx=i: example_problems[idx],
                outputs=problem_input
            )
    
    return interface

# Main execution
if __name__ == "__main__":
    interface = create_gradio_interface()
    interface.launch(share=True)