Tool Calling ermöglicht es LLMs, mit externen Systemen zu interagieren, indem es Zugang zu vordefinierten Funktionen während der Antwortgenerierung bereitstellt. Diese Dokumentation behandelt zunächst die Grundlagen der Evaluierung von LLM Tool Calling mit Elluminate. Anschließend gibt sie praktische Anleitungen dazu, wie Sie Ihr bestehendes agentisches System anpassen können, um es mit Elluminate zu evaluieren. Schließlich zeigt sie ein fortgeschrittenes Beispiel für die Evaluierung eines Agenten mit Lesezugriff auf ein Dateisystem.
Grundlegende Verwendung
Ein Beispiel, das die Integration von Wetter-Tools für Echtzeitdatenzugriff zeigt:
| template, _ = await client.prompt_templates.aget_or_create(
user_prompt_template="""\
You are a helpful weather assistant. The user is asking: {{user_query}}.
Use the available weather tools to provide accurate information.
Respond in the units most customary of the location being queried.""",
name="Weather Assistant with Tools",
tools=[
FunctionTool( # (1)!
type="function",
function={
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state/country, e.g. Berlin, DE",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use",
},
},
"required": ["location", "unit"],
"additionalProperties": False,
},
"strict": True,
},
),
],
tool_choice="auto", # (2)!
)
|
-
Tools definieren: Setzen Sie tools
auf Funktions-Tool-Definitionen unter Verwendung von OpenAIs FunctionTool
-Typ. Diese Definitionen beschreiben jede Funktion und spezifizieren genau, welche Eingabedaten die Funktion erwartet und welche Ausgabedaten sie zurückgibt.
-
Template erstellen: Optional können Sie tool_choice
setzen, um zu kontrollieren, wann das Modell Tools verwenden soll. Standardmäßig ist es "auto", wenn es weggelassen wird.
Sobald das Prompt-Template mit den Tool-Definitionen definiert wurde, verläuft der Rest des Evaluierungsprozesses wie gewohnt. Experimente werden normal ausgeführt - das Modell erzeugt automatisch Tool-Aufrufe (führt sie jedoch nicht aus) - und die Kriterien evaluieren die gewählten Tools.
Tool-Aufrufe können in der 'tool_calls'
der Assistenten-Nachricht als Liste der aufgerufenen Tools gefunden werden:
| for message in response.messages:
if message["role"] == "assistant":
print(f"{message['tool_calls']}")
|
Tool-Ausführung
Elluminate unterstützt derzeit nicht die Ausführung Ihrer Tools. Beim Durchführen von Experimenten sehen Sie den Tool-Aufruf, der ausgewählt wurde, aber nicht die tatsächliche Ausgabe oder Ergebnisse dieses Tools. Um die Ausführung von Tools zu evaluieren, lesen Sie den Abschnitt Fortgeschrittenes Beispiel unten, da besondere Sorgfalt erforderlich ist.
Vollständiges Grundbeispiel
| import asyncio
from elluminate import Client
from elluminate.schemas import RatingMode
from openai.types.beta import FunctionTool
async def main():
client = Client()
template, _ = await client.prompt_templates.aget_or_create(
user_prompt_template="""\
You are a helpful weather assistant. The user is asking: {{user_query}}.
Use the available weather tools to provide accurate information.
Respond in the units most customary of the location being queried.""",
name="Weather Assistant with Tools",
tools=[
FunctionTool( # (1)!
type="function",
function={
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state/country, e.g. Berlin, DE",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use",
},
},
"required": ["location", "unit"],
"additionalProperties": False,
},
"strict": True,
},
),
],
tool_choice="auto", # (2)!
)
collection, _ = await client.collections.aget_or_create(
name="Weather Query Test Data",
variables=[
{"user_query": "What's the weather like in London, UK and should I bring an umbrella?"},
{"user_query": "Compare the current weather in Boston and San Francisco."},
],
)
await client.criteria.aget_or_generate_many(template)
experiment, _ = await client.experiments.aget_or_create(
name="Weather Tool Calling Experiment",
prompt_template=template,
collection=collection,
description="Testing tool calling capabilities for weather queries",
generate=True,
rating_mode=RatingMode.FAST,
block=True,
n_epochs=1,
)
for i, response in enumerate(experiment.rated_responses, 1):
print(f"Example {i}:")
print(f"Query: {response.prompt.template_variables.input_values['user_query']}")
print("Response:")
for message in response.messages:
if message["role"] == "assistant":
print(f"{message['tool_calls']}")
print()
if __name__ == "__main__":
asyncio.run(main())
|
Tools werden mit OpenAIs Standard-FunctionTool
-Format definiert:
from openai.types.beta import FunctionTool
weather_tool = FunctionTool(
type="function",
function={
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state/country, e.g. Berlin, DE"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use"
}
},
"required": ["location", "unit"],
"additionalProperties": False
},
"strict": True
}
)
Der tool_choice
-Parameter kontrolliert, wann und wie das Modell verfügbare Tools verwendet:
-
Automatische Auswahl (tool_choice="auto"
): Lassen Sie das Modell entscheiden, wann und welche Tools verwendet werden sollen.
-
Erforderliche Verwendung (tool_choice="required"
): Zwingen Sie das Modell, mindestens ein Tool zu verwenden.
-
Deaktivierte Tools (tool_choice="none"
): Deaktivieren Sie alle Tools für diese Antwort.
-
Spezifische Funktion: Zwingt das Modell, ein bestimmtes Tool aufzurufen.
tool_choice={
"type": "function",
"function": {"name": "get_current_weather"}
}
Evaluierung Ihres agentischen Systems
Die Evaluierung eines agentischen Systems mit Elluminate folgt einem ähnlichen Prozess wie die Evaluierung eines einzelnen Prompts. Der einzige Unterschied besteht darin, dass anstatt den Prompt direkt zu inferenzieren, um die LLM-Antwort zu erhalten, Ihr Agent eine beliebige Anzahl von Tool-Aufrufen durchführen kann, bevor er zu seiner endgültigen Antwort kommt. Daher wird die Evaluierung sowohl an der gesamten Tool-Kette als auch an der endgültigen Antwort durchgeführt. Dieser Abschnitt umreißt den prinzipiellen Ansatz zur Evaluierung Ihres agentischen Systems. Der folgende Abschnitt führt durch ein vollständiges Beispiel, das diesen Ansatz in der Praxis anwendet.
Der erste Schritt ist nahezu identisch mit der Evaluierung eines einzelnen Prompts. Sie müssen Elluminate mit Ihrem Prompt-Template laden, Kriterien hinzufügen oder generieren und eine Sammlung mit repräsentativen Eingaben für Ihr System erstellen.
Nuancierte Unterschiede
- Tool-Definitionen als Bewertungskontext: Damit das Bewertungsmodell Zugang zu allen Tools und ihren Parameterbeschreibungen hat, müssen Sie die Tool-Definitionen zum Prompt-Template hinzufügen. Vom SDK aus kann dies über die Parameter
tools
und tool_choice
bei allen Create-Methoden erfolgen. Das Frontend hat auch ein spezielles Feld zum Hinzufügen von Tool-Definitionen. Dies bietet dem Bewertungsmodell während der Bewertung wertvollen Kontext darüber, was jedes Tool und seine Parameter tun.
- Maßgeschneiderte Kriterien für Tool-Aufrufe: Es kann vorteilhaft sein, Tools und ihre Parameter explizit namentlich in den Kriterien zu erwähnen. Dies hilft dem Bewertungsmodell zu wissen, auf welchen Teil des Tool-Aufrufs es sich während der Bewertung konzentrieren soll. Zum Beispiel ist "Wurde
get_current_weather
mit den Temperatur-units
aufgerufen, die am häufigsten am gegebenen location
verwendet werden?" präziser als "Sind die Einheiten für die Stadt korrekt?".
Mit diesem Setup muss Ihr bestehender agentischer Code, da Elluminate Tool-Aufrufe nicht ausführen kann, die Tool-Kette inferenzieren und die endgültige Antwort selbst produzieren. Dann müssen Sie die gesamte Kette von Tool-Aufrufen, Antworten sowie die endgültige Ausgabe manuell als eine einzige Antwort in Elluminate hinzufügen. Wichtig ist, dass die Einbeziehung der gesamten Aufrufkette erforderlich ist, wenn Sie sowohl den Tool-Calling-Prozess als auch die endgültige Ausgabe Ihres Agenten evaluieren möchten.
Tool-Aufrufe manuell als Antwort hinzufügen
Das SDK bietet die Methode client.responses.add
, um manuell eine Antwort hinzuzufügen. Diese Methode akzeptiert entweder einen String oder eine Liste von OpenAI-Completion-Nachrichten als Antwort. Wenn Sie eine Liste von Completion-Nachrichten bereitstellen, bilden sie alle zusammen die einzige Antwort. Dies ermöglicht es dem Bewertungsmodell, nicht nur die endgültige Ausgabe zu bewerten, sondern auch alle zwischenzeitlichen Tool-Aufrufe und Tool-Ergebnisse.
Die Methode client.responses.add_many
funktioniert genau gleich, wird aber für das Bulk-Hinzufügen von Antworten verwendet.
Fortgeschrittenes Beispiel
Dieses fortgeschrittene Beispiel demonstriert, wie man ein minimales, agentisches LLM-System mit Elluminate evaluiert. Der Agent hat Zugang zu grundlegenden Dateisystem-Tools, die es ihm ermöglichen, Verzeichnisse zu navigieren, Dateien zu lesen und ihre Metadaten zu analysieren. Er wird mit grundlegenden Fragen wie "Was ist die größte Datei im System?" beauftragt und darauf evaluiert, ob er korrekt geantwortet hat und welche Methoden er während des Prozesses verwendet hat.
Manuelle Tool-Ausführung erforderlich
Elluminate unterstützt keine Tool-Ausführung. Daher muss die Tool-Ausführung immer noch manuell in Ihrem Code behandelt werden. Sie müssen dann die gesamte Kette von Tool-Aufrufen, Tool-Ausgaben und endgültiger Antwort als manuell hinzugefügte Antwort an Elluminate zurückgeben.
Dieses Beispiel implementiert zunächst mehrere Dateisystem-Operationen als Python-Funktionen. Es gibt Funktionen, um das aktuelle Arbeitsverzeichnis zurückzugeben, Verzeichnisse zu wechseln, Dateien aufzulisten und andere Operationen.
Tool-Funktionsimplementierung
| def pwd() -> str: # (1)!
return os.getcwd()
def chdir(directory: str) -> None: # (2)!
os.chdir(directory)
def list_dir() -> list[tuple[str, str]]: # (3)!
ret = []
items = os.listdir(".")
for item in items:
if os.path.isfile(item):
ret.append((item, "FILE"))
elif os.path.isdir(item):
ret.append((item, "DIRECTORY"))
return ret
def file_stats(file_name: str) -> dict[str, Any]: # (4)!
stat_info = os.stat(file_name)
return {
"Size (bytes)": stat_info.st_size,
"Last Modified": datetime.datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
"Created At": datetime.datetime.fromtimestamp(stat_info.st_ctime).strftime("%Y-%m-%d %H:%M:%S"),
}
def read_file(file_name: str) -> str: # (5)!
with open(file_name, "r") as file:
return file.read()
|
- Aktuelles Verzeichnis: Gibt den aktuellen Arbeitsverzeichnispfad zurück
- Verzeichnisnavigation: Ändert das aktuelle Arbeitsverzeichnis
- Verzeichnislisting: Listet alle Dateien und Verzeichnisse mit ihren Typen auf
- Dateistatistiken: Ruft detaillierte Dateimetadaten einschließlich Größe und Zeitstempel ab
- Dateilesung: Liest und gibt den Inhalt von Textdateien zurück
Tools werden mit OpenAIs FunctionTool
-Format definiert, wobei jede Python-Funktion auf eine strukturierte Tool-Definition abgebildet wird. So erhält jede der oben definierten Python-Methoden ihre eigene FunctionTool
-Definition, die ihren Namen, ihre Beschreibung und die akzeptierten Argumente festlegt.
Tool-Definitions-Setup
| tools = [
FunctionTool(
type="function",
function={
"name": "pwd",
"description": "Get the current working directory path",
"parameters": {"type": "object", "properties": {}, "required": []},
},
),
FunctionTool(
type="function",
function={
"name": "chdir",
"description": "Change the current working directory",
"parameters": {
"type": "object",
"properties": {"directory": {"type": "string", "description": "The directory path to change to"}},
"required": ["directory"],
},
},
),
FunctionTool(
type="function",
function={
"name": "list_dir",
"description": "List all files and directories in the current directory",
"parameters": {"type": "object", "properties": {}, "required": []},
},
),
FunctionTool(
type="function",
function={
"name": "file_stats",
"description": "Get detailed statistics about a specific file including size, creation time, and modification time",
"parameters": {
"type": "object",
"properties": {
"file_name": {"type": "string", "description": "The name of the file to get statistics for"}
},
"required": ["file_name"],
},
},
),
FunctionTool(
type="function",
function={
"name": "read_file",
"description": "Read and return the contents of a text file",
"parameters": {
"type": "object",
"properties": {"file_name": {"type": "string", "description": "The name of the file to read"}},
"required": ["file_name"],
},
},
),
]
|
Wie bereits erwähnt, muss die Tool-Ausführung außerhalb von Elluminate behandelt werden. In einer for-Schleife im Skript inferenziert es die Prompt-Nachrichten und überprüft, ob ein Tool aufgerufen wurde. Wenn ja, führt es das Tool aus und sendet das Ergebnis zurück an das LLM. Wenn nein, verlässt es die Schleife mit der vollständigen Nachrichtenkonversation.
Multi-Turn-Konversationsbehandlung
| def chat_with_tools(openai_client, messages, max_iterations=30):
# Reset the current working directory to root since the tool calls from
# previous chats may have changed directories
chdir("/")
responded_messages = []
for iteration in range(1, max_iterations + 1):
print(f"\n--- Iteration {iteration} ---")
# Get response from OpenAI gpt-4o-mini with tool calling enabled
response = openai_client.chat.completions.create( # (1)!
model="gpt-4o-mini",
messages=messages + responded_messages,
tools=[tool.model_dump() for tool in tools],
tool_choice="auto",
)
assistant_message = response.choices[0].message
responded_messages.append(assistant_message.model_dump())
# Check if the LLM wants to call any tools and execute each tool
if assistant_message.tool_calls: # (2)!
print("AI is calling tools...")
for tool_call in assistant_message.tool_calls:
print(f"Calling function: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")
function_result = execute_function_call(tool_call.function)
print(f"Result: {function_result}")
responded_messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": tool_call.function.name,
"content": function_result,
}
)
else:
print("Final response:")
print(assistant_message.content)
return responded_messages
print("Max iterations reached!")
return responded_messages
|
- OpenAI-Integration: Verwendet OpenAIs Chat-Completion-API mit den Tool-Definitionen und Tool-Choice auf
"auto"
-Modus gesetzt.
- Tool-Ausführungsschleife: Wenn Tools aufgerufen werden, führt sie diese automatisch aus und stellt die Ergebnisse als Fortsetzung der Nachrichtenkonversation zurück an das Modell.
Experiment-Setup und Evaluierung
Alles zusammenfügend integriert dieser Workflow den Tool-Ausführungscode mit Elluminates Experiment-System. Ein Prompt-Template mit den FunctionTool
-Definitionen und eine Sammlung repräsentativer Benutzeranfragen werden erstellt. Dann werden Kriterien manuell dem Prompt-Template zugewiesen. Einmal eingerichtet, wird der Tool-Ausführungscode für jede Eingabe in der Sammlung ausgeführt. Die vollständigen Nachrichtenkonversationen werden zurück in Elluminate gespeichert und als Teil eines Experiments bewertet.
Vollständiger Experiment-Workflow
| prompt_template, _ = await client.prompt_templates.aget_or_create( # (1)!
user_prompt_template=[
ChatCompletionSystemMessageParam(
role="system",
content="You are a helpful file system assistant. Use the provided tools to explore and analyze the file system. Always start by checking the current directory and exploring the structure before answering questions. Ignore the /tmp and /var directories.",
),
ChatCompletionUserMessageParam(role="user", content="{{user_query}}"),
],
name="File System Explorer with Tools",
tools=tools,
tool_choice="auto",
)
collection, _ = await client.collections.aget_or_create(
name="File System Query Test Data",
variables=[
{"user_query": "What is the largest file in the system?", "answer": "app.log"},
{"user_query": "Which file was created most recently?", "answer": "settings.json"},
{
"user_query": "Can you find all the text files (.txt) and tell me which one has the longest content?",
"answer": "report.txt",
},
{"user_query": "Which files has configuration information?", "answer": "settings.json"},
],
)
await client.criteria.aadd_many(
[
"Did the response say the correct file was: {{answer}}?",
"Were at most 7 tool calls used?",
"Was 'list_dir' called on the /files directory by calling 'chdir' in to it?",
"Does the tool call trace NOT show any sign of confusion or misguidance to solve the task at hand?",
],
prompt_template,
delete_existing=True,
)
responses = []
for template_vars in collection.variables:
rendered_messages = prompt_template.render_messages( # (2)!
user_query=template_vars.input_values["user_query"]
)
# Handle tool calling conversation manually from this script
response_messages = chat_with_tools(openai_client, rendered_messages) # (3)!
response = await client.responses.aadd( # (4)!
response=response_messages,
prompt_template=prompt_template,
template_variables=template_vars,
)
responses.append(response)
experiment, _ = await client.experiments.aget_or_create( # (5)!
name="File System Tool Calling Experiment",
prompt_template=prompt_template,
collection=collection,
description="Testing tool calling capabilities for file system operations",
)
# Rate responses and attribute their ratings to the `experiment`
await client.ratings.arate_many(responses, experiment=experiment)
|
- Prompt-Template: Definiert einen grundlegenden System-Prompt und einen Benutzer-Prompt, der mit einer
user_query
aus einer Sammlung gefüllt werden soll.
- Nachrichten-Rendering: Um manuell zu inferenzieren, muss der
user_query
-Platzhalter im Prompt-Template ausgefüllt werden. Dies gibt die vollständig gerenderten Nachrichten zurück, die direkt an OpenAIs Completion-Client weitergegeben werden können.
- Manuelle Tool-Ausführung: Behandelt die vollständige Tool-Calling-Konversation manuell außerhalb von Elluminate
- Antwortaufzeichnung: Fügt manuell die endgültige Tool-Calling-Konversation als Antwort zu Elluminate hinzu
- Experiment-Erstellung: Erstellt ein Experiment und bewertet die Antworten gegen die Kriterien des Prompt-Templates
Vollständiges fortgeschrittenes Beispiel
| import asyncio
import datetime
import json
import os
import time
from typing import Any
from elluminate import Client
from openai import AzureOpenAI, OpenAI
from openai.types.beta import FunctionTool
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
from pyfakefs.fake_filesystem_unittest import Patcher
def get_openai_client() -> AzureOpenAI | OpenAI:
if "AZURE_OPENAI_ENDPOINT" in os.environ:
return AzureOpenAI(
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
api_version=os.environ.get("OPENAI_API_VERSION"),
api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
)
else:
return OpenAI()
def pwd() -> str: # (1)!
return os.getcwd()
def chdir(directory: str) -> None: # (2)!
os.chdir(directory)
def list_dir() -> list[tuple[str, str]]: # (3)!
ret = []
items = os.listdir(".")
for item in items:
if os.path.isfile(item):
ret.append((item, "FILE"))
elif os.path.isdir(item):
ret.append((item, "DIRECTORY"))
return ret
def file_stats(file_name: str) -> dict[str, Any]: # (4)!
stat_info = os.stat(file_name)
return {
"Size (bytes)": stat_info.st_size,
"Last Modified": datetime.datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
"Created At": datetime.datetime.fromtimestamp(stat_info.st_ctime).strftime("%Y-%m-%d %H:%M:%S"),
}
def read_file(file_name: str) -> str: # (5)!
with open(file_name, "r") as file:
return file.read()
tools = [
FunctionTool(
type="function",
function={
"name": "pwd",
"description": "Get the current working directory path",
"parameters": {"type": "object", "properties": {}, "required": []},
},
),
FunctionTool(
type="function",
function={
"name": "chdir",
"description": "Change the current working directory",
"parameters": {
"type": "object",
"properties": {"directory": {"type": "string", "description": "The directory path to change to"}},
"required": ["directory"],
},
},
),
FunctionTool(
type="function",
function={
"name": "list_dir",
"description": "List all files and directories in the current directory",
"parameters": {"type": "object", "properties": {}, "required": []},
},
),
FunctionTool(
type="function",
function={
"name": "file_stats",
"description": "Get detailed statistics about a specific file including size, creation time, and modification time",
"parameters": {
"type": "object",
"properties": {
"file_name": {"type": "string", "description": "The name of the file to get statistics for"}
},
"required": ["file_name"],
},
},
),
FunctionTool(
type="function",
function={
"name": "read_file",
"description": "Read and return the contents of a text file",
"parameters": {
"type": "object",
"properties": {"file_name": {"type": "string", "description": "The name of the file to read"}},
"required": ["file_name"],
},
},
),
]
# Function mapping for tool execution
function_map = {
"pwd": pwd,
"chdir": chdir,
"list_dir": list_dir,
"file_stats": file_stats,
"read_file": read_file,
}
def execute_function_call(function_call) -> str:
"""Execute a function call and return the result"""
function_name = function_call.name
arguments = json.loads(function_call.arguments)
try:
return str(function_map[function_name](**arguments))
except Exception as e:
return f"Error executing {function_name}: {str(e)}"
def chat_with_tools(openai_client, messages, max_iterations=30):
# Reset the current working directory to root since the tool calls from
# previous chats may have changed directories
chdir("/")
responded_messages = []
for iteration in range(1, max_iterations + 1):
print(f"\n--- Iteration {iteration} ---")
# Get response from OpenAI gpt-4o-mini with tool calling enabled
response = openai_client.chat.completions.create( # (1)!
model="gpt-4o-mini",
messages=messages + responded_messages,
tools=[tool.model_dump() for tool in tools],
tool_choice="auto",
)
assistant_message = response.choices[0].message
responded_messages.append(assistant_message.model_dump())
# Check if the LLM wants to call any tools and execute each tool
if assistant_message.tool_calls: # (2)!
print("AI is calling tools...")
for tool_call in assistant_message.tool_calls:
print(f"Calling function: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")
function_result = execute_function_call(tool_call.function)
print(f"Result: {function_result}")
responded_messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": tool_call.function.name,
"content": function_result,
}
)
else:
print("Final response:")
print(assistant_message.content)
return responded_messages
print("Max iterations reached!")
return responded_messages
def setup_filesystem(patcher):
"""Set up the fake filesystem with sample files"""
# Create a more interesting fake file system
patcher.fs.create_file(
"/files/report.txt",
contents="Annual sales report for 2024. Revenue increased by 15% compared to last year.",
)
patcher.fs.create_file("/files/notes.txt", contents="Meeting notes from project kickoff.")
patcher.fs.create_file("/files/photo1.jpg", contents="[Binary image data would be here]")
patcher.fs.create_file(
"/logs/app.log",
contents="2024-01-15 10:30:22 - Application started\n2024-01-15 10:31:45 - User logged in\n2024-01-15 11:22:10 - Error: Connection timeout",
)
# Sleep to create time differences
time.sleep(2)
patcher.fs.create_file("/files/cache.tmp", contents="Temporary cache data")
time.sleep(1)
patcher.fs.create_file(
"/files/settings.json", contents='{"theme": "dark", "auto_save": true, "max_files": 100}'
)
async def main():
client = Client()
openai_client = get_openai_client()
# Set up a "virtual" filesystem. All `os` commands within this context manager will be faked
with Patcher() as patcher:
setup_filesystem(patcher)
prompt_template, _ = await client.prompt_templates.aget_or_create( # (1)!
user_prompt_template=[
ChatCompletionSystemMessageParam(
role="system",
content="You are a helpful file system assistant. Use the provided tools to explore and analyze the file system. Always start by checking the current directory and exploring the structure before answering questions. Ignore the /tmp and /var directories.",
),
ChatCompletionUserMessageParam(role="user", content="{{user_query}}"),
],
name="File System Explorer with Tools",
tools=tools,
tool_choice="auto",
)
collection, _ = await client.collections.aget_or_create(
name="File System Query Test Data",
variables=[
{"user_query": "What is the largest file in the system?", "answer": "app.log"},
{"user_query": "Which file was created most recently?", "answer": "settings.json"},
{
"user_query": "Can you find all the text files (.txt) and tell me which one has the longest content?",
"answer": "report.txt",
},
{"user_query": "Which files has configuration information?", "answer": "settings.json"},
],
)
await client.criteria.aadd_many(
[
"Did the response say the correct file was: {{answer}}?",
"Were at most 7 tool calls used?",
"Was 'list_dir' called on the /files directory by calling 'chdir' in to it?",
"Does the tool call trace NOT show any sign of confusion or misguidance to solve the task at hand?",
],
prompt_template,
delete_existing=True,
)
responses = []
for template_vars in collection.variables:
rendered_messages = prompt_template.render_messages( # (2)!
user_query=template_vars.input_values["user_query"]
)
# Handle tool calling conversation manually from this script
response_messages = chat_with_tools(openai_client, rendered_messages) # (3)!
response = await client.responses.aadd( # (4)!
response=response_messages,
prompt_template=prompt_template,
template_variables=template_vars,
)
responses.append(response)
experiment, _ = await client.experiments.aget_or_create( # (5)!
name="File System Tool Calling Experiment",
prompt_template=prompt_template,
collection=collection,
description="Testing tool calling capabilities for file system operations",
)
# Rate responses and attribute their ratings to the `experiment`
await client.ratings.arate_many(responses, experiment=experiment)
print("Finished all ratings. View the results from the frontend.")
if __name__ == "__main__":
asyncio.run(main())
|