<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Amrltqt's Blog</title>
        <link>https://amrltqt.com</link>
        <description>Blog posts from Amrltqt's Blog</description>
        <lastBuildDate>Thu, 28 May 2026 21:18:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Writizzy</generator>
        <language>en</language>
        <image>
            <title>Amrltqt's Blog</title>
            <url>https://writizzy.b-cdn.net/blogs/eacaab44-eff6-4bea-ba44-f9d0c39c7aa6/1779561312731-x8vnxge.png</url>
            <link>https://amrltqt.com</link>
        </image>
        <copyright>All rights reserved 2026, Amrltqt's Blog</copyright>
        <item>
            <title><![CDATA[Partage de contenu entre IA et plateformes]]></title>
            <link>https://amrltqt.com/p/partage-de-contenu-entre-ia-et-plateformes</link>
            <guid>https://amrltqt.com/p/partage-de-contenu-entre-ia-et-plateformes</guid>
            <pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les plateformes se protègent contre les agents IA. Pourtant il faudra trouver un compromis pour faciliter leur navigation.]]></description>
            <content:encoded><![CDATA[<p>Si on se met purement du côté d’un utilisateur, le fait que les plateformes en ligne d’achat de produits ou de partage de contenu, monétisé ou non, se blindent pour éviter de consommer leur contenu en dehors de leur écosystème est un peu pénible.</p>
<p>Maintenant qu’on commence à voir des agents s’insérer dans notre quotidien. En tout cas dans le mien. J’utilise le harnais Hermes avec Codex où les modèles de Mistral. J’ai des difficultés à interagir avec ces plateformes avec ces agents, car derrière les URLs que je peux leur partager se trouve tout un tas de mesures hostiles à mes nouveaux outils.</p>
<p>Clairement ce n’est pas le sens de l’histoire, même si je comprends la nécessité de monétiser ces contenus ou de se protéger d’un trafic trop important et de maîtriser leur exfiltration. J’ai la sensation qu’il va falloir faire évoluer, dans leur intérêt et le nôtre, les pratiques pour trouver un nouveau compromis.</p>
<p>Sans aller plus loin dans la réflexion sur les business models ou autres, il y en a trop de différents, on peut vite voir que ce qui va intéresser un agent c’est la matière brute.</p>
<p>Sur une marketplace c’est le produit, le prix, la description. Sur des articles de presse, au moins un titre et un résumé.</p>
<p>Toutes les plateformes ne sont pas prêtes à partager autant et les stratégies pour le faire ne sont pas évidentes. Pourtant je pense qu’elles vont être forcées de le faire pour faciliter la navigation parallèle des agents.</p>
<p>Est-ce qu’on va généraliser les MCP ? Est-ce qu’on va rediriger le trafic des agents vers une vue .md ? Est-ce qu’on va avoir des URLs portant une partie du contenu ?</p>
<p>Chaque site web fournissant un MCP ou un moyen d’accès dédié n’est pas envisageable. Je crois que les URLs sont toujours le meilleur moyen de naviguer. Est-ce que lorsqu’on se présente comme un agent, via un header par exemple, on est redirigé vers une URL qui contient l’URL principale et quelques informations utiles pour susciter la navigation ? Pourquoi pas. Il ne faut pas oublier que pour la plupart des ressources en ligne, le but c’est quand même d’être découvertes.</p>
<p>Est-ce qu’on aura des plateformes intermédiaires qui feront ce travail ? Je pense que les deux approches sont possibles.</p>
<p>Une autre solution repose sur la production d’extensions d’URLs qui contiennent une partie du contenu pour décrire ce qu’il y a derrière. J’imagine qu’il est possible d’encoder le contenu pour simplifier le partage avec des IA.</p>
<p>Je sais pas dans quel sens le vent va tourner, mais je suis prêt à parier qu&#39;il tournera et qu&#39;il faudra que ces plateformes acceptent d&#39;être consommé par des agents autonomes, au moins pour la partie emergée de leur iceberg de données.</p>
]]></content:encoded>
            <category>reflexions</category>
            <category>agents</category>
            <category>web</category>
            <category>ia</category>
            <enclosure url="https://writizzy.b-cdn.net/blogs/eacaab44-eff6-4bea-ba44-f9d0c39c7aa6/1779623581717-zozi5vg.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Worktree, ton meilleur ami ?]]></title>
            <link>https://amrltqt.com/p/worktree-ton-meilleur-ami</link>
            <guid>https://amrltqt.com/p/worktree-ton-meilleur-ami</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Les worktrees, ces potes qu'on vont t'emmener vibe coder jusqu'au bout de la nuit.]]></description>
            <content:encoded><![CDATA[<p>On est en février 2026. Laisser Claude, Codex, Devstral (et Kimi qui enflamme les réseaux) bosser pendant que tu supervises est devenu un vrai workstyle. Tu as écrit des AGENTS.md, des skills, des CLAUDE.md, tu es très chaud sur toutes les bonnes pratiques que t&#39;as lues sur le net.</p>
<p>Tu maîtrises et ton agent préféré a la main sur le plan que tu as crafté avec amour pendant des heures, il tourne, longtemps. Et maintenant, que faire ? Prendre un café, faire du sport, dormir ? Non, tu as été entraîné pendant 15 ans à bosser comme un idiot pendant 8 heures, pas question de rester les bras croisés dès qu&#39;un agent fait ton boulot d&#39;avant. Tu vas donc multitasker : ouvrir une autre feature, mettre à jour de la doc, revoir un test qui date de trois semaines... mais tu te heurtes à un mur. Ton agent est occupé. Tu ne veux pas le déranger. Tu veux lancer un nouveau truc sans cloner une seconde fois ton repo.</p>
<p>Tu vas utiliser <code>git worktree</code>. C&#39;est ton nouveau meilleur ami.</p>
<h2>Pourquoi un worktree ?</h2>
<p>Parce que tu veux rester dans le même dépôt, garder les mêmes remotes, et travailler sur une autre branche ou expérimentation pendant qu&#39;un agent continue d&#39;écrire du code sur la branche principale. Créer un worktree, c&#39;est comme ouvrir une nouvelle fenêtre sur ton repo. C&#39;est une copie complète qui pointe vers une autre branche.</p>
<p>Tu fais :</p>
<p><code>git worktree add ../mon-prochain-truc feature/autre-chose</code></p>
<p>Tu te retrouves avec un dossier <code>../mon-prochain-truc</code>, tu y ouvres un nouveau terminal, tu peux commencer du code, des docs, une revue de tests… l&#39;agent continue à tourner là où tu es resté.</p>
<p>Le petit truc surprenant, mais terriblement évident, c&#39;est qu&#39;il faut créer ce répertoire en dehors de l&#39;arbre courant actuellement géré par git. D&#39;où le <code>../</code>. Ce n&#39;est pas une espèce d&#39;environnement interne ou autre, c&#39;est vraiment une copie ailleurs qu&#39;il faut penser à nettoyer.</p>
<h3>Comment je l&#39;utilise</h3>
<ol>
<li>Je réserve les worktrees aux tâches courtes (doc, tests, ticket de bug). L&#39;agent est déjà parti sur la branche principale et j&#39;ai pas la largeur d&#39;esprit de lancer plusieurs gros changements en même temps.</li>
<li>Je crée un worktree vers une branche dédiée au même niveau que mon repo courant, par exemple <code>&lt;monrepo&gt;-&lt;mabranche&gt;</code>.</li>
<li>J&#39;ouvre un autre terminal, je travaille, je commit, je pousse, et je reviens sur la branche principale quand j&#39;ai besoin d&#39;évaluer ou réintégrer ce que l&#39;agent a généré.</li>
</ol>
<p>Quand j&#39;ai fini, je supprime le worktree :</p>
<p><code>git worktree remove ../mon-prochain-truc</code></p>
<p>Le dossier disparaît, mais le travail reste sur la branche associée et ça, c&#39;est royal. Plus qu&#39;à push !</p>
<h3>Quelques garde-fous</h3>
<ul>
<li>Fais attention aux fichiers non suivis : un worktree a ses propres modifications. Comme toujours, commit ou stash avant de supprimer le worktree.</li>
<li>Tu peux ouvrir plusieurs worktrees, mais garde en tête que chaque branche a son propre HEAD. Ne mélange pas les chemins de build ou les environnements virtuels sans faire attention.</li>
<li>Si tu utilises un agent pour merger ou rebaser, laisse-le terminer avant de supprimer le worktree.</li>
</ul>
<p>Dans ce monde où la hustle culture est reine, <code>git worktree</code> te donne les pleins pouvoirs pour la productivité infinie, mais aussi pour une belle explosion de charge mentale. On parlera de cette problématique dans le prochain post. Pense à t&#39;abonner à la newsletter, garde cette astuce près de toi, va à ton rythme et fais attention à ta santé.</p>
]]></content:encoded>
            <category>productivité</category>
            <category>agents</category>
        </item>
        <item>
            <title><![CDATA[Wrapping 2025: Deux ans à envoyer des chiffres dans des chats]]></title>
            <link>https://amrltqt.com/p/wrapping-2025-deux-ans-a-envoyer-des-chiffres-dans-des-chats</link>
            <guid>https://amrltqt.com/p/wrapping-2025-deux-ans-a-envoyer-des-chiffres-dans-des-chats</guid>
            <pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Les dashboards existent, mais les gens n’y vont pas. Depuis deux ans, je développe EZD pour envoyer la donnée directement là où on travaille déjà, Slack, mail, chat et rendre la BI plus simple, plus banale mais surtout plus utilisée.]]></description>
            <content:encoded><![CDATA[<p>Fin 2025, ça fait un peu plus de <strong>deux ans</strong> que je développe <a href="https://ez-dashboard.com">ez-dashboard.</a></p>
<p>À la base, ce n’était pas un projet de SaaS. Juste une tentative de résoudre un problème que je voyais partout : les dashboards existent, mais <strong>les gens n’y vont pas</strong>.</p>
<p>Pourquoi ? Des logiciels trop compliqués, des licences qui coûtent un bras, et tout simplement le fait qu’il faut déjà savoir <strong>où les trouver</strong>.</p>
<p>Dans les petites structures, la donnée finit souvent dans Google Sheets ou Excel. Ça marche… jusqu’au moment où tout le monde s’emmêle, où les versions divergent, et où plus personne ne sait quel chiffre est le bon.</p>
<p>Dans les plus grosses, c’est l’inverse : plateformes data, outils de BI, équipes dédiées. C’est solide, mais lourd, lent, et sous le contrôle parfois flou de ce qu’on appelle la <em>data gouvernance</em> — une science assez inexacte. Et malgré tout, les équipes continuent à demander les chiffres dans Slack ou par mail.</p>
<p>Le point commun, c’est que <strong>l’accès à la donnée reste compliqué</strong>.</p>
<p>De mon côté, je suis de moins en moins convaincu par le modèle “tu veux un chiffre → va ouvrir un outil”. J’ai plutôt envie que la donnée arrive <strong>là où on échange déjà</strong>.</p>
<p>C’est le choix que j’ai fait pour EZD il y a deux ans. Tu configures des dashboards, et les chiffres te sont envoyés dans Slack, par mail, ou ailleurs. Pas besoin d’aller les chercher. Pas besoin d’apprendre un nouvel outil pour les consulter.</p>
<p>La solution est en production, utilisée par quelques clients qui en sont satisfaits. J’espère pouvoir parler de leur expérience ici prochainement.</p>
<p>L’objectif n’est pas de réinventer la BI. J’essaie juste de la rendre un peu plus <strong>banale</strong>, un peu plus <strong>accessible</strong>, un peu plus <strong>utilisée</strong>.</p>
<p>Créer ce qu’on appelle de la <em>boring tech</em> : un logiciel que tu oublies, qui ne cherche pas à t’enfermer dans son écosystème, mais qui fait simplement son travail discrètement et efficacement. C’est une autre manière de voir la technologie, à laquelle je tiens beaucoup.</p>
<p>Pour l’essayer, il suffit de créer un compte sur <a href="https://ez-dashboard.com">https://ez-dashboard.com</a></p>
<p>Je suis à la recherche de feedbacks pour développer cette idée plus largement. Les quotas en free sont volontairement assez bas. Si tu veux tester plus sérieusement, envoie-moi un message à <strong><a href="https://amrltqt.commailto:quentin@ez-dashboard.com">quentin@ez-dashboard.com</a></strong>, je pourrai débloquer un peu plus.</p>
<p>Bonne fin d’année 2025.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2025, l’année des agents. Et après ?]]></title>
            <link>https://amrltqt.com/p/human-in-the-loop-ia-2026</link>
            <guid>https://amrltqt.com/p/human-in-the-loop-ia-2026</guid>
            <pubDate>Sun, 14 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Human-in-the-Loop, agents IA et confiance : pourquoi l’intégration humaine est un prérequis à l’autonomie.
]]></description>
            <content:encoded><![CDATA[<p>2025 a été l’année des agents. On a découvert qu’on pouvait améliorer significativement la performance des modèles de langage en les faisant itérer, en injectant de l’information et du contexte tout au long du processus de construction d’une réponse.</p>
<p>On a fait un bond en avant massif dans l’architecture de ces systèmes. Aujourd’hui, ils produisent enfin la valeur qu’on espérait depuis la sortie de ChatGPT. Est-ce que cette valeur compense les investissements consentis ? Difficile à dire. L’avenir nous le dira. Fin 2025, l’intégration la plus convaincante reste néanmoins celle des copilotes de programmation. La valeur est immédiate, mesurable, et il est logique que le secteur ait d’abord investi là où il était le plus mature. Claude Code est bluffant, Codex aussi, et même Vibe de Mistral est très agréable à utiliser.</p>
<p>Maintenant qu’on en est là, la question devient évidente : quelle est la suite ?</p>
<p>Est-ce que les agents vont devenir toujours plus autonomes et aller plus loin seuls ? Ou est-ce qu’on va continuer dans la voie ouverte par les agents de code, qui exposent chaque étape, chaque action, et permettent à un opérateur humain de corriger, d’ajuster, de valider ?</p>
<p>De mon point de vue, l’autonomie totale n’est pas pour tout de suite, sauf pour des tâches très connues, très maîtrisées et déjà bien balisées. En revanche, je pense que le principe du <em>Human-in-the-Loop</em> que l’on retrouve dans les agents de codage va se diffuser très rapidement dans d’autres secteurs.</p>
<p>Qu’est-ce qui caractérise les agents HITL ?</p>
<p>Les agents peuvent agir sur leur environnement. Via des sorties structurées et l’appel de fonctions, ils proposent des actions concrètes. En leur laissant itérer sur les conséquences de leurs propres actions, on leur donne une forme d’autonomie partielle.</p>
<p>C’est exactement ce que font les agents de codage. La vraie question est donc : pourquoi réserver ce type d’interaction au développement logiciel ? Pourquoi ne pas proposer les mêmes patterns pour d’autres métiers ?</p>
<p>Un project manager pourrait utiliser un agent pour organiser des tâches, planifier des dépendances, préparer des communications ou déclencher des mails en fonction des délais. Évidemment, je ne laisserais pas un agent faire tout seul. Il ne comprendra pas tous les aspects subtils du métier.</p>
<p>Et c’est précisément là que l’humain entre dans la boucle. Le <em>Human-in-the-Loop</em> ne consiste pas à brider l’agent. Il s’agit de concevoir une expérience dans laquelle l’agent pousse ses capacités au maximum, mais dans un cadre contrôlé, sous la supervision de l’humain pour lequel il travaille. </p>
<p>Ces systèmes ont, selon moi, une caractéristique clé : ils permettent à l’agent de prendre des risques sans s’engager. Autrement dit, l’agent doit disposer d’un espace de travail sans impact tant que l’humain n’a pas validé le résultat. Dans le monde du code, cela se matérialise très bien par une merge request : l’agent propose, le développeur décide. Pour un project manager, cela pourrait être la génération d’un graphe de dépendances, avec des tâches et des échéances, qui ne devient réel qu’une fois validé et partagé.</p>
<p>Je pense que cette étape est fondamentale. Le Human-in-the-Loop n’est pas l’opposé de l’autonomie : c’en est un préalable.</p>
<p>Les agents progresseront vers plus d’autonomie grâce à deux facteurs principaux :</p>
<ul>
<li>la confiance progressive des opérateurs dans les actions proposées, qui justifie l’investissement dans ces systèmes ;</li>
<li>l’accumulation de données issues d’usages réels, permettant d’améliorer continuellement les agents sur des tâches ciblées.</li>
</ul>
<p>Je pense que nous sommes aujourd’hui suffisamment matures pour commencer à développer ce type de solutions.</p>
<p>J’en ai fait l’expérience avec <a href="https://ez-dashboard.com">ez-dashboard</a>, un projet sur lequel j’ai développé un agent capable d’opérer directement dans le navigateur de l’utilisateur. Travailler à ce niveau d’intégration m’a convaincu que ce modèle est applicable bien au-delà du développement logiciel.</p>
<p>Je suis convaincu que 2026 verra émerger des exemples très concrets d’interactions homme–agent réellement utiles. Moins spectaculaires que les promesses d’autonomie totale, mais beaucoup plus crédibles et adoptables.</p>
]]></content:encoded>
            <enclosure url="https://writizzy.b-cdn.net/blogs/eacaab44-eff6-4bea-ba44-f9d0c39c7aa6/1765738106534-czw835k.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Déployer un MCP sécurisé en entreprise]]></title>
            <link>https://amrltqt.com/p/2025-11-01-secure-context-sharing-with-mcp-1b1a464e59c8</link>
            <guid>https://amrltqt.com/p/2025-11-01-secure-context-sharing-with-mcp-1b1a464e59c8</guid>
            <pubDate>Sat, 01 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[On parle beaucoup de MCP, mais en pratique à part pour les outils génériques comme la suite atlassian ou Slack on préfère l'éviter. ]]></description>
            <content:encoded><![CDATA[<p>On parle beaucoup de MCP, mais en entreprise à part pour connecter des services génériques genre la suite Atlassian, drive, slack, etc.. c&#39;est finalement pas vraiment adopté.</p>
<p>Développer des applications d&#39;entreprise ça nécessite d&#39;échanger des informations sensibles qu&#39;on ne souhaite pas transmettre au modèle de langage.</p>
<p>MCP est vu comme l&#39;usb-c du monde agentique et c&#39;est très cool, mais comment on fait quand on a besoin de passer des tokens, des informations utilisateurs et que ces informations ne doivent pas passer par le modèle de langage?</p>
<p>Si des informations sensibles transitent par le modèle de langage, alors il y a un risque qu&#39;un utilisateur mal intentionné puisse contourner toutes les instructions et éventuellement les modifier. Et ça c&#39;est pas génial.</p>
<p>Dans la suite de l&#39;article on va montrer une approche avec un client utilisant OpenAI agent SDK et un serveur fastmcp. Le client et le serveur MCP tournent en local, mais dans l&#39;idée, les deux tournent dans un environment que vous maîtrisez (pas sur le poste de l&#39;utilisateur).</p>
<h3>Un peu de contexte.</h3>
<p>Je travaille pour une marketplace, je veux aider nos clients à retrouver leur commande. J&#39;ai des APIs pour ça et j&#39;aimerais bien que mon agent puisse gérer des questions comme &quot;Où en est ma commande #123 ?&quot;.</p>
<p>Un agent est capable de comprendre la question, de détecter le numéro de commande et d&#39;appeler un outil MCP pour renvoyer les informations.</p>
<p>Voyons un peu de code tout cru. On retrouve côté serveur quelque chose comme ça:</p>
<pre><code>ORDERS = [
    {
        &quot;id&quot;: &quot;#12345&quot;,
        &quot;user_id&quot;: &quot;user-123&quot;,
        &quot;status&quot;: &quot;shipped&quot;,
        &quot;customer&quot;: &quot;Jean Dupont&quot;,
        &quot;address&quot;: &quot;12 rue de la soif, Toulouse, France&quot;
    },
    {
        &quot;id&quot;: &quot;#67890&quot;,
        &quot;user_id&quot;: &quot;user-456&quot;,
        &quot;status&quot;: &quot;delivered&quot;,
        &quot;customer&quot;: &quot;Jeanne Martin&quot;,
        &quot;address&quot;: &quot;46 avenue de la joie, Bordeaux, France&quot;
    }
]

@mcp.tool
def find_order(order_id: str) -&gt; str:
    for order in ORDERS:
        if order[&quot;id&quot;] == order_id:
            return f&quot;Order {order_id} is {order[&#39;status&#39;]} to {order[&#39;customer&#39;]} at {order[&#39;address&#39;]}&quot;
    return f&quot;Order {order_id} not found&quot;
</code></pre>
<p>Et côté client quelque chose comme ça:</p>
<pre><code>USER = &quot;user-123&quot;

async with MCPServerStreamableHttp(
    name=&quot;Streamable HTTP Python Server&quot;,
    params={
        &quot;url&quot;: &quot;http://localhost:8000/mcp&quot;,
        &quot;timeout&quot;: 10,
    },
) as server:
    agent = Agent(
        name=&quot;My Agent&quot;,
        instructions=&quot;You&#39;re a customer support agent, use your tools to find the order status.&quot;,
        mcp_servers=[server],
        model_settings=ModelSettings(tool_choice=&quot;required&quot;),
        model=&quot;gpt-4.1-mini&quot;
    )

    result = await Runner.run(agent, &quot;Where is my order #67890&quot;)
    print(result.final_output)py
</code></pre>
<p><em>Si vous avez une galère pour comprendre ce code ou des interrogation sur comment démarrer, mettez un commentaire sous l&#39;article.</em></p>
<p>Si vous utilisez votre agent avec un serveur MCP en local vous verrez que ça va bien marcher, mais peut être un peu trop car qu&#39;importe si la commande est lié à l&#39;utilisateur du client. Aucun contrôle n&#39;est fait côté serveur pour le moment.</p>
<h3>Les options</h3>
<p>Plusieurs pistes sont envisageables.</p>
<ul>
<li>Utiliser les query params d&#39;abord lors de la définition du <em>MCPServerStreamableHTTP</em> et récupérer les informations côté serveur en inspectant l&#39;url*.*</li>
<li>Créer les paramètres côté serveur et les réécrire à la volée en utilisant un tool côté client qui s&#39;occupe d&#39;initialiser et d&#39;appeler MCP lui même.</li>
<li>Signer un token JWT, passer les informations en claims et utiliser le header authorization pour passer les infos.</li>
</ul>
<p>Le premier c&#39;est ok, mais pas hyper securisé. Le second c&#39;est totalement contreproductif, pourquoi utiliser MCP si on part là dessus. Le troisième si le contexte à partager est important c&#39;est pas génial, mais c&#39;est securisé et plutôt bien supporté. On part là dessus!</p>
<h3>Correction du code serveur</h3>
<p>On va utiliser un code <a href="https://fr.wikipedia.org/wiki/HMAC">HMAC</a> pour sécuriser la transmission des informations. Pour ça côté serveur on va simplement réutiliser ce que fastmcp propose à savoir un JWTVerfier.</p>
<pre><code>SECRET = os.getenv(&quot;JWT_SECRET&quot;, &quot;default-secret-key-change-me&quot;)

verifier = JWTVerifier(
    public_key=SECRET,
    issuer=&quot;order-agent&quot;,
    audience=&quot;order-api&quot;,
    algorithm=&quot;HS256&quot;
)

mcp = FastMCP(&quot;MCP Order Server&quot;, auth=verifier)
</code></pre>
<p>Et on modifie le tool pour tirer partie de l&#39;access_token qui va être décodé par le verifier. A chaque appel, ce middleware va vérifier qu&#39;il y a bien un <a href="https://fr.wikipedia.org/wiki/JSON_Web_Token">JWT</a> transmis, vérifier qu&#39;il est fiable et transmettre les infos pour que les handlers puisse y accéder.</p>
<p>On va donc aussi modifier le code du tool MCP pour vérifier que l&#39;utilisateur est bien fourni.</p>
<pre><code>@mcp.tool
def find_order(order_id: str) -&gt; str:
    access_token = get_access_token()
    if not access_token:
        raise ValueError(&quot;Access token is missing&quot;)

    claims = access_token.claims
    for order in ORDERS:
        if order[&quot;id&quot;] == order_id and claims[&quot;sub&quot;] == order[&quot;user_id&quot;]:
            return f&quot;Order {order_id} is {order[&#39;status&#39;]} to {order[&#39;customer&#39;]} at {order[&#39;address&#39;]}&quot;
    return f&quot;Order {order_id} not found&quot;
</code></pre>
<p>A noter que les informations qu&#39;on veut transmettre sont dans les claims et qu&#39;on peut maintenant faire confiance au client. C&#39;est la clé secrete, qu&#39;il faut partager secrètement avec le client, qui garantie que les informations transmises sont fiables.</p>
<h3>Correction du client</h3>
<p>Côté client on va simplement chiffrer les informations que l&#39;on veut transmettre. Ici on va utiliser le user_id qu&#39;on va mettre dans le champ sub vu qu&#39;on soumet la demande pour cet utilisateur.</p>
<pre><code>SECRET = os.getenv(&quot;JWT_SECRET&quot;, &quot;default-secret-key-change-me&quot;)
USER = &quot;user-123&quot;

payload = {
    &quot;sub&quot;: USER,
    &quot;exp&quot;: datetime.datetime.now(datetime.UTC) + datetime.timedelta(hours=1),
    &quot;aud&quot;: &quot;order-api&quot;,
    &quot;iss&quot;: &quot;order-agent&quot;
}

token = jwt.encode(payload, SECRET, algorithm=&quot;HS256&quot;)
</code></pre>
<p>Ce token qu&#39;on a créé, on va simplement le passer en header de l&#39;appel HTTP.</p>
<pre><code>async with MCPServerStreamableHttp(
    name=&quot;Streamable HTTP Python Server&quot;,
    params={
        &quot;url&quot;: &quot;http://localhost:8000/mcp&quot;,
        &quot;timeout&quot;: 10,
        &quot;headers&quot;: {&quot;Authorization&quot;: f&quot;Bearer {token}&quot;}
    },
)
</code></pre>
<p>Et voilà, c&#39;est tout.</p>
<h3>Conclusion</h3>
<p>Le design est ok, c&#39;est pas foufou car on doit chiffrer un token à chaque appel, si on veut faire de la réutilisation on peut aussi mais ça demande un peu plus de logique.</p>
<ol>
<li>Par contre c&#39;est complètement stateless, il suffit de partager la clé secrète par variable d&#39;environnement et on n&#39;a aucune autre dépendance. Pas de session, rien.</li>
<li>On peut embarquer le context utilisateur dans les claims au-delà du sub, très pratique si vous avez des informations qui ne sont pas forcément sensible mais qui n&#39;ont pas d&#39;intérêt à être transmis dans le contexte du modèle de langage comme des rôles ou des préférences utilisateur.</li>
<li>FastMCP fait le job, très peu de lignes de code.</li>
</ol>
<p>Alors, c&#39;est pas différent de ce qu&#39;on peut faire en SOA, donc c&#39;est pas très novateur. Par contre, maintenant vous avez une solution si vous avez besoin de transmettre des informations de contexte entre votre client et un serveur MCP!</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*ny7eNqRGb9teK2Bvayy3wA.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[[Veille Technique] Rust / Python interop]]></title>
            <link>https://amrltqt.com/p/2025-10-19-veille-technique-rust-python-interop-1617b09229fe</link>
            <guid>https://amrltqt.com/p/2025-10-19-veille-technique-rust-python-interop-1617b09229fe</guid>
            <pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Je ne me suis pas entraîné depuis un moment. La tête dans le guidon, j'ai mis la veille technique de côté un peu trop longtemps à mon goût.

*** ** * ** ***

### \[Veille Technique\] Rust / Python int...]]></description>
            <content:encoded><![CDATA[<p>Je ne me suis pas entraîné depuis un moment. La tête dans le guidon, j&#39;ai mis la veille technique de côté un peu trop longtemps à mon goût.</p>
<hr>
<h3>[Veille Technique] Rust / Python interop</h3>
<p>Je ne me suis pas entraîné depuis un moment. La tête dans le guidon, j&#39;ai mis la veille technique de côté un peu trop longtemps à mon goût.</p>
<p>En lisant le code de <a href="https://github.com/karpathy/nanochat">nanochat</a> de Karpathy, je me suis rendu compte que j&#39;étais un peu largué. Rien de mieux qu&#39;aller se casser les dents sur projet pour mettre les idées dans le bon ordre.</p>
<p>Cet article va résumer une partie de mes sur l&#39;interopérabilité Rust vers Python. Il y a 10 ans j&#39;ai fait la même chose en C vers Python, c&#39;était fun, voyons un peu comment les choses ont évolués depuis.</p>
<p>La méthode employée est un classique, un terminal, un éditeur et je fonce. Ici je vais essayer une autre approche, je vais vous dire tout ce qui s&#39;est passé, plutôt que vous donner une recette toute faite.</p>
<h3>Mise en place</h3>
<p>Je vais faire des prédictions de séries temporelles. Je vais démarrer en python, gérer l&#39;entraînement et les prédictions dans un bout de code rust et les servir en python.</p>
<p>L&#39;environnement :</p>
<ul>
<li>Python 3.13.7</li>
<li>uv 0.8.13</li>
<li>rustc 1.89.0</li>
</ul>
<p>J&#39;ai créé un dossier <em>pyseries</em> et initialisé un projet avec <em>uv init</em> et directement dans le repertoire j&#39;ai créé un projet rust avec <em>cargo new pyseries-rs.</em></p>
<h3>Découverte de pyo3</h3>
<p>Si j&#39;en suis là, c&#39;est parce qu&#39;en lisant nanochat j&#39;ai découvert pyo3 et je me suis dit que c&#39;était génial. Karpathy l&#39;utilise pour son tokenizer et en un coup d&#39;oeil on distingue très bien comment sont décrit les modules, les fonctions etc. Donc on va l&#39;essayer et voir ce que ça donne.</p>
<p>Si vous n&#39;avez pas envie de lire cet article et d&#39;aller vite, il suffit de lire la <a href="https://pyo3.rs/v0.26.0/">doc</a> de pyo3. Je crois quand même que c&#39;est bien de voir ce qui s&#39;est passé de mon côté. Vous allez gagner de précieuses heures de mise en place.</p>
<p>Quand on fait un cargo new pyseries-rs, on crée un template pour un exécutable et pas une lib. Donc je vais ajouter les dépendances pour pyo3 et faire une lib.</p>
<p>En regardant la doc je découvre qu&#39;on peut faire varier le crate-type pour définir une <a href="https://doc.rust-lang.org/reference/linkage.html#r-link.cdylib">*cdylib*</a>*.*Le nom est cryptique mais il signifie que c&#39;est une bibliothèque de liens dynamique (les fameuses .dll sur windows ou .so sous linux).</p>
<pre><code>[package]
name = &quot;pyseries-rs&quot;
version = &quot;0.1.0&quot;
edition = &quot;2024&quot;

[lib]
name = &quot;pyseries-rs&quot;
crate-type = [&quot;cdylib&quot;]

[dependencies]
pyo3 = { version = &quot;0.26.0&quot;, features = [&quot;extension-module&quot;] }
</code></pre>
<p>Il y a des pièges ici, je vous en reparle plus loin.</p>
<h3>Un peu de code maintenant</h3>
<p>On va reprendre bêtement (deuxième piège) celui de la doc et faire un round de compilation.</p>
<p>Dans mon lib.rs je glisse l&#39;exemple.</p>
<pre><code>use pyo3::prelude::*;

#[pyfunction]
fn sum_as_string(a: usize, b: usize) -&gt; PyResult&lt;String&gt; {
    Ok((a + b).to_string())
}

#[pymodule]
fn string_sum(m: &amp;Bound&lt;&#39;_, PyModule&gt;) -&gt; PyResult&lt;()&gt; {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}
</code></pre>
<p>On reconnaît des choses intéressantes. D&#39;abord les annotations <em>pyfunction</em> et <em>pymodule</em>semblent indiquer qu&#39;on décrit des artefacts python.</p>
<p>Si on regarde de plus près on voit que la fonction sum_as_string est classique, à part qu&#39;elle retourne un type PyResult.</p>
<p>La deuxième fonction est un peu plus déroutante. Elle reçoit une référence de module en entrée et on lui ajoute la fonction annotée pyfunction. Il y a une macro wrap_pyfunction! au milieu qui doit cacher la complexité du binding.</p>
<h3>Bonjour Maturin</h3>
<p>En avançant dans la doc je retrouve <em>maturin</em> , que j&#39;avais vu rapidement dans le <em>pyproject.toml</em> de nanochat. Je comprend maintenant que c&#39;est un outil de build pour aider à la mise en oeuvre de la compilation, des liens et de toute la magie noire pour rendre python et rust interopérables.</p>
<p>On l&#39;installe et j&#39;initialise le projet</p>
<pre><code>uv add maturin
uv run maturin init
</code></pre>
<p>Et c&#39;est l&#39;échec (on va en avoir quelques uns, c&#39;est le but de l&#39;article)</p>
<pre><code>💥 maturin failed
  Caused by: `maturin init` cannot be run on existing projects
</code></pre>
<p>Notre projet existe déjà, si je redémarre un projet dans le futur je commence avec <a href="https://github.com/PyO3/maturin">maturin</a>. Je vais continuer en manuel en attendant.</p>
<p>Dans le <em>pyproject.toml</em>je vais rajouter le build-system (inspiré de nanochat)</p>
<pre><code>[build-system]
requires = [&quot;maturin&gt;=1.7,&lt;2.0&quot;]
build-backend = &quot;maturin&quot;

[tool.maturin]
module-name = &quot;pyseries_rs&quot;
bindings = &quot;pyo3&quot;
python-source = &quot;.&quot;
manifest-path = &quot;pyseries-rs/Cargo.toml&quot;
</code></pre>
<p>Alors, plusieurs choses ici, car j&#39;ai fais ça progressivement.</p>
<ul>
<li>Si on ne met pas la configuration <em>[tool.maturin]</em> on va pas forcément bien cibler le <em>Cargo.toml</em>.</li>
<li>En python les modules n&#39;ont pas de tiret, j&#39;ai donc mis un <em>pyseries_rs</em> et c&#39;est le début de la catastrophe. Vous allez voir.</li>
</ul>
<p>Ma configuration au point je vais utiliser la commande <em>uv run maturin develop</em> . Au bout de quelques essais pour produire le <em>pyproject.toml</em> ci-dessus j&#39;obtiens une compilation</p>
<pre><code>Built pyseries @ file:///Users/quentin/code/pyseries
Installed 1 package in 1ms
🔗 Found pyo3 bindings
🐍 Found CPython 3.12 at /Users/quentin/code/pyseries/.venv/bin/python
📡 Using build options bindings from pyproject.toml
Audited 1 package in 1ms
   Compiling pyo3-build-config v0.26.0
   Compiling pyo3-ffi v0.26.0
   Compiling pyo3-macros-backend v0.26.0
   Compiling pyo3 v0.26.0
   Compiling pyo3-macros v0.26.0
   Compiling pyseries_rs v0.1.0 (/Users/quentin/code/pyseries/pyseries_rs)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.98s
⚠️  Warning: Couldn&#39;t find the symbol `PyInit_pyseries_rs` in the native library. Python will fail to import this module. If you&#39;re using pyo3, check that `#[pymodule]` uses `pyseries_rs` as module name
📦 Built wheel for CPython 3.12 to /var/folders/ld/mzw0q0c541q94y5hpzttqqhm0000gn/T/.tmpi8eC3a/pyseries-0.1.0-cp312-cp312-macosx_11_0_arm64.whl
✏️ Setting installed package as editable
🛠 Installed pyseries-0.1.0
</code></pre>
<p>Plein d&#39;info ici:</p>
<ul>
<li>J&#39;ai du python 3.12 au lieu de 3.13, ça vient d&#39;uv et de mes contraintes, tant mieux.</li>
<li>Le code rust à été compilé dans l&#39;opération</li>
<li>Le package rust créé une wheel qui est immédiatement installée comme editable dans le projet.</li>
<li>Et : ⚠️ Warning: Couldn&#39;t find the symbol `PyInit_pyseries_rs` in the native library. Python will fail to import this module. If you&#39;re using pyo3, check that `#[pymodule]` uses `pyseries_rs` as module name</li>
</ul>
<p>L&#39;alerte est préoccupante, je comprend que le nom de la fonction annotée*#[pymodule]* est importante. Il me faut donc la renommer. J&#39;ai aussi renommé tout côté<em>Cargo.toml</em> pour être 100% sur de pas avoir de problème avec les tiret.</p>
<pre><code>#[pymodule]
fn pyseries_rs(m: &amp;Bound&lt;&#39;_, PyModule&gt;) -&gt; PyResult&lt;()&gt; {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}
</code></pre>
<p>On recompile avec <em>uv run maturin develop</em>. Plus d&#39;alerte, comportement nominal.</p>
<h3>Test final</h3>
<p>On va simplement utiliser le REPL de l&#39;installation python de l&#39;environnement virtuel.</p>
<pre><code>uv run python
&gt;&gt;&gt; import pyseries_rs
&gt;&gt;&gt; pyseries_rs.sum_as_string(1, 2)
&#39;3&#39;
</code></pre>
<p>Ça marche ! Maturin nous a fait une wheel python, l&#39;a installé le module dans l&#39;environnement virtuel d&#39;uv. Si je relance <em>uv run maturin develop</em>le module rust est mis à jour.</p>
<h3>Conclusion</h3>
<p>C&#39;est génial, je ne m&#39;attendais pas à ce que ce soit aussi peu contraignant de démarrer un projet comme ça. J&#39;ai souvenir qu&#39;il fallait faire plus d&#39;effort pour obtenir un résultat similaire en C. Des efforts surtout liés au build et à la configuration de la librairie pour faire un wheel.</p>
<p>Au moment où j&#39;écris, je n&#39;ai pas entamé la suite, la librairie pour faire des prédictions. J&#39;espère pouvoir proposer la suite rapidement.
<img src="https://cdn-images-1.medium.com/max/800/1*ZfhMMKNzBpmXVW5X805h6Q.png" alt="" /> GPT generated image</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*ZfhMMKNzBpmXVW5X805h6Q.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Agents IA : éliminez les informations superflues, gagnez en pertinence]]></title>
            <link>https://amrltqt.com/p/2025-07-13-agents-ia-liminez-les-informations-superflues-gagnez-en-pertinence-1558f0a4cd71</link>
            <guid>https://amrltqt.com/p/2025-07-13-agents-ia-liminez-les-informations-superflues-gagnez-en-pertinence-1558f0a4cd71</guid>
            <pubDate>Sun, 13 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Le context est la clé de la performance des modèles de langage dans la production de réponse pertinente. Comment en tirer parti au maximum?]]></description>
            <content:encoded><![CDATA[<p>Les modèles de raisonnement sont de plus en plus efficaces ; mais si les éléments transmis dans le contexte des modèles de langage sont de faible qualité, les réponses s&#39;en ressentiront.</p>
<p>Mon hypothèse, c&#39;est que, quelle que soit la capacité du modèle, si la qualité du contexte est faible (imprécis, faux ou à côté de la plaque...), alors la réponse ne sera pas satisfaisante.</p>
<p>C&#39;est pourquoi travailler sur le prompt et utiliser un modèle de raisonnement puissant n&#39;est que le début de l&#39;histoire quand on veut améliorer la qualité des agents. Il faut bien évidemment ne laisser aucune marge d&#39;erreur dans la récupération d&#39;informations, que ce soit via des techniques de RAG ou, plus simplement, via les informations transmises par les outils.</p>
<p>Ce qu&#39;on appelle  les outils , ce sont des programmes que le modèle de langage demande à son programme hôte d&#39;exécuter pour obtenir des informations, effectuer un calcul ou appliquer une modification.</p>
<p>Lorsqu&#39;on construit un outil, la majorité d&#39;entre nous (développeurs) se contentent, par exemple, d&#39;envoyer aux agents le JSON brut des API en espérant que le modèle de langage devinera le sens de chaque champ. Ça fonctionne... jusqu&#39;à ce que les imperfections qui ruinent l&#39;expérience apparaissent.</p>
<p>Prenons un exemple : j&#39;ai un outil qui me permet de récupérer le statut d&#39;une commande depuis une API.</p>
<p>Une réponse typique de cette API est la suivante :</p>
<pre><code>{
   &quot;internal_id&quot;: &quot;1234&quot;,
   &quot;customer_reference&quot;: &quot;#ORD-154-15&quot;,
   &quot;status&quot;: &quot;PAID&quot;,
   &quot;delivery_date&quot;: &quot;2025-07-15&quot;,
   &quot;risk_refund&quot;: &quot;HIGH&quot;
}
</code></pre>
<p>Imaginons qu&#39;une requête soit :  Quand est-ce que mon colis va être livré ?  et qu&#39;on se serve de cette API pour répondre. Ça signifie qu&#39;on va injecter cette réponse dans le modèle.</p>
<p>Premier problème : il y a des informations qu&#39;on ne veut absolument pas montrer au client. Or tout ce qui est ajouté dans le contexte du modèle de langage est accessible par le client, y compris des informations sensibles comme risk_refund, et éventuellement le statut ou l&#39;identifiant internal_id. Le champ risk_refund pose un problème de sécurité, et l&#39;identifiant interne doit rester interne.</p>
<p>On peut commencer par supprimer ces informations pour obtenir une réponse qui ne transmet plus de données sensibles :</p>
<pre><code>{
   &quot;customer_reference&quot;: &quot;#ORD-154-15&quot;,
   &quot;status&quot;: &quot;PAID&quot;,
   &quot;delivery_date&quot;: &quot;2025-07-15&quot;,
}
</code></pre>
<p>Maintenant, il faut garder à l&#39;esprit qu&#39;on échange avec un modèle de langage. Son interface, c&#39;est du texte : ce texte sera utilisé pour former des tokens, lesquels serviront à générer la suite de la réponse. Pour mettre toutes les chances de mon côté, je vais donc reformater la sortie pour fournir une explication textuelle de la situation de la commande :</p>
<pre><code>Commande #ORD-154-15, payée, sera livrée le 15 Juillet 2025.
</code></pre>
<p>Ce changement est important : transformer le document JSON en texte ajoute du contexte, du vocabulaire et un ton. Avec un template Jinja, il est assez simple de transformer la sortie JSON.</p>
<p>Exemple:</p>
<pre><code>{# order_summary.j2 #}
{% set status_map = {
    &quot;PENDING&quot;: &quot;en attente de paiement&quot;,
    &quot;PAID&quot;:    &quot;payée&quot;,
    &quot;CANCEL&quot;:  &quot;annulée&quot;
} %}

{% set date_parts = order.delivery_date.split(&#39;-&#39;) %}
{% set mois_fr = [&quot;&quot;, &quot;janvier&quot;, &quot;février&quot;, &quot;mars&quot;, &quot;avril&quot;, &quot;mai&quot;, &quot;juin&quot;, &quot;juillet&quot;, &quot;août&quot;, &quot;septembre&quot;, &quot;octobre&quot;, &quot;novembre&quot;, &quot;décembre&quot;] %}
Commande {{ order.customer_reference }}, {{ status_map.get(order.status, order.status) }}, sera livrée le {{ date_parts[2]|int }} {{ mois_fr[date_parts[1]|int] }} {{ date_parts[0] }}.
</code></pre>
<p>Arrive ensuite la spécialisation : en fonction du statut, je vais transmettre les informations de manière différente.</p>
<p>Quelques exemples :</p>
<blockquote>
<p>*Commande #ORD-154--15, en attente de paiement.*Les informations de livraison seront fournies après réception du paiement.
*Commande #ORD-154--15, payée.*Votre colis est en cours de préparation et sera expédié dans les 24 h.
*Commande #ORD-154--15, annulée.*Aucun débit n&#39;a été effectué et la commande est maintenant clôturée.
*Commande #ORD-154--15, en attente de paiement.*Les informations de livraison seront fournies suite au paiement.</p>
</blockquote>
<p>Avec cette méthode, le contexte est transmis sans information superflue, et on peut s&#39;attendre à des réponses précises même avec les modèles de langage les moins performants. En revanche, cela demande plus d&#39;énergie : il faut concevoir une couche de présentation des données textuelles spécialement pour les modèles de langage.</p>
<p>Si vous investissez massivement dans les agents, cette manière d&#39;exposer vos données est certainement un aspect clé de votre investissement.</p>
]]></content:encoded>
            <category>agents</category>
            <category>ia</category>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*4iFmfHbz3TuszDqpFF_lOw.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Le contrôle d’accès et les agents]]></title>
            <link>https://amrltqt.com/p/2025-05-18-le-contr-le-d-acc-s-et-les-agents-fb86a02e0f16</link>
            <guid>https://amrltqt.com/p/2025-05-18-le-contr-le-d-acc-s-et-les-agents-fb86a02e0f16</guid>
            <pubDate>Sun, 18 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Considérant que les agents sont des systèmes utilisant des modèles de langage pouvant invoquer des programmes en autonomie, à un moment...

*** ** * ** ***

### Le contrôle d'accès et les agents

Cons...]]></description>
            <content:encoded><![CDATA[<p>Considérant que les agents sont des systèmes utilisant des modèles de langage pouvant invoquer des programmes en autonomie, à un moment...</p>
<hr>
<h3>Le contrôle d&#39;accès et les agents</h3>
<p>Considérant que les agents sont des systèmes utilisant des modèles de langage pouvant invoquer des programmes en autonomie, à un moment donné on souhaite les laisser accéder à des systèmes dont l&#39;accès est limité.</p>
<p>La vaste majorité des logiciels en ligne sont soumis à des contrôles pour vérifier que les utilisateurs qui s&#39;y connectent ont bien le droit de consommer ou modifier leurs propres ressources.</p>
<p>Comment permettre à des agents d&#39;accéder à ces ressources sans risques pour la sécurité?</p>
<p>Cet article ne va pas vous donner toutes les clés pour y arriver mais quelques bases pour démarrer et progresser sur le sujet.</p>
<p>Comme je travaille pour une marketplace en ligne, on va utiliser un petit exemple sur le thème des commandes</p>
<pre><code>from demo.models import Order, User

USERS = [
    User(id=0, email=&quot;alpha@demo.com&quot;),
    User(id=1, email=&quot;beta@demo.com&quot;),
]

ORDERS = [
    Order(id=0, user_id=0, model=&quot;battery&quot;, quantity=4, price_cts=7499),
    Order(id=1, user_id=1, model=&quot;shampoo&quot;, quantity=1, price_cts=1000),
]

class MarketplaceService:
    def __init__(self, trusted_user_id: int):
        self.users = USERS
        self.orders = ORDERS

        if not self.get_user(trusted_user_id):
            raise ValueError(&quot;User does not exists&quot;)

        self.trusted_user_id = trusted_user_id

    def get_order(self, id: int) -&gt; Order:
        order = next(filter(lambda o: o.id == id and o.user_id == self.trusted_user_id, self.orders), None)
        if not order:
            raise ValueError(&quot;Order not found&quot;)
        return order

    def get_user(self, id: int) -&gt; User:
        user = next(filter(lambda u: u.id == id, self.users), None)
        if not user:
            raise ValueError(&quot;User id not found&quot;)
        return user
</code></pre>
<p>On va créer un agent pour gérer l&#39;accès à cette donnée en langage naturel. De nombreux frameworks existent déjà, pour vous dire à l&#39;heure où j&#39;écris, aws vient d&#39;en sortir un dans la même veine.</p>
<p>De mon côté après avoir créé ma propre interprétation basée sur le papier <a href="https://arxiv.org/pdf/2210.03629">REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS</a> j&#39;ai fini par me retourner sur l&#39;implémentation proposée par OpenAI dans son <em><a href="https://openai.github.io/openai-agents-python/">agents-sdk</a></em>.</p>
<pre><code>from textwrap import dedent

from agents import Agent
from demo.tools import find_order_by_id
from demo.models import DemoContext

agent = Agent[DemoContext](
    name=&quot;orders&quot;,
    instructions=dedent(
        &quot;&quot;&quot;
            Help the user to find their order information in the database.
            Tool is available to help you retrieve the data.
        &quot;&quot;&quot;
    ),
    model=&quot;gpt-4.1-nano&quot;,
    tools=[
        find_order_by_id
    ]
)
</code></pre>
<p>Pour sécuriser l&#39;accès, il faut faire attention à la fonction déclarée dans tools. Elle permet de lire le contenu d&#39;une commande via son identifiant. On peut imaginer d&#39;autres manières d&#39;accéder aux données, l&#39;exemple sera toujours pertinent.</p>
<p>Vous noterez que l&#39;agent prend en charge un *Context.*C&#39;est un objet qui vit dans l&#39;interpréteur python et qui ne sera pas partagé avec le modèle de langage.</p>
<p>Vous pouvez y attacher des informations sensibles et maîtriser sa mise à jour à deux moments:</p>
<ul>
<li>à l&#39;initialisation lorsque l&#39;agent est instancié</li>
<li>dans les fonctions appelée par les modèles de langage via le<em>function calling</em></li>
</ul>
<p>C&#39;est là que nous allons intervenir. Nous voulons sécuriser que l&#39;utilisateur connecté ne puisse accéder qu&#39;a ses informations. Ce que supporte le service décrit plus haut.</p>
<p>Pour transmettre au service l&#39;identifiant de l&#39;utilisateur, nous utiliserons le <em>Context</em> via un champ <em>trusted_user_id</em>qui sera disponible dans chaque fonctions appelée par l&#39;agent. Pour ça il faut inspecter la description de la fonction appelée.</p>
<pre><code>from textwrap import dedent
from agents import function_tool, RunContextWrapper

from demo.service import MarketplaceService
from demo.models import DemoContext

def get_service(trusted_user_id: int) -&gt; MarketplaceService:
    return MarketplaceService(trusted_user_id)


@function_tool
async def find_order_by_id(wrapper: RunContextWrapper[DemoContext], id: int) -&gt; str:
    &quot;&quot;&quot;
    Find an order given its integer identifier

    Args:
        id (int): integer identifier of an order

    Returns:
        str: a representation of the order for the agent
    &quot;&quot;&quot;

    user_id = wrapper.context.trusted_user_id
    service = get_service(user_id)

    try:
        order = service.get_order(id)
        price = round(order.price_cts / 100, 2)
        return dedent(
            f&quot;&quot;&quot;
                Order ID: # {order.id}
                Model: {order.model}
                Price: {price}
            &quot;&quot;&quot;
        )
    except:
        return &quot;Impossible to retrieve the order&quot;
</code></pre>
<p>Vous pouvez voir que l&#39;identifiant de l&#39;utilisateur est extrait du <em>Context</em> et qu&#39;il sert à récupérer le services en l&#39;initialisant à la volée. Rien n&#39;empêche de le récupérer autrement si nécessaire.</p>
<p>La fonction construite ainsi garantie qu&#39;il est impossible de récupérer via le service les commandes d&#39;un autre utilisateur. Qu&#39;importent les techniques de <em>jailbreak</em> qui pourraient être utilisées par un utilisateur malveillant.</p>
<p>L&#39;identifiant de l&#39;utilisateur utilisé pour l&#39;accès contrôlé n&#39;est <strong>jamais</strong>manipulé par le modèle de langage.</p>
<p>Pour mettre en musique les exemples ci-dessus vous pouvez utiliser cette fonction.</p>
<pre><code>async def main(user_id: int):
    context = DemoContext(
        trusted_user_id=user_id
    )
    answer_1 = await Runner.run(
        starting_agent=agent,
        input=&quot;Give me the order 0&quot;,
        context=context
    )

    print(answer_1.final_output)

    answer_2 = await Runner.run(
        starting_agent=agent,
        input=&quot;Give me the order 1&quot;,
        context=context
    )

    print(answer_2.final_output)
</code></pre>
<p>Je laisse à vos soins le fait d&#39;étendre cette approche. Si vous voulez retrouver le code utilisé dans cette démonstration vous pouvez le retrouver sur mon compte <a href="https://github.com/amrltqt/agent-sdk-secure-access">Github</a>.
 Secure data access with openai agents-sdk</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*8ynWLdm2Yl0CfoXgaFFasw.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Retour d’expérience sur l’escalade avec des LLM : tests et ajustements]]></title>
            <link>https://amrltqt.com/p/2024-12-13-retour-d-exp-rience-sur-l-escalade-avec-des-llm-tests-et-ajustements-70de23e9e672</link>
            <guid>https://amrltqt.com/p/2024-12-13-retour-d-exp-rience-sur-l-escalade-avec-des-llm-tests-et-ajustements-70de23e9e672</guid>
            <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Cette semaine, j'ai travaillé sur des agents de réponse automatique pour répondre à des questions générales sur nos services et les...

*** ** * ** ***

### **Retour d'expérience sur l'escalade avec d...]]></description>
            <content:encoded><![CDATA[<p>Cette semaine, j&#39;ai travaillé sur des agents de réponse automatique pour répondre à des questions générales sur nos services et les...</p>
<hr>
<h3><strong>Retour d&#39;expérience sur l&#39;escalade avec des LLM : tests et ajustements</strong></h3>
<p>Cette semaine, j&#39;ai travaillé sur des agents de réponse automatique pour répondre à des questions générales sur nos services et les problèmes rencontrés dans l&#39;entreprise. L&#39;objectif est de rendre possible l&#39;accès à de l&#39;information en instantané sans pour autant prendre le risque de raconter n&#39;importe quoi.</p>
<p>C&#39;est dans ce cadre que j&#39;ai développé un mécanisme d&#39;escalade. C&#39;est à dire le fait que l&#39;agent qui produit une réponse automatique arrive à tirer la sonnette d&#39;alarme pour dire de lui même qu&#39;il ne peut pas répondre.</p>
<p>Je ne vais pas rentrer dans le design de nos systèmes multi-agent aujourd&#39;hui, mais il faut assumer qu&#39;on séquence l&#39;utilisation de différents modèles pour créer la réponse la plus adaptée à l&#39;utilisateur.</p>
<p><strong>Méthode #1 : tout dans un seul prompt</strong></p>
<p>Au départ, j&#39;ai voulu tester une approche simple : tout gérer dans un seul prompt. Le modèle générait la réponse et je faisais en même temps une vérification rapide de sa qualité. Quelque chose comme, <em>&quot;write an answer and if no answer seems relevant for reason x or y suggests an escalation&quot;</em></p>
<p>Le problème, c&#39;est que cette méthode n&#39;était pas optimale. Le modèle avait trop de choses à gérer en même temps : répondre à la question, intégrer le contexte de documents suite à une étape de retrieval et vérifier la qualité de la réponse. Cela a donnée de bons résultats, de moins bons. Disons qu&#39;on s&#39;intéressait pas forcément à l&#39;escalade à ce moment là.</p>
<p>Mais le sujet de l&#39;escalade est devenu important et il fallait plus de marge de manoeuvre.</p>
<p><strong>Méthode #2 : découper, mais des réponses imprécises</strong></p>
<p>Pour adapter cela, j&#39;ai testé une approche plus stricte où je voulais découper le processus. L&#39;idée était de s&#39;assurer que le modèle couvrait bien le sujet <strong>avant</strong> même de générer la réponse. Si la question n&#39;était pas suffisamment couverte par les documents, je renvoyais la demande à un humain.</p>
<p>Elle avait l&#39;avantage (trop tôt) de sembler plus économe en ressources car elle évite la génération d&#39;une réponse.</p>
<p>Mais cette méthode a posé un nouveau problème : le modèle a commencé à générer des réponses imprécises ou incomplètes. Par exemple, si la langue de la question ne correspondait pas à celle des documents fournis, ou si les documents ne couvraient pas le sujet, le modèle refusait strictement de proposer une réponse pour escalader.</p>
<p>C&#39;était honnêtement une vrai tête de mule. Difficile à calibrer.</p>
<p>J&#39;ai pas mal douté qu&#39;il faille revenir sur le découpage, mais en expliquant le problème à une collègue, j&#39;ai compris que je faisais les choses de travers.</p>
<p><strong>Méthode #3 : générer d&#39;abord, évaluer ensuite</strong></p>
<p>J&#39;ai donc pris un autre chemin. Plutôt que de bloquer tout en amont, j&#39;ai laissé le modèle générer sa réponse, pour finalement évaluer la réponse ensuite. L&#39;évaluation est bien plus efficace, et c&#39;est flagrant ! En générant d&#39;abord, on donne de la liberté au modèle. Ensuite, on peut juger de la pertinence de la réponse, et si nécessaire, activer l&#39;escalade vers un humain. Cela permet de mieux contrôler le processus sans sacrifier la réactivité.</p>
<p>Même si cette approche semble plus efficace pour l&#39;instant, il faut encore la tester à l&#39;échelle pour voir si elle tient la route dans des conditions réelles. Ce qui fait vraiment la différence, c&#39;est de laisser d&#39;abord les agents essayer de répondre. L&#39;évaluation finale se fait après coup, pour juger si la réponse mérite d&#39;être désactivée et envoyée à un humain. Cette méthode permet de garder de la souplesse tout en filtrant les erreurs importantes au dernier moment, sans trop de rigidité en amont.</p>
<p><strong>Conclusion</strong></p>
<p>En bref, laisser le modèle répondre d&#39;abord et évaluer ensuite semble être la bonne approche. Ça offre plus de flexibilité et permet un meilleur contrôle des escalades. Il reste à tester à grande échelle, mais les premiers résultats sont prometteurs.</p>
<p><em>Texte corrigé avec chatgpt.com</em>
<img src="https://cdn-images-1.medium.com/max/800/0*IR2piKm3wrEsJ56_" alt="" /></p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/0*IR2piKm3wrEsJ56_" length="0" type="image//max/800/0*IR2piKm3wrEsJ56_"/>
        </item>
        <item>
            <title><![CDATA[Reflexion sur l’usage des outils de Genai sur mon quotidien de développeur]]></title>
            <link>https://amrltqt.com/p/2024-11-28-reflexion-sur-l-usage-des-outils-de-genai-sur-mon-quotidien-de-d-veloppeur-2e71166605ad</link>
            <guid>https://amrltqt.com/p/2024-11-28-reflexion-sur-l-usage-des-outils-de-genai-sur-mon-quotidien-de-d-veloppeur-2e71166605ad</guid>
            <pubDate>Thu, 28 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Je suis développeur de logiciels. Depuis un an et demi, j'utilise Copilot et GPT au quotidien. Ces outils ne sont pas des gadgets. Ils ont...

*** ** * ** ***

### **Reflexion sur l'usage des outils d...]]></description>
            <content:encoded><![CDATA[<p>Je suis développeur de logiciels. Depuis un an et demi, j&#39;utilise Copilot et GPT au quotidien. Ces outils ne sont pas des gadgets. Ils ont un impact concret et profond sur ma manière de travailler.</p>
<p>Ils m&#39;épargnent des tâches répétitives et m&#39;aident à me concentrer sur ce qui compte : explorer des idées, vérifier qu&#39;elles tiennent la route, aller à l&#39;essentiel. Ce sont des alliés précieux. Ils apportent des perspectives neuves, des angles auxquels je n&#39;avais pas pensé. Ils clarifient, structurent, accélèrent.</p>
<p>Je ne suis pas perfectionniste. Ce qui m&#39;intéresse, c&#39;est produire, avancer, creuser des concepts et créer des choses qui fonctionnent. Grâce à ces outils, je sors du code propre sans y passer des heures.</p>
<p>Le gain est net. J&#39;ai au moins doublé ma productivité. J&#39;apprends plus vite, je fais moins d&#39;erreurs. Mes projets avancent à un rythme soutenu, et en parallèle, ma maîtrise des outils et des concepts progresse.</p>
<p>Mais ce n&#39;est pas sans conséquences. Ce qu&#39;on faisait avant en une demi-journée peut être bouclé en une heure. Ce qui prenait une semaine parfois prend une journée. Le temps dégagé ne reste pas vide longtemps : il se remplit naturellement par des tâches plus complexes. Ce sont des problèmes qui demandent réflexion, créativité, concentration. C&#39;est un rythme différent, exigeant.</p>
<p>Ce mode de travail me convient. J&#39;apprécie les défis intellectuels, mais ce n&#39;est pas le cas de tout le monde. La profession va devoir s&#39;adapter à un modèle où les tâches simples disparaissent et où le cœur du métier devient la résolution de problèmes complexes.</p>
<p>L&#39;intelligence artificielle ne remplace pas le développeur. Elle amplifie ce qu&#39;on est capable de faire. Elle pousse à aller plus loin, plus vite. Mais pour en tirer le meilleur, il faut accepter de changer de rythme et d&#39;aborder le travail autrement.</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*SOsIqQUf98Yr4VED6ZVbeg.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Je n’ai pas envie de discuter avec des machines à café]]></title>
            <link>https://amrltqt.com/p/2024-06-03-je-n-ai-pas-envie-de-discuter-avec-des-machines-caf-38b109f1fc30</link>
            <guid>https://amrltqt.com/p/2024-06-03-je-n-ai-pas-envie-de-discuter-avec-des-machines-caf-38b109f1fc30</guid>
            <pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Est-ce que dans 4 ans, on continuera à écrire "stp" à ChatGPT et on attendra tranquillement la réponse devant notre écran?

*** ** * ** ***

### Je n'ai pas envie de discuter avec des machines à café...]]></description>
            <content:encoded><![CDATA[<p>Est-ce que dans 4 ans, on continuera à écrire &quot;stp&quot; à ChatGPT et on attendra tranquillement la réponse devant notre écran?</p>
<hr>
<h3>Je n&#39;ai pas envie de discuter avec des machines à café</h3>
<p>Est-ce que dans 4 ans, on continuera à écrire &quot;stp&quot; à ChatGPT et attendre la fin du petit effet streaming du contenu pour avoir nos réponses? Est-ce qu&#39;on aura dépassé cette phase de hype autour des modèles de langages qui lâchent leur prose au compte goutte alors qu&#39;on attend tranquillement devant notre PC ?
<img src="https://cdn-images-1.medium.com/max/800/0*mrjJok26F60GxnLB" alt="" /></p>
<p>Dans 4 ans, j&#39;espère qu&#39;on pourra simplement demander un &quot;café fort&quot; en entrant dans la cuisine, sans avoir à formuler une commande détaillée pour que la machine démarre la préparation. Donner des ordres à ma machine à café en parlant tout haut me conviendrait bien.</p>
<p>Demander des choses en parlant, ça marche. L&#39;humanité a utilisé le langage pour transmettre des ordres pendant des millénaires. Pour commander les machines, nous utilisons des langages de programmation, pratiques et formels, mais pas accessibles à tous. La solution la plus simple serait de programmer les machines pour comprendre notre langue. C&#39;est là que les modèles de langage offrent une opportunité incroyable.</p>
<p>Pour une machine à café, par exemple, il faudrait qu&#39;elle capte tout ce qu&#39;on dit dans la pièce, comprenne qu&#39;un ordre lui est destiné et l&#39;exécute. Siri, Alexa et Google Assistant ont tenté cette approche, mais les avancées récentes d&#39;OpenAI et Google montrent des progrès bien plus prometteurs.</p>
<p>C&#39;est tout à fait possible que dans 4 ans, des produits qui embarquent peu de technologie aujourd&#39;hui soient équipés de capacités à comprendre le langage naturel pour extraire les ordres pertinents.</p>
<p>Ca me paraît impossible pour une machine de traiter un flux audio en continu pour en extraire l&#39;ordre à réaliser. La réalité d&#39;un environnement aussi complexe qu&#39;une cuisine dépasse le simple décodage du langage humain :D</p>
<p>Pour réaliser cet exploit, la machine devra certainement :</p>
<ul>
<li>Identifier une personne dans la cuisine (ou dans un périmètre acceptable)</li>
<li>Capter et analyser toutes les conversations de cette personne</li>
<li>Filtrer le contenu non pertinent pour ses actions possibles</li>
<li>Identifier et paramétrer l&#39;ordre adressé</li>
<li>Vérifier que la personne donnant l&#39;ordre a l&#39;autorisation nécessaire</li>
<li>Et pourquoi pas, confirmer en utilisant elle-même le langage humain.</li>
</ul>
<p>Il faudra une bonne compréhension du langage naturel. Merci aux modèles de langage. Mais surtout, il faudra beaucoup de code pour intégrer toutes ces fonctionnalités et tout la prise en compte du contexte nécessaire à l&#39;exécution de l&#39;ordre.</p>
<p>Nous ne nous contenterons certainement pas de parler aux machines et n&#39;attendrons pas patiemment qu&#39;elles nous répondent. Les interactions devront être immédiates et leurs réponses instantanées pour être efficaces. Aujourd&#39;hui, les modèles de langage et leur capacité à générer du contenu en streaming sont impressionnants. Mais je parie que cette petite tendance disparaîtra aussi vite qu&#39;elle est apparue.</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/0*mrjJok26F60GxnLB" length="0" type="image//max/800/0*mrjJok26F60GxnLB"/>
        </item>
        <item>
            <title><![CDATA[What if UX is the new best friend of data intensive application?]]></title>
            <link>https://amrltqt.com/p/2021-05-26-what-if-ux-is-the-new-best-friend-of-data-intensive-application-3c3a7d69315f</link>
            <guid>https://amrltqt.com/p/2021-05-26-what-if-ux-is-the-new-best-friend-of-data-intensive-application-3c3a7d69315f</guid>
            <pubDate>Wed, 26 May 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[I'm spending several hours per days reviewing applications that rely on a huge amount of data. They provide insight to customers, simulate...

*** ** * ** ***

### What if UX is the new best friend of...]]></description>
            <content:encoded><![CDATA[<p>I&#39;m spending several hours per days reviewing applications that rely on a huge amount of data. They provide insight to customers, simulate...</p>
<hr>
<h3>What if UX is the new best friend of data intensive application?</h3>
<p>I&#39;m spending several hours per days reviewing applications that rely on a huge amount of data. They provide insight to customers, simulate hypothesis or just help to look for new business opportunities.</p>
<p>More and more, to explore data, software engineers leverage web based user interfaces to build applications that intensively consume data. Those, supported by a data pipeline that prepare the data in advance to serve customer need efficiently.
<img src="https://cdn-images-1.medium.com/max/800/1*PwdzL4CT620n015OJZEeRQ.png" alt="" /> Simplified vue of a data application architecture with from the left to the right: a data pipeline with some datasets, a database and the web app displaying KPIs</p>
<p>With this architecture, you&#39;re able to feed an application with aggregated data based on large volume of data. You will not suffer from latency or issues with memory management as the data will be pre-computed and stored in a database. This is made possible if you&#39;re able to anticipate the queries that will be performed by the user.</p>
<p>The word anticipation is important. It basically means that data need to be prepared to fit user request, and those workflows are known in advance. For example, on a monthly revenue for the twenty first best products or on a list of the ten next maintenance to prioritized on a car the user workflow is perfectly managed. This situation is ideal as you are able to prepare exactly what the user want to display. There will be few questions about refresh time and number of users at the same time but honestly, this is not a big deal.</p>
<p>Things are going to be more complex at the time when the user will be able to generate query on his own. Imagine that the user want now to request not the tenth first rows but dynamically expand the query to thousands. Imagine also that the product aggregation is too large and now, we need to exclude or filter on specific products.</p>
<p>Actually, the more you will add features to filter and aggregate from the application then the more anticipating all possible queries will be a complex job. At first, you were having a responsive and efficient system, but now you need to trade-off between performance, database costs and even the customer satisfaction directly. When you&#39;re there, a step back is necessary to think about the options that you have before building the impossible.</p>
<ol>
<li>The first one will be analyzed in an other post, but it basically involve an architecture-switch to leverage an <strong>asynchronous system to send back your query to your distributed system</strong> and secure a result while assuming a higher response latency.</li>
<li>The second one is to work directly on the user needs to tune or hack its actual experience to facilitate anticipation when responding to user queries.</li>
</ol>
<p>Finding a path between the anticipation needs of engineers and the gluttony of users for data involves looking at the user experience. You need to think about user interface, user needs (or what they seems too) and the workflow they want to use.</p>
<p>Obviously as we&#39;re dealing with user feelings and this post have is not about the data itself there are no silver bullet solutions.</p>
<p>But here are the points I&#39;m trying to optimize or look for in priority when I face such issue.</p>
<p><strong>Access patterns</strong></p>
<p>How does the user try to access the data? Does it use a direct reference? Do we need to stick to the common pattern of having lists of objects prior to accessing the searched one? Navigating the data and application performance or intimately linked. Whenever possible try to reduce navigation to the minimum.</p>
<p>I&#39;ve met many users who know well the reference of their data, business users with experience have deal with excel files for years, and probably build methodologies or trick to find data faster. Leveraging this experience in accessing the data instead of building a traditional web navigation can help you to save compute and time by avoiding useless queries. Imagine that instead of building a Search  List Proposition  Object Result you create a Business Object Reference  Object result.</p>
<p>This approach is interesting also on engineering side as it will help them to anticipate which field are crucial to build the correct index and cache that will improve response delay. You can now avoid preparing the whole data table and just be sure that a few subset of data is enough to fit the need. For the example above you can even avoid building list of objects, pagination, search orders.</p>
<p><strong>One complex object at a time instead of list</strong></p>
<p>You shouldn&#39;t ever accept to load data that is not directly displayed to the user. It will cost a lot in term of response delay and in term of computing resource while being perfectly useless for the user. For example, let&#39;s take a query that perform a compute intensive aggregation on several keys on your database. There are two scenarios:</p>
<ol>
<li>User provide the scope, the query is processed, it takes time then it&#39;s available for the user and it ends up with the user to scroll to find the few keys that are really interesting.</li>
<li>The user anticipate the key that he&#39;s looking for and then process the query for this unique object. Then by clicking on a forward reference he can jump on the other one reprocessing a new query.</li>
</ol>
<p>Focusing on one object at a time is a good way to save time and processing while offering engineer a good anticipation compared to the real volume of data. In the first case you can deal with aggregating over a stack of object that is continuously growing (I want 100 objects, 1000 ...), in the second one you help the user to focus and provide only the information he&#39;s looking for.</p>
<p><strong>Filter, filter, filter.</strong></p>
<p>You got the trick, the idea is to work on the user experience to find all the path possible to avoid requesting data that will not be used. But when you provide abilities to generate a query you always need to deal with the risk of performance / experience issues.</p>
<p>In fact, instead of wasting time to look for the data they need in a bunch of rows, power users should build the perfectly fitted query they need. It could be direct access, a set of filters or even a domain specific language to query efficiently the data. When the user share the &quot;rules&quot;, engineering job can start to prepare datasets and views that will fit those rules leveraging the right indexes or preparing expensive aggregates.</p>
<p><strong>Wrap up</strong></p>
<p>To reduce continuously the risk of waste you need to closely work with users to have a comprehensive understanding of the data usage. It will be easier to avoid all the common experience issues that you find when you build product that fits industry standard instead of building a fitted experience.</p>
<p>Following the role you take in building such application you need to always keep in mind that their are multiple ways to achieve the same goal when building such products . Thinking about hacking your current perception of user needs can provide great benefit for the user experience directly, but also on application performance and definitely can help you to reduce the computing waste and user frustration.</p>
]]></content:encoded>
            <enclosure url="https://cdn-images-1.medium.com/max/800/1*PwdzL4CT620n015OJZEeRQ.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>