Le contrôle d’accès et les agents

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
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'accès est limité.
La vaste majorité des logiciels en ligne sont soumis à des contrôles pour vérifier que les utilisateurs qui s'y connectent ont bien le droit de consommer ou modifier leurs propres ressources.
Comment permettre à des agents d'accéder à ces ressources sans risques pour la sécurité?
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.
Comme je travaille pour une marketplace en ligne, on va utiliser un petit exemple sur le thème des commandes
from demo.models import Order, User
USERS = [
User(id=0, email="alpha@demo.com"),
User(id=1, email="beta@demo.com"),
]
ORDERS = [
Order(id=0, user_id=0, model="battery", quantity=4, price_cts=7499),
Order(id=1, user_id=1, model="shampoo", 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("User does not exists")
self.trusted_user_id = trusted_user_id
def get_order(self, id: int) -> 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("Order not found")
return order
def get_user(self, id: int) -> User:
user = next(filter(lambda u: u.id == id, self.users), None)
if not user:
raise ValueError("User id not found")
return user
On va créer un agent pour gérer l'accès à cette donnée en langage naturel. De nombreux frameworks existent déjà, pour vous dire à l'heure où j'écris, aws vient d'en sortir un dans la même veine.
De mon côté après avoir créé ma propre interprétation basée sur le papier REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS j'ai fini par me retourner sur l'implémentation proposée par OpenAI dans son agents-sdk.
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="orders",
instructions=dedent(
"""
Help the user to find their order information in the database.
Tool is available to help you retrieve the data.
"""
),
model="gpt-4.1-nano",
tools=[
find_order_by_id
]
)
Pour sécuriser l'accès, il faut faire attention à la fonction déclarée dans tools. Elle permet de lire le contenu d'une commande via son identifiant. On peut imaginer d'autres manières d'accéder aux données, l'exemple sera toujours pertinent.
Vous noterez que l'agent prend en charge un *Context.*C'est un objet qui vit dans l'interpréteur python et qui ne sera pas partagé avec le modèle de langage.
Vous pouvez y attacher des informations sensibles et maîtriser sa mise à jour à deux moments:
- à l'initialisation lorsque l'agent est instancié
- dans les fonctions appelée par les modèles de langage via lefunction calling
C'est là que nous allons intervenir. Nous voulons sécuriser que l'utilisateur connecté ne puisse accéder qu'a ses informations. Ce que supporte le service décrit plus haut.
Pour transmettre au service l'identifiant de l'utilisateur, nous utiliserons le Context via un champ trusted_user_idqui sera disponible dans chaque fonctions appelée par l'agent. Pour ça il faut inspecter la description de la fonction appelée.
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) -> MarketplaceService:
return MarketplaceService(trusted_user_id)
@function_tool
async def find_order_by_id(wrapper: RunContextWrapper[DemoContext], id: int) -> str:
"""
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
"""
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"""
Order ID: # {order.id}
Model: {order.model}
Price: {price}
"""
)
except:
return "Impossible to retrieve the order"
Vous pouvez voir que l'identifiant de l'utilisateur est extrait du Context et qu'il sert à récupérer le services en l'initialisant à la volée. Rien n'empêche de le récupérer autrement si nécessaire.
La fonction construite ainsi garantie qu'il est impossible de récupérer via le service les commandes d'un autre utilisateur. Qu'importent les techniques de jailbreak qui pourraient être utilisées par un utilisateur malveillant.
L'identifiant de l'utilisateur utilisé pour l'accès contrôlé n'est jamaismanipulé par le modèle de langage.
Pour mettre en musique les exemples ci-dessus vous pouvez utiliser cette fonction.
async def main(user_id: int):
context = DemoContext(
trusted_user_id=user_id
)
answer_1 = await Runner.run(
starting_agent=agent,
input="Give me the order 0",
context=context
)
print(answer_1.final_output)
answer_2 = await Runner.run(
starting_agent=agent,
input="Give me the order 1",
context=context
)
print(answer_2.final_output)
Je laisse à vos soins le fait d'étendre cette approche. Si vous voulez retrouver le code utilisé dans cette démonstration vous pouvez le retrouver sur mon compte Github. Secure data access with openai agents-sdk
