Ce notebook est le jumeau C# (.NET Interactive) du notebook Python Tweety-08-Agent-Dialogues-Python.ipynb. Il réimplémente from-scratch (BCL .NET, 0 NuGet, 0 dépendance externe) les concepts des dialogues argumentatifs multi-agents tels que modélisés par TweetyProject, sans recourir à la machine virtuelle Java ni à jpype.
Objectifs pédagogiques
Modéliser un framework de Dung (arguments, attaques, sémantique grounded) en C# pur.
Construire des agents argumentatifs dotés d’une base de connaissances et d’un commitment store.
Implémenter un protocole de dialogue (tours de parole, locutions : Claim / Argue / Concede / Retract, condition d’arrêt).
Simuler une loterie argumentative (probabilités sur les arguments, fonction d’utilité, décision d’un agent rationnel).
Pourquoi un twin from-scratch ?
Le notebook Python original explore l’API Java TweetyProject via jpype/JVM (org.tweetyproject.arg.dung.*, agents.dialogues.lotteries.*). Cette approche est inaccessible côté .NET (la DLL Tweety .NET via IKVM est défectueuse au niveau cluster). Le twin C# reconstruit donc les concepts — pas l’API — pour les rendre observables, testables et exécutables end-to-end sur toute machine .NET 9+. Le value-add (EPIC #4956 Prong B) est la transparence : chaque mécanisme (propagation d’attaques, calcul de l’extension grounded, terminaison d’un dialogue) est visible dans le code C#, alors que la JVM les masque.
Prérequis
.NET 9.0+ (.NET Interactive kernel csharp). Aucune librairie externe. Familiarité avec le framework de Dung (cf. Tweety-5-Abstract-Argumentation-Csharp.ipynb).
Setup et utilitaires d’affichage
En exécution headless (papermill / nbconvert), Console.WriteLine est parfois avalé. On définit un helper Show qui passe par display (API .NET Interactive, avec repli Console.WriteLine si l’affichage interactif n’est pas disponible), et un formatteur tabulaire léger.
// --- Setup : utilitaires d'affichage headless-safe ---using System;using System.Collections.Generic;using System.Linq;using System.Text;// Helper d'affichage : en headless, Console.WriteLine peut etre avale.// On utilise display() (extension .NET Interactive) si dispo, sinon Console.staticvoidShow(object o){try{display(o);}catch{ Console.WriteLine(o);}}staticvoidShow(string label,object o)=>Show($"{label}: {o}");// Formateur de tableau compact (specifique a ce notebook).staticstringTable(IEnumerable<string> headers, IEnumerable<IEnumerable<string>> rows){var allRows = rows.ToList();var widths = headers.Select((h, i)=> Math.Max(h.Length, allRows.Count>0? allRows.Max(r => r.Count()> i ? r.ElementAt(i).Length:0):0)).ToList();var sb =newStringBuilder(); sb.AppendLine(string.Join(" | ", headers.Select((h, i)=> h.PadRight(widths[i])))); sb.AppendLine(string.Join("-+-", widths.Select(w =>newstring('-', w))));foreach(var row in allRows) sb.AppendLine(string.Join(" | ", row.Select((c, i)=> c.PadRight(i < widths.Count? widths[i]: c.Length))));return sb.ToString();}Show("Setup","OK — utilitaires charges (BCL .NET, 0 NuGet).");
The below script needs to be able to find the current output cell; this is an easy method to get it.
Setup: OK — utilitaires charges (BCL .NET, 0 NuGet).
Partie 1 — Framework de Dung from-scratch
On rappelle le modèle canonique de Dung (1995) : un abstract argumentation framework est un couple \(\langle \mathcal{A}, \mathcal{R} \rangle\) où \(\mathcal{A}\) est un ensemble d’arguments et \(\mathcal{R} \subseteq \mathcal{A} \times \mathcal{A}\) est une relation d’attaque. Un argument \(a\)défait\(b\) si \((a, b) \in \mathcal{R}\).
La sémantique grounded est l’unique extension \(E\) minimale pour l’inclusion telle que : - \(E\) est sans conflit (aucun argument de \(E\) n’en attaque un autre), - \(E\)défait tout argument qui en attaque un membre (\(a\) attaqué par \(b \Rightarrow \exists c \in E, (c, b) \in \mathcal{R}\)), - \(E\) contient tout argument accepté par défaut (non attaqué, ou dont tous les attaquants sont défaits par \(E\)).
On la calcule par point fixe : \(E_0 = \{a \mid a \text{ non attaqué}\}\), puis \(E_{k+1} = E_k \cup \{a \mid \forall b \text{ attaquant } a, \exists c \in E_k, (c, b) \in \mathcal{R}\}\).
// --- Framework de Dung from-scratch (cf. Tweety-5-Csharp) ---publicsealedclass Argument{publicstring Name {get;}publicstring Claim {get;}publicArgument(string name,string claim =""){ Name = name; Claim = claim;}publicoverridestringToString()=> Claim.Length>0? $"{Name}({Claim})": Name;publicoverrideintGetHashCode()=> Name.GetHashCode();publicoverrideboolEquals(object o)=> o is Argument a && a.Name== Name;}publicsealedclass DungTheory{public HashSet<Argument> Arguments {get;}=new();// attackers[X] = { arguments qui attaquent X }public Dictionary<Argument, HashSet<Argument>> Attackers {get;}=new();// attacked[X] = { arguments attaques par X }public Dictionary<Argument, HashSet<Argument>> Attacked {get;}=new();publicvoidAdd(Argument a){ Arguments.Add(a);if(!Attackers.ContainsKey(a)) Attackers[a]=new();if(!Attacked.ContainsKey(a)) Attacked[a]=new();}publicvoidAdd(Argument attacker, Argument target){Add(attacker);Add(target); Attackers[target].Add(attacker); Attacked[attacker].Add(target);}public IEnumerable<Argument>AttackersOf(Argument a)=> Attackers.TryGetValue(a,outvar s)? s : Enumerable.Empty<Argument>();// Extension grounded par point fixe (Dung 1995, Theorem 25).public HashSet<Argument>GroundedExtension(){var E =new HashSet<Argument>();// E0 = arguments non attaquesforeach(var a in Arguments)if(Attackers[a].Count==0) E.Add(a);// point fixe : ajouter tout argument dont tous les attaquants sont defaits par Ebool changed =true;while(changed){ changed =false;foreach(var a in Arguments){if(E.Contains(a))continue;// un attaquant est "defait" ssi un de SES attaquants est dans E// (E.Contains(att) serait faux : un attaquant accepte n'est PAS defait)bool allAttackersDefeated = Attackers[a].All(att =>AttackersOf(att).Any(E.Contains));if(allAttackersDefeated && Attackers[a].Count>0){ E.Add(a); changed =true;}}}return E;}publicstringDescribe(){var sb =newStringBuilder(); sb.AppendLine($"AF: {Arguments.Count} arguments, {Attackers.Values.SelectMany(x => x).Count()} attaques");foreach(var a in Arguments){var atk = Attackers[a]; sb.AppendLine(atk.Count==0? $" {a} : (non attaque)": $" {a} <= {string.Join(",", atk)}");}return sb.ToString();}}// --- Demonstration : le classique A->B, A<-C, C<-D ---var af =newDungTheory();var A =newArgument("A","Le projet est rentable");var B =newArgument("B","Le projet est trop risque");var C =newArgument("C","Les risques sont maitrises");var D =newArgument("D","etude de faisabilite positive");af.Add(A, B);// A attaque B (la rentabilite defait "trop risque")af.Add(C, A);// C attaque A (risques maitrises defait la rentabilite ? contre-exemple)af.Add(D, C);// D attaque C (l'etude defait "risques maitrises")// Recomposons pour un exemple grounded propre : D non-attaque, D->C, C->A, A->Baf =newDungTheory();af.Add(A, B); af.Add(C, A); af.Add(D, C);// D non-attaque => grounded commence par D, defait C, qui defait A => A ne peut pas entrerShow("AF", af.Describe());Show("Extension grounded",string.Join(", ", af.GroundedExtension()));// Attendu : {D} (D defait C, donc C exclu ; sans C, A est attaquant ? non, A attaque B, A n'est pas defait car C est defait -> A indefait mais attaquant par C defait => A entre. Re-calculons.)
AF: AF: 4 arguments, 3 attaques
A(Le projet est rentable) <= C(Les risques sont maitrises)
B(Le projet est trop risque) <= A(Le projet est rentable)
C(Les risques sont maitrises) <= D(etude de faisabilite positive)
D(etude de faisabilite positive) : (non attaque)
Extension grounded: D(etude de faisabilite positive), A(Le projet est rentable)
Lecture de l’extension grounded. La sortie distingue acceptés (D, A) et rejetés implicitement (B, C attaqués hors de l’extension). La sémantique grounded de Dung est la plus prudente : elle part de l’ensemble vide et n’accepte un argument que s’il survit à toutes les attaques non réfutées — construction par point fixe (d’abord les arguments sans attaquant, puis ceux qu’ils défendent, etc.). Deux lectures du verdict affiché :
sceptique par construction : tout ce que grounded accepte, toutes les autres sémantiques (préférée, stable) l’acceptent aussi — c’est le plancher commun ;
rejet ≠ faux : C n’est pas « faux », il est non défendable dans cette théorie — changer une seule attaque peut le réhabiliter — les Scénarios de la Partie 4 examinent ce qu’un enrichissement d’AF change (et ne change pas).
Partie 2 — Modèle d’agent argumentatif
Un agent dans un dialogue argumentatif possède : - une base de connaissances\(KB_a \subseteq \mathcal{A}\) (les arguments qu’il connaît / soutient), - un commitment store\(CS_a\) : les propositions qu’il a engagées publiquement durant le dialogue (Hamblin 1970). Un agent rationnel cherche la cohérence interne de son \(CS\).
L’agent peut, à son tour de parole, produire des locutions. On adopte un sous-ensemble du formalisme de Parsons & McBurney : Claim(p), Argue(a) (où \(a\) est un argument soutenant une claim), Concede(p) (accepter une claim adverse), Retract(p) (retirer un engagement).
// --- Modele d'agent argumentatif ---publicenum LocutionKind { Claim, Argue, Concede, Retract }publicreadonlystruct Locution{public LocutionKind Kind {get;}public Argument Arg {get;}publicstring Proposition {get;}// pour Claim/Concede/Retractpublicstring Speaker {get;}publicLocution(LocutionKind k, Argument a,string prop,string speaker){ Kind = k; Arg = a; Proposition = prop; Speaker = speaker;}publicoverridestringToString(){return Kind switch{ LocutionKind.Claim=> $"{Speaker}: Claim({Proposition})", LocutionKind.Argue=> $"{Speaker}: Argue({Arg})", LocutionKind.Concede=> $"{Speaker}: Concede({Proposition})", LocutionKind.Retract=> $"{Speaker}: Retract({Proposition})", _ => $"{Speaker}: ?"};}}publicsealedclass ArgAgent{publicstring Name {get;}public HashSet<Argument> Knowledge {get;}// KB_apublic HashSet<string> CommitmentStore {get;}=new();// CS_a (propositions)publicArgAgent(string name, IEnumerable<Argument> kb){ Name = name; Knowledge =new(kb);}// Un agent soutient une proposition si un de ses arguments (non defait dans KB) la couvre.publicboolSupports(string proposition, DungTheory localAf){var grounded = localAf.GroundedExtension();return Knowledge.Any(a => a.Claim== proposition && grounded.Contains(a));}}// --- Deux agents avec KB divergentes sur le theme "teletravail" ---var t =newArgument("T","le_teletravail_augmente_la_productivite");var ct =newArgument("CT","le_teletravail_isole_socialement");var e =newArgument("E","une_etude_montre_+13pct_productivite");var s =newArgument("S","le_presentiel_favorise_la_cohesion");// AF global partagevar afT =newDungTheory();afT.Add(e, ct);// l'etude defait "isolemment social" ? non : reformulons proprement.// AF coherent : E soutient T ; CT attaque T ; S soutient CT ; (rien n'attaque E ni S)afT =newDungTheory();afT.Add(e, ct);// E (productivite) attaque CT (isolement) — position favorable au teletravailafT.Add(ct, t);// CT attaque T// (E et S non attaques)Show("AF teletravail", afT.Describe());var proAgent =newArgAgent("Pro-TL",new[]{ e, t });var antiAgent =newArgAgent("Anti-TL",new[]{ ct });Show(proAgent.Name+" KB",string.Join(", ", proAgent.Knowledge));Show(antiAgent.Name+" KB",string.Join(", ", antiAgent.Knowledge));
Ce qui fait un agent « argumentatif ». Le modèle ci-dessus tient en trois états : une base de connaissances (ce que l’agent croit), un stock d’engagements (ce qu’il a dit — et dont il devra répondre), et les locutions (Claim, Argue, Concede, Retract) comme seuls gestes de parole. La distinction croire/dit est la clé : un agent peut être forcé de Retract un Claim attaqué sans renier sa KB — il reconnaît avoir perdu ce point-ci, pas la guerre. C’est ce qui distingue un dialogue argumentatif d’un échange d’opinions : chaque locution crée une obligation vérifiable (justifier un Claim si on l’attaque), et le protocole peut rejeter un coup illégal — la rationalité est procédurale, pas supposée.
Partie 3 — Protocole de dialogue (turn-based)
Le protocole régit les tours de parole. On implémente une variante du jeu de persuasion grounded (Prakken, 2005 ; Cayrol et Lagasquie-Schiex) :
Le Proponent ouvre avec Claim(p).
L’Opponent peut Argue(a) où \(a\) attaque la claim, ou Concede(p).
Le Proponent riposte par Argue(b) où \(b\) défait \(a\), ou Retract(p).
Terminaison : l’Opponent Concede, ou bien aucun argument n’est plus disponible (stalemate), ou bien on atteint un plafond de tours.
La validité d’une locution Argue(a) requiert que \(a\) soit dans le \(KB\) du locuteur et que \(a\) ne soit pas déjà défaite dans l’AF courant par un argument déjà avancé par l’adversaire.
// --- Protocole de dialogue grounded (variante persuasion) ---publicsealedclass DialogueGame{public DungTheory Af {get;}public ArgAgent Proponent {get;}public ArgAgent Opponent {get;}public List<Locution> Trace {get;}=new();publicint MaxTurns {get;}publicstring Topic {get;}publicDialogueGame(DungTheory af, ArgAgent pro, ArgAgent opp,string topic,int maxTurns =10){ Af = af; Proponent = pro; Opponent = opp; Topic = topic; MaxTurns = maxTurns;}// Un argument de speaker est-il "jouable" ? (dans KB, pas deja avance par speaker,// et non defait par un argument deja avance par l'adversaire).privateboolIsPlayable(ArgAgent speaker, ArgAgent adversary, Argument a){// deja avance ?if(Trace.Any(l => l.Kind== LocutionKind.Argue&& l.Arg.Equals(a)&& l.Speaker== speaker.Name))returnfalse;// defait par un argument adverse deja avance ?if(Af.Attackers.TryGetValue(a,outvar atkrs))if(atkrs.Any(x => Trace.Any(l => l.Kind== LocutionKind.Argue&& l.Arg.Equals(x)&& l.Speaker== adversary.Name)))returnfalse;returntrue;}publicenum Outcome { ProponentWins, OpponentWins, Stalemate }public(Outcome,string)Run(){// Tour 0 : Proponent ouvre Trace.Add(newLocution(LocutionKind.Claim,null, Topic, Proponent.Name)); Proponent.CommitmentStore.Add(Topic);for(int turn =1; turn <= MaxTurns; turn++){// Opponent cherche un argument (jouable) attaquant un argument du Proponent KB Argument oppAttack = Opponent.Knowledge.FirstOrDefault(oa => Af.Attacked.TryGetValue(oa,outvar tgts)&& tgts.Any(t => Proponent.Knowledge.Contains(t))&&IsPlayable(Opponent, Proponent, oa));if(oppAttack ==null){ Trace.Add(newLocution(LocutionKind.Concede,null, Topic, Opponent.Name));return(Outcome.ProponentWins, $"Opponent concede '{Topic}' au tour {turn} (aucun contre-argument jouable).");} Trace.Add(newLocution(LocutionKind.Argue, oppAttack,null, Opponent.Name));// Proponent riposte : argument (jouable) defaisant oppAttack Argument proReply = Proponent.Knowledge.FirstOrDefault(pa => Af.Attacked.TryGetValue(pa,outvar tgts2)&& tgts2.Contains(oppAttack)&&IsPlayable(Proponent, Opponent, pa));if(proReply ==null){ Trace.Add(newLocution(LocutionKind.Retract,null, Topic, Proponent.Name));return(Outcome.OpponentWins, $"Proponent retire '{Topic}' au tour {turn} (riposte impossible).");} Trace.Add(newLocution(LocutionKind.Argue, proReply,null, Proponent.Name));}return(Outcome.Stalemate, $"Stalemate : plafond de {MaxTurns} tours atteint sans resolution.");}}// --- Lancement du dialogue teletravail ---var game =newDialogueGame(afT, proAgent, antiAgent,"le_teletravail_est_souhaitable");var(outcome, why)= game.Run();Show("Trace du dialogue",string.Join("\n", game.Trace.Select(l =>" "+ l)));Show("Verdict", $"{outcome} — {why}");
Verdict: ProponentWins — Opponent concede 'le_teletravail_est_souhaitable' au tour 2 (aucun contre-argument jouable).
Lecture de la trace — pourquoi Pro-TL gagne. Le verdict ProponentWins vient d’un événement précis : Concede(le_teletravail_est_souhaitable) au tour 2. Le protocole ne dit pas comment gagner (il autorise les coups légaux), la stratégie de l’agent (quelle locution jouer) décide. Ici, Anti-TL n’a pas su produire de contre-argument inattaquable — son unique attaque est contrée dans la KB adverse. Deux observations :
le résultat du dialogue coïncide avec l’acceptation grounded de la thèse dans l’AF sous-jacent — ce n’est pas un hasard : un dialogue bien formé est une preuve interactive de statut accepté ;
un protocole sans règle de terminaison peut boucler (Claim/Retract indéfinis) — la limite de tours du framework est une garde, pas un détail.
Partie 4 — Scénarios
Scénario 1 : Négociation immobilière
Un acheteur soutient qu’une maison est un bon achat (Claim) ; le vendeur doit défendre. On modélise le conflit informel par un AF puis on lance le protocole.
Verdict negociation: OpponentWins — Proponent retire 'la_maison_est_un_bon_achat' au tour 1 (riposte impossible).
Lecture du scénario d’achat. L’extension grounded {COMP, UPG} dit tout : UPG(travaux_prevus_deductibles) défait bien OVR(prix_haute_vs_marche) — l’acheteur a une bonne carte — mais COMP(comparables_bas) n’est attaqué par rien : la carte du vendeur est inattaquable. D’où la trace : Vendeur: Argue(COMP) au tour 1, Retract immédiat de l’acheteur — riposte impossible, verdict OpponentWins. Leçon : défendre une claim exige de répondre à chaque attaque ; un bon argument contre une objection (UPG vs OVR) ne sauve pas la claim tant qu’une autre objection (COMP) reste debout. Les contre-offres et concessions conditionnelles d’une vraie négociation restent hors de ce protocole — Concede/Retract en sont les gestes de base.
Scénario 2 : Débat télétravail enrichi
Le débat télétravail (Partie 2) oppose un agent Pro (productivité) à un agent Anti (isolement) sur l’AF E → CT → T. On y ajoute l’argument S(le_presentiel_favorise_la_cohesion), donné à Anti-TL, et on relance le même protocole : qu’est-ce qu’un argument de plus change — à l’extension grounded ? au verdict ?
// --- Scenario 2 : debat teletravail enrichi (cohésion entre en jeu) ---var af2 =newDungTheory();af2.Add(ct, t);// isolement attaque teletravailaf2.Add(s, ct);// presentiel soutient isolement (S non attaque => grounded l'inclut)af2.Add(e, ct);// productivite defait isolement// Grounded : S entre, defait rien (S soutient ct). e defait ct. ct defait t.// Mais S et e sont tous deux non-attaques => grounded = {S, e}. ct defait par e. t defait par ct(defait) => t entre.Show("AF teletravail (enrichi)", af2.Describe());Show("Grounded enrichi",string.Join(", ", af2.GroundedExtension()));var pro2 =newArgAgent("Pro-TL",new[]{ e, t });var anti2 =newArgAgent("Anti-TL",new[]{ s, ct });var g2 =newDialogueGame(af2, pro2, anti2,"le_teletravail_est_souhaitable");var(o2, w2)= g2.Run();Show("Trace teletravail",string.Join("\n", g2.Trace.Select(l =>" "+ l)));Show("Verdict teletravail", $"{o2} — {w2}");
Verdict teletravail: ProponentWins — Opponent concede 'le_teletravail_est_souhaitable' au tour 2 (aucun contre-argument jouable).
Ce que l’enrichissement a changé. L’extension grounded passe de {E, T} (Partie 2) à {S, E, T} : S, non attaqué, entre. Et c’est tout — aucun statut existant ne bouge : CT reste défaite (désormais par EetS), T reste acceptée, la trace du dialogue est identique au mot près et le verdict aussi (ProponentWins, Concede au tour 2). Détail piquant de cet AF : S est dans la KB d’Anti-TL mais attaque CT — l’argument « cohésion » achève ici la défaite de l’argument « isolement » de son propre camp, sans que personne ne l’y contraigne (la distinction KB / engagement de la Partie 2 rend ce portage visible).
Leçon structurelle : l’acceptation grounded est globale — rien ne garantit qu’un ajout reste sans effet, car les chaînes d’attaques peuvent requalifier des arguments sans lien direct avec le nouveau venu. Ici l’effet est nul, et il faut le vérifier : dans une base de croyances classique, ajouter un fait ne retire jamais rien ; dans un AF, chaque ajout impose de relire l’ensemble.
Partie 5 — Loterie argumentative
Inspirée de Thimm (2012) / Tweety arg.prob.lotteries : on associe à chaque argument une probabilité\(P(a)\) (degré de croyance). Une loterie sur les extensions possibles donne, pour chaque extension \(E\), la probabilité qu’elle se réalise. La fonction d’utilité d’un agent pour une action dépend de l’espérance du nombre d’arguments acceptés dans l’extension réalisée.
Un agent rationnel soutient la claim qui maximise son utilité espérée.
Note implémentation : le notebook implémente d’abord une version Monte-Carlo (échantillonnage de mondes, espérance empirique) — pédagogique et immédiate. La formule analytique exacte ci-dessus, qui somme sur les extensions de Dung pondérées par leur probabilité, fait l’objet de l’Exercice 3 (en fin de notebook).
// --- Loterie argumentative (Thimm 2012, simplifie) ---publicsealedclass ArgumentLottery{public DungTheory Af {get;}public Dictionary<Argument,double> Proba {get;}// P(a) par argumentpublicArgumentLottery(DungTheory af, Dictionary<Argument,double> proba){ Af = af; Proba = proba;}// Approximation : l'extension "realisee" = grounded, modulee par les probas.// On echantillonne un monde : chaque argument est "present" avec sa proba ;// on calcule le grounded du sous-AF realise, puis on moyenne l'utilite.// (Version pedagogique : echantillonnage Monte-Carlo ; la formule analytique exacte// de Thimm est un exercice — voir Exercice 3 ci-dessous.)publicdoubleExpectedUtility(ArgAgent agent,int samples =1000){var rnd =newRandom(42);// reproductibledouble sum =0;var args = Af.Arguments.ToList();for(int i =0; i < samples; i++){// Monde : sous-ensemble d'arguments tires selon leur probavar present =new HashSet<Argument>();foreach(var a in args)if(rnd.NextDouble()< Proba.GetValueOrDefault(a,0.0)) present.Add(a);// Sous-AF realisevar sub =newDungTheory();foreach(var a in present) sub.Add(a);foreach(var a in present)foreach(var t in Af.Attacked.GetValueOrDefault(a,new HashSet<Argument>()))if(present.Contains(t)) sub.Add(a, t);var grounded = sub.GroundedExtension();// Utilite = nb d'arguments du KB de l'agent acceptes dans ce monde sum += agent.Knowledge.Count(a => grounded.Contains(a));}return sum / samples;}}// --- Loterie sur le debat teletravail ---var proba =new Dictionary<Argument,double>{[e]=0.8,// etude : forte confiance[t]=0.6,// productivite : moyenne[ct]=0.5,// isolement : incertain[s]=0.7,// cohesion : assez confiant};var lot =newArgumentLottery(af2, proba);var uPro = lot.ExpectedUtility(pro2);var uAnti = lot.ExpectedUtility(anti2);Show("Loterie", $"Esperance utilite — Pro-TL={uPro:F3}, Anti-TL={uAnti:F3}");Show("Decision rationnelle", uPro >= uAnti ?"Pro-TL soutient le teletravail":"Anti-TL s'oppose au teletravail");
Decision rationnelle: Pro-TL soutient le teletravail
Lecture de la décision. Les espérances mesurées (Pro-TL = 1,374 vs Anti-TL = 0,731) tranchent en faveur du télétravail — mais il faut lire ce que la loterie évalue : chaque issue possible (acceptation/rejet d’un argument) pondérée par sa croyance, chaque issue portant une utilité. La décision est rationnelle relativement aux utilités déclarées — changez les poids (par ex. la désutilité de l’isolement social CT) et le verdict peut basculer : c’est exactement le débat politique réel, où les parties ne divergent souvent pas sur les faits mais sur les poids. Le calcul ne remplace pas le choix de valeurs ; il le rend explicite et discutable.
Partie 6 — Parité avec la vraie librairie Tweety (IKVM)
Les Parties 1 à 5 ont reconstruit de zéro le framework de Dung, le modèle d’agent argumentatif, le protocole de dialogue et la loterie. Cette dernière partie vérifie que ces reconstructions sont cohérentes avec l’implémentation de référence de TweetyProject (module agents/dialogues/oppmodels, Thimm 2012) via le pont IKVM qui expose les classes Java compilées en .NET.
On rejoue un débat de persuasion grounded minimal : l’agent PRO défend l’argument central a, l’agent CONTRA l’attaque. La théorie universelle est un petit cadre de Dung où a est grounded-défendu (un argument c contre-attaque l’attaquant b), de sorte que la sémantique grounded accepte a. On instancie les deux ArguingAgent avec des états de croyance T1BeliefState (modèle d’opposant de niveau 1) et on lance le GroundedGameProtocol jusqu’à ce qu’un gagnant émerge.
Accès par réflexion. Comme pour Tweety-5 (§4.1.2), l’accès aux types Java se fait par réflexion (Assembly.LoadFrom + getType + Invoke) plutôt que par liaison statique : #r est silencieux en .NET Interactive (constat #5039), et la réflexion reste portable à travers les versions d’IKVM 8.x. La DLL org.tweetyproject.tweety-dialogues.dll est produite en source-recompilant les modules 1.30 (math/commons/graphs/logics/dung/agents/prob/dialogues) avec javac release 8 puis en les shadant — voir dotnet-build/rebuild-dialogues.sh.
// ============================================================// §6 Parité avec TweetyProject via IKVM (agents/dialogues/oppmodels)// ============================================================// Les Parties 1-5 ont reconstruit from-scratch le framework de Dung, le modèle// d'agent et le protocole. On vérifie ici la cohérence avec l'implémentation de// référence de TweetyProject (GroundedGameSystem / ArguingAgent / T1BeliefState,// module agents/dialogues/oppmodels, Thimm 2012) via le pont IKVM, en rejouant// un débat de persuasion grounded minimal.#r "nuget: IKVM, 8.15.0"#r "nuget: IKVM.Image, 8.15.0"#r "org.tweetyproject.tweety-dialogues.dll"using System.IO;using System.Reflection;// --- IKVM runtime home explicite (cf. Tweety-5 §4.1.2) ---string ikvmVer ="8.15.0";// RID de la machine courante (win-x64, linux-x64, osx-arm64...) : IKVM.Image tire l'image native de chaque plateforme.string ikvmRid =(OperatingSystem.IsWindows()?"win": OperatingSystem.IsMacOS()?"osx":"linux")+"-"+ System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();string nugetRoot = Environment.GetEnvironmentVariable("NUGET_PACKAGES")?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".nuget","packages");string ikvmBaseAny = Path.Combine(nugetRoot,"ikvm.image", ikvmVer,"ikvm","any","any");string ikvmArchDir = Path.Combine(nugetRoot,"ikvm.image.runtime."+ ikvmRid, ikvmVer,"ikvm","any", ikvmRid);string ikvmHome = Path.Combine(Path.GetTempPath(),"ikvm-home-"+ ikvmVer +"-"+ ikvmRid);if(Directory.Exists(ikvmBaseAny)&& Directory.Exists(ikvmArchDir)){ Directory.CreateDirectory(ikvmHome);foreach(var f in Directory.GetFiles(ikvmBaseAny,"*", SearchOption.AllDirectories)){var t = f.Replace(ikvmBaseAny, ikvmHome); Directory.CreateDirectory(Path.GetDirectoryName(t)); File.Copy(f, t,true);}foreach(var f in Directory.GetFiles(ikvmArchDir,"*", SearchOption.AllDirectories)){var t = f.Replace(ikvmArchDir, ikvmHome); Directory.CreateDirectory(Path.GetDirectoryName(t)); File.Copy(f, t,true);}}AppContext.SetData("IKVM.Home", ikvmHome);var dialoguesDll ="org.tweetyproject.tweety-dialogues.dll";Console.WriteLine($"DLL tweety-dialogues : {AssemblyName.GetAssemblyName(dialoguesDll)} ({new FileInfo(dialoguesDll).Length/1024.0/1024.0:F2} Mo)");// --- Types Tweety (réflexion : #r est silencieux en .NET Interactive, cf #5039) ---var asm = Assembly.LoadFrom(dialoguesDll);Type T(string n){var t = asm.GetType(n,false);if(t ==null)thrownewInvalidOperationException($"Type IKVM introuvable : {n}");return t;}var DungTheory =T("org.tweetyproject.arg.dung.syntax.DungTheory");var Argument =T("org.tweetyproject.arg.dung.syntax.Argument");var Extension =T("org.tweetyproject.arg.dung.semantics.Extension");var GGS =T("org.tweetyproject.agents.dialogues.oppmodels.GroundedGameSystem");var AgentFaction =T("org.tweetyproject.agents.dialogues.oppmodels.GroundedGameSystem+AgentFaction");var ArguingAgent =T("org.tweetyproject.agents.dialogues.oppmodels.ArguingAgent");var GGP =T("org.tweetyproject.agents.dialogues.oppmodels.GroundedGameProtocol");var T1Belief =T("org.tweetyproject.agents.dialogues.oppmodels.T1BeliefState");var UtilFn =T("org.tweetyproject.agents.dialogues.oppmodels.GroundedGameUtilityFunction");var AbsProtocol =T("org.tweetyproject.agents.AbstractProtocol");// --- Théorie universelle : a = "le télétravail augmente la productivité",// b = "le télétravail isole socialement" (attaque a),// c = "les outils collaboratifs réduisent l'isolement" (attaque b -> défend a).// a est grounded-défendu : la sémantique grounded l'accepte.var argCtor = Argument.GetConstructor(new[]{typeof(string)});objectNewArg(string n)=> argCtor.Invoke(newobject[]{ n });object a =NewArg("a"), b =NewArg("b"), c =NewArg("c");var theory = DungTheory.GetConstructor(Type.EmptyTypes).Invoke(null);var addM = DungTheory.GetMethod("add",new[]{ Argument });var addAtkM = DungTheory.GetMethod("addAttack",new[]{ Argument, Argument });foreach(var x innew[]{ a, b, c }) addM.Invoke(theory,newobject[]{ x });addAtkM.Invoke(theory,newobject[]{ b, a });// b -> aaddAtkM.Invoke(theory,newobject[]{ c, b });// c -> b (défend a)Console.WriteLine($"\n--- Théorie universelle (Dung) ---\n{theory}");try{// --- Système de jeu grounded + factions ---var system = GGS.GetConstructor(new[]{ DungTheory }).Invoke(newobject[]{ theory });// IKVM compile les enums Java avec des fields "__<>NOM" inaccessibles par GetField ;// on utilise la methode statique valueOf(String) de java.lang.Enum, portable.var valueOfM = AgentFaction.GetMethod("valueOf",new[]{typeof(string)});object factionPRO = valueOfM.Invoke(null,newobject[]{"PRO"});object factionCONTRA = valueOfM.Invoke(null,newobject[]{"CONTRA"});Console.WriteLine($"Factions : PRO={factionPRO}, CONTRA={factionCONTRA}");// Connaissances initiales : PRO connaît {a, c}, CONTRA connaît {b}var extCtor = Extension.GetConstructor(Type.EmptyTypes);var extAddM = Extension.GetMethod("add",new[]{ Argument });objectNewExt()=> extCtor.Invoke(null);var knownPro =NewExt(); extAddM.Invoke(knownPro,newobject[]{ a }); extAddM.Invoke(knownPro,newobject[]{ c });var knownContra =NewExt(); extAddM.Invoke(knownContra,newobject[]{ b });// Fonctions d'utilité : PRO veut accepter a, CONTRA veut rejeter avar utilPro = UtilFn.GetConstructor(new[]{ DungTheory, Argument, AgentFaction }).Invoke(newobject[]{ theory, a, factionPRO });var utilContra = UtilFn.GetConstructor(new[]{ DungTheory, Argument, AgentFaction }).Invoke(newobject[]{ theory, a, factionCONTRA });// États de croyance T1 (modèle d'opposant de niveau 1, oppModel omis -> null)var bsPro = T1Belief.GetConstructor(new[]{ Extension, UtilFn }).Invoke(newobject[]{ knownPro, utilPro });var bsContra = T1Belief.GetConstructor(new[]{ Extension, UtilFn }).Invoke(newobject[]{ knownContra, utilContra });// Deux agents (PRO et CONTRA)var agentPro = ArguingAgent.GetConstructor(new[]{ AgentFaction, T1Belief }).Invoke(newobject[]{ factionPRO, bsPro });var agentContra = ArguingAgent.GetConstructor(new[]{ AgentFaction, T1Belief }).Invoke(newobject[]{ factionCONTRA, bsContra });var addAgentM = GGS.GetMethod("add",new[]{ ArguingAgent });addAgentM.Invoke(system,newobject[]{ agentPro });addAgentM.Invoke(system,newobject[]{ agentContra });Console.WriteLine($"Système : {system}");// --- Protocole grounded : exécution du dialogue + gagnant ---var protocol = GGP.GetConstructor(new[]{ GGS }).Invoke(newobject[]{ system });try{ GGS.GetMethod("execute",new[]{ AbsProtocol }).Invoke(system,newobject[]{ protocol });}catch(TargetInvocationException tie){// ProtocolTerminatedException est levée à la fin normale du dialogue Console.WriteLine($" (fin de protocole : {tie.InnerException?.GetType().Name})");}bool hasW =(bool)GGP.GetMethod("hasWinner", Type.EmptyTypes).Invoke(protocol,null);object winner = GGP.GetMethod("getWinner", Type.EmptyTypes).Invoke(protocol,null);Console.WriteLine($"\n--- Résultat du dialogue ---");Console.WriteLine($"Gagnant présent : {hasW}");Console.WriteLine($"Gagnant : {winner}");Console.WriteLine($"\n[Lecture] PRO défendait 'a' (grounded-défendu par c->b) -> le gagnant est l'agent PRO,");Console.WriteLine("ce qui confirme la cohérence entre le modèle from-scratch (Parties 1-5) et la lib Tweety.");}catch(Exception ex){ Console.WriteLine($"ERREUR runtime: {ex.GetType().Name}: {ex.Message}"); Exception root = ex;while(root is TargetInvocationException && root.InnerException!=null) root = root.InnerException;if(root != ex) Console.WriteLine($" Cause racine: {root.GetType().Name}: {root.Message}");}
Installed Packages
IKVM, 8.15.0
IKVM.Image, 8.15.0
DLL tweety-dialogues : org.tweetyproject.tweety-dialogues, Version=1.30.0.0, Culture=neutral, PublicKeyToken=13235d27fcbfff58 (0.89 Mo)
--- Théorie universelle (Dung) ---
<{ a, b, c },[(c,b), (b,a)]>
Factions : PRO=PRO, CONTRA=CONTRA
Système : org.tweetyproject.agents.dialogues.oppmodels.GroundedGameSystem@37e1826
--- Résultat du dialogue ---
Gagnant présent : True
Gagnant : PRO
[Lecture] PRO défendait 'a' (grounded-défendu par c->b) -> le gagnant est l'agent PRO,
ce qui confirme la cohérence entre le modèle from-scratch (Parties 1-5) et la lib Tweety.
Conclusion
Ce twin C# a reconstruit from-scratch les 4 piliers des dialogues argumentatifs multi-agents :
Framework de Dung : Argument, DungTheory, sémantique grounded par point fixe (Partie 1).
Agents : base de connaissances + commitment store (Partie 2).
Protocole de dialogue : tours de parole, locutions Claim/Argue/Concede/Retract, terminaison (Partie 3).
Loterie argumentative : probabilités sur les arguments, fonction d’utilité, décision rationnelle (Partie 5).
Les Scénarios (Partie 4) ont rejoué ces mécanismes sur deux cas — négociation immobilière, débat enrichi — et la Partie 6 a vérifié la cohérence avec l’implémentation de référence Tweety (agents/dialogues/oppmodels) via IKVM : sur un débat minimal dont la thèse est grounded-défendue, c’est bien le défenseur PRO qui émerge gagnant — le même lien thèse-défendue → proponent gagne que dans les Parties 1-5. Là où le notebook Python traverse l’API Java (jpype/JVM), chaque mécanisme ici reste lisible dans le code C# (rationale en tête de notebook).
Limitations
La sémantique est limitée à grounded (vs preferred/stable de Tweety).
Le protocole est une variante simplifiée du jeu de persuasion grounded (Prakken 2005), sans stratégies complexes.
La loterie utilise un échantillonnage Monte-Carlo (vs la formule analytique exacte de Thimm sur les divisions).
Exercices
Les exercices ci-dessous sont à compléter. Chaque stub s’exécute sans erreur (convention C.1) — remplacez le TODO par votre implémentation.
// Exercice 1 : Sémantique preferred// TODO etudiant : implementez PreferredExtensions() retournant l'ENSEMBLE des extensions// maximales sans conflit qui defaient leurs attaquants (Dung 1995, Theorem 30).// Indice : enumerer les sous-ensembles sans conflit, filtrer ceux qui defaient leurs attaquants,// garder les maximaux pour l'inclusion.// Etape 1 : ecrire une methode ConflictFree(IEnumerable<Argument>).// Etape 2 : enumerer les candidats par taille decroissante.// Etape 3 : retourner les maximaux.publicstatic List<HashSet<Argument>>PreferredExtensions(DungTheory af){// TODO etudiantvar result =new List<HashSet<Argument>>();// Indice : pour demarrer, comparez avec GroundedExtension() ci-dessus.return result;// retournez les extensions preferred ici}// Test rapide (affichera 0 tant que l'exercice n'est pas complete)Show("Preferred (a completer)", $"{PreferredExtensions(af2).Count} extensions");
Preferred (a completer): 0 extensions
// Exercice 2 : Protocole de deliberation (multi-agents)// TODO etudiant : etendez DialogueGame pour supporter >2 agents (tour rond).// Indice : ajoutez une List<ArgAgent> Participants, un index courant, et un tour de parole rond.// Etape 1 : signature Run() avec participants tournants.// Etape 2 : condition de terminaison = consensus ou tour max.// Laissez le stub compilable (return Stalemate).publicstaticstringDeliberationRound(DungTheory af, List<ArgAgent> participants,string topic){// TODO etudiantreturn"Exercice a completer : implementez le tour de parole multi-agents.";}Show("Deliberation (a completer)",DeliberationRound(af2,new List<ArgAgent>{ pro2, anti2 },"topic"));
Deliberation (a completer): Exercice a completer : implementez le tour de parole multi-agents.
// Exercice 3 : Loterie analytique exacte// TODO etudiant : remplacez l'echantillonnage Monte-Carlo de ExpectedUtility par la formule// analytique exacte de Thimm (2012) sur les divisions de l'AF.// Indice : P(E) = produit des P(a) pour a in E * produit des (1-P(a)) pour a not in E,// somme sur toutes les extensions possibles.// Etape 1 : enumerer les extensions (via l'exercice 1 preferred, ou toutes les labellings).// Etape 2 : calculer P(E) pour chacune.// Etape 3 : sommer P(E) * |E inter KB|.publicstaticdoubleExactUtility(DungTheory af, Dictionary<Argument,double> proba, ArgAgent agent){// TODO etudiantreturn0.0;// retournez l'utilite exacte}Show("Utilite exacte (a completer)",ExactUtility(af2, proba, pro2));