Ein modellunabhängiges Open-Supply-Agenten-Framework, das die Abhängigkeitsinjektion unterstützt

12 Min. Lektüre

Vor 13 Stunden

Im Idealfall können Sie Agentenanwendungen bereits während der Entwicklung evaluieren, anstatt dass die Evaluierung erst nachträglich erfolgt. Damit dies funktioniert, müssen Sie jedoch in der Lage sein, sowohl interne als auch externe Abhängigkeiten des Agenten, den Sie entwickeln, zu simulieren. Ich bin von PydanticAI äußerst begeistert, da es die Abhängigkeitsinjektion von Grund auf unterstützt. Es ist das erste Framework, das es mir ermöglicht hat, Agentenanwendungen auf evaluierungsgesteuerte Weise zu erstellen.

Bild der Krakauer Tuchhallen, vom Autor mit Google Imagen erstellt. Dieses Gebäude wurde im Laufe der Jahrhunderte phasenweise erbaut, wobei Verbesserungen je nach den Mängeln des aktuellen Gebäudes vorgenommen wurden. Mit anderen Worten: Evaluationsgetriebene Entwicklung.

In diesem Artikel werde ich über die zentralen Herausforderungen sprechen und die bewertungsgesteuerte Entwicklung eines einfachen Agenten mithilfe von PydanticAI demonstrieren.

Herausforderungen bei der Entwicklung von GenAI-Anwendungen

Wie viele GenAI-Entwickler habe ich auf ein Agenten-Framework gewartet, das den gesamten Entwicklungslebenszyklus unterstützt. Jedes Mal, wenn ein neues Framework auf den Markt kommt, probiere ich es aus und hoffe, dass dies das Richtige sein wird – siehe zum Beispiel meine Artikel darüber DSPy, Langkette, LangGraph und Autogen.

Ich finde, dass es zentrale Herausforderungen gibt, mit denen ein Softwareentwickler bei der Entwicklung einer LLM-basierten Anwendung konfrontiert ist. Diese Herausforderungen sind normalerweise kein Hindernis, wenn Sie einen einfachen PoC mit GenAI erstellen, aber sie werden Ihnen zu schaffen machen, wenn Sie LLM-basierte Anwendungen in der Produktion erstellen.

Welche Herausforderungen?

(1) Nichtdeterminismus: Im Gegensatz zu den meisten Software program-APIs könnten Aufrufe eines LLM mit genau derselben Eingabe jedes Mal unterschiedliche Ausgaben zurückgeben. Wie fängt man überhaupt an, eine solche Anwendung zu testen?

(2) LLM-Einschränkungen: Grundlegende Modelle wie GPT-4, Claude und Gemini sind durch ihre Trainingsdaten (z. B. kein Zugriff auf vertrauliche Unternehmensinformationen), ihre Fähigkeiten (z. B. können Sie keine Unternehmens-APIs und -Datenbanken aufrufen) und ihre Fähigkeit zur Planung/Begründung eingeschränkt.

(3) LLM-Flexibilität: Selbst wenn Sie sich dafür entscheiden, bei LLMs eines einzelnen Anbieters wie Anthropic zu bleiben, werden Sie möglicherweise feststellen, dass Sie für jeden Schritt ein anderes LLM benötigen – vielleicht benötigt ein Schritt Ihres Workflows ein kleines Sprachmodell mit niedriger Latenz (Haiku), ein anderer erfordert eine hervorragende Fähigkeit zur Codegenerierung (Sonnet) und ein dritter Schritt erfordert ein ausgezeichnetes Kontextbewusstsein (Opus).

(4) Änderungsrate: GenAI-Technologien entwickeln sich schnell weiter. In jüngster Zeit wurden viele Verbesserungen bei den grundlegenden Modellfunktionen vorgenommen. Die grundlegenden Modelle generieren nicht mehr nur Textual content basierend auf Benutzereingaben. Sie sind jetzt multimodal, können strukturierte Ausgaben generieren und über Speicher verfügen. Wenn Sie jedoch versuchen, LLM-agnostisch zu entwickeln, verlieren Sie oft den Low-Degree-API-Zugriff, der diese Funktionen aktiviert.

Um das erste Downside, den Nichtdeterminismus, anzugehen, müssen Ihre Softwaretests ein Bewertungsframework integrieren. Sie werden nie eine Software program haben, die zu 100 % funktioniert; Stattdessen müssen Sie in der Lage sein, Software program zu entwerfen, die x % korrekt ist, Leitplanken und menschliche Aufsicht aufzubauen, um Ausnahmen abzufangen, und das System in Echtzeit zu überwachen, um Regressionen abzufangen. Der Schlüssel zu dieser Fähigkeit ist Evaluationsorientierte Entwicklung (mein Begriff), eine Erweiterung der testgetriebenen Entwicklung in der Software program.

Evaluationsgetriebene Entwicklung. Skizze des Autors.

Die aktuelle Problemumgehung für alle LLM-Einschränkungen in Herausforderung Nr. 2 besteht in der Verwendung Agentenarchitekturen wie RAG bieten dem LLM Zugriff auf Instruments und nutzen Muster wie Reflection, ReACT und Chain of Thought. Daher muss Ihr Framework in der Lage sein, Agenten zu orchestrieren. Es ist jedoch schwierig, Agenten zu bewerten, die externe Instruments aufrufen können. Du musst es können Fügen Sie Proxys für diese externen Abhängigkeiten ein sodass Sie sie einzeln testen und beim Erstellen bewerten können.

Um Herausforderung Nr. 3 zu bewältigen, muss ein Agent in der Lage sein, die Fähigkeiten verschiedener Arten grundlegender Modelle aufzurufen. Ihr Agent-Framework muss es sein LLM-agnostisch auf der Granularität eines einzelnen Schritts eines Agenten-Workflows. Um die Price der Änderungsüberlegungen (Herausforderung Nr. 4) anzugehen, möchten Sie die Fähigkeit behalten, Änderungen vorzunehmen Zugang auf niedriger Ebene auf die grundlegenden Modell-APIs und um nicht mehr benötigte Abschnitte aus Ihrer Codebasis zu entfernen.

Gibt es ein Framework, das alle diese Kriterien erfüllt? Lange Zeit conflict die Antwort nein. Das Beste, was ich erreichen konnte, conflict die Verwendung von Langchain, der Abhängigkeitsinjektion von Pytest, und Deepeval mit so etwas (das vollständige Beispiel finden Sie hier). Hier):

from unittest.mock import patch, Mock
from deepeval.metrics import GEval

llm_as_judge = GEval(
identify="Correctness",
standards="Decide whether or not the precise output is factually right primarily based on the anticipated output.",
evaluation_params=(LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT),
mannequin='gpt-3.5-turbo'
)

@patch('lg_weather_agent.retrieve_weather_data', Mock(return_value=chicago_weather))
def eval_query_rain_today():
input_query = "Is it raining in Chicago?"
expected_output = "No, it isn't raining in Chicago proper now."
end result = lg_weather_agent.run_query(app, input_query)
actual_output = end result(-1)

print(f"Precise: {actual_output} Anticipated: {expected_output}")
test_case = LLMTestCase(
enter=input_query,
actual_output=actual_output,
expected_output=expected_output
)

llm_as_judge.measure(test_case)
print(llm_as_judge.rating)

Im Wesentlichen würde ich für jeden LLM-Aufruf ein Mock-Objekt (chicago_weather im obigen Beispiel) erstellen und den Aufruf an das LLM (retrieve_weather_data im obigen Beispiel) mit dem hartcodierten Objekt patchen, wann immer ich diesen Teil des Agenten-Workflows verspotten musste. Die Abhängigkeitsinjektion ist allgegenwärtig, Sie benötigen eine Menge fest codierter Objekte und der Aufruf-Workflow wird extrem schwer zu verfolgen. Beachten Sie, dass es keine Möglichkeit gibt, eine Funktion wie diese zu testen, wenn Sie keine Abhängigkeitsinjektion haben: Offensichtlich gibt der externe Dienst das aktuelle Wetter zurück und es gibt keine Möglichkeit, die richtige Antwort auf eine Frage wie „Ob“ zu ermitteln oder auch nicht, es regnet gerade.

Additionally … gibt es ein Agent-Framework, das die Abhängigkeitsinjektion unterstützt, pythonisch ist, Low-Degree-Zugriff auf LLMs bietet, modellagnostisch ist, die Erstellung einer Bewertung nach der anderen unterstützt und einfach zu verwenden und zu befolgen ist?

Quick. PydanticAI erfüllt die ersten drei Anforderungen; der vierte (Low-Degree-LLM-Zugriff) ist nicht möglich, aber das Design schließt dies nicht aus. Im Relaxation dieses Artikels zeige ich Ihnen, wie Sie damit eine evaluierungsgesteuerte Entwicklung einer Agentenanwendung durchführen können.

1. Ihre erste PydanticAI-Anwendung

Beginnen wir mit der Erstellung einer einfachen PydanticAI-Anwendung. Dabei werden mithilfe eines LLM Fragen zu Bergen beantwortet:

    agent = llm_utils.agent()
query = "What's the tallest mountain in British Columbia?"
print(">> ", query)
reply = agent.run_sync(query)
print(reply.knowledge)

Im obigen Code erstelle ich einen Agenten (ich zeige Ihnen gleich, wie das geht) und rufe dann run_sync auf, übergebe die Benutzeraufforderung und erhalte die Antwort des LLM. run_sync ist eine Möglichkeit, den Agenten das LLM aufrufen zu lassen und auf die Antwort zu warten. Andere Möglichkeiten bestehen darin, die Abfrage asynchron auszuführen oder ihre Antwort zu streamen. (Vollständiger Code ist hier, wenn Sie mitmachen möchten).

Führen Sie den obigen Code aus und Sie erhalten etwas wie:

>>  What's the tallest mountain in British Columbia?
The tallest mountain in British Columbia is **Mount Robson**, at 3,954 metres (12,972 toes).

Um den Agenten zu erstellen, erstellen Sie ein Modell und weisen Sie den Agenten dann an, dieses Modell für alle seine Schritte zu verwenden.

import pydantic_ai
from pydantic_ai.fashions.gemini import GeminiModel

def default_model() -> pydantic_ai.fashions.Mannequin:
mannequin = GeminiModel('gemini-1.5-flash', api_key=os.getenv('GOOGLE_API_KEY'))
return mannequin

def agent() -> pydantic_ai.Agent:
return pydantic_ai.Agent(default_model())

Die Idee hinter default_model() besteht darin, ein relativ kostengünstiges, aber schnelles Modell wie Gemini Flash als Normal zu verwenden. Anschließend können Sie das in bestimmten Schritten verwendete Modell nach Bedarf ändern, indem Sie run_sync() ein anderes Modell übergeben.

Unterstützung des PydanticAI-Modells sieht spärlich ausaber die am häufigsten verwendeten Modelle – die aktuellen Frontier-Modelle von OpenAI, Groq, Gemini, Mistral, Ollama und Anthropic – werden alle unterstützt. Über Ollama erhalten Sie Zugriff auf Llama3, Starcoder2, Gemma2 und Phi3. Es scheint nichts Wesentliches zu fehlen.

2. Pydantic mit strukturierten Ausgaben

Das Beispiel im vorherigen Abschnitt gab Freiformtext zurück. In den meisten Agenten-Workflows möchten Sie, dass das LLM strukturierte Daten zurückgibt, damit Sie diese direkt in Programmen verwenden können.

Wenn man bedenkt, dass diese API von Pydantic stammt, ist die Rückgabe einer strukturierten Ausgabe recht einfach. Definieren Sie einfach die gewünschte Ausgabe als Datenklasse (vollständiger Code lautet Hier):

from dataclasses import dataclass

@dataclass
class Mountain:
identify: str
location: str
top: float

Wenn Sie den Agenten erstellen, teilen Sie ihm den gewünschten Ausgabetyp mit:

agent = Agent(llm_utils.default_model(),
result_type=Mountain,
system_prompt=(
"You're a mountaineering information, who offers correct data to most people.",
"Present all distances and heights in meters",
"Present location as distance and course from nearest massive metropolis",
))

Beachten Sie auch die Verwendung der Systemaufforderung zur Angabe von Einheiten usw.

Wenn wir dies auf drei Fragen anwenden, erhalten wir:

>>  Inform me in regards to the tallest mountain in British Columbia?
Mountain(identify='Mount Robson', location='130km North of Vancouver', top=3999.0)
>> Is Mt. Hood simple to climb?
Mountain(identify='Mt. Hood', location='60 km east of Portland', top=3429.0)
>> What is the tallest peak within the Enchantments?
Mountain(identify='Mount Stuart', location='100 km east of Seattle', top=3000.0)

Aber wie intestine ist dieser Agent? Ist die Höhe des Mt. Robson korrekt? Ist Mt. Stuart wirklich der höchste Gipfel der Enchantments? All diese Informationen könnten halluziniert gewesen sein!

Es gibt für Sie keine Möglichkeit herauszufinden, wie intestine eine Agentenanwendung ist, es sei denn, Sie bewerten den Agenten anhand von Referenzantworten. Man kann es nicht einfach „ins Auge fassen“. Leider greifen hier viele LLM-Frameworks zu kurz – sie erschweren die Evaluierung bei der Entwicklung der LLM-Anwendung.

3. Bewerten Sie anhand von Referenzantworten

Wenn Sie beginnen, anhand von Referenzantworten zu evaluieren, zeigt PydanticAI seine Stärken. Alles ist ziemlich pythonisch, sodass Sie ganz einfach benutzerdefinierte Bewertungsmetriken erstellen können.

So bewerten wir beispielsweise ein zurückgegebenes Bergobjekt anhand von drei Kriterien und erstellen eine zusammengesetzte Bewertung (Vollständiger Code ist hier):

def consider(reply: Mountain, reference_answer: Mountain) -> Tuple(float, str):
rating = 0
purpose = ()
if reference_answer.identify in reply.identify:
rating += 0.5
purpose.append("Appropriate mountain recognized")
if reference_answer.location in reply.location:
rating += 0.25
purpose.append("Appropriate metropolis recognized")
height_error = abs(reference_answer.top - reply.top)
if height_error < 10:
rating += 0.25 * (10 - height_error)/10.0
purpose.append(f"Top was {height_error}m off. Appropriate reply is {reference_answer.top}")
else:
purpose.append(f"Unsuitable mountain recognized. Appropriate reply is {reference_answer.identify}")

return rating, ';'.be part of(purpose)

Jetzt können wir dies mit einem Datensatz aus Fragen und Referenzantworten ausführen:

    questions = (
"Inform me in regards to the tallest mountain in British Columbia?",
"Is Mt. Hood simple to climb?",
"What is the tallest peak within the Enchantments?"
)

reference_answers = (
Mountain("Robson", "Vancouver", 3954),
Mountain("Hood", "Portland", 3429),
Mountain("Dragontail", "Seattle", 2690)
)

total_score = 0
for l_question, l_reference_answer in zip(questions, reference_answers):
print(">> ", l_question)
l_answer = agent.run_sync(l_question)
print(l_answer.knowledge)
l_score, l_reason = consider(l_answer.knowledge, l_reference_answer)
print(l_score, ":", l_reason)
total_score += l_score

avg_score = total_score / len(questions)

Wenn wir das ausführen, erhalten wir:

>>  Inform me in regards to the tallest mountain in British Columbia?
Mountain(identify='Mount Robson', location='130 km North-East of Vancouver', top=3999.0)
0.75 : Appropriate mountain recognized;Appropriate metropolis recognized;Top was 45.0m off. Appropriate reply is 3954
>> Is Mt. Hood simple to climb?
Mountain(identify='Mt. Hood', location='60 km east of Portland, OR', top=3429.0)
1.0 : Appropriate mountain recognized;Appropriate metropolis recognized;Top was 0.0m off. Appropriate reply is 3429
>> What is the tallest peak within the Enchantments?
Mountain(identify='Dragontail Peak', location='14 km east of Leavenworth, WA', top=3008.0)
0.5 : Appropriate mountain recognized;Top was 318.0m off. Appropriate reply is 2690
Common rating: 0.75

Die Höhe des Mt. Robson beträgt 45 m; Die Höhe des Dragontail-Gipfels betrug 318 m. Wie würden Sie das beheben?

Das ist richtig. Sie würden eine RAG-Architektur verwenden oder den Agenten mit einem Software ausstatten, das die korrekten Höheninformationen bereitstellt. Lassen Sie uns den letzteren Ansatz verwenden und sehen, wie man das mit Pydantic macht.

Beachten Sie, wie uns die evaluierungsgesteuerte Entwicklung den Weg zur Verbesserung unserer Agentenanwendung zeigt.

4a. Verwendung eines Werkzeugs

PydanticAI unterstützt mehrere Möglichkeiten, einem Agenten Instruments bereitzustellen. Hier kommentiere ich eine Funktion, die immer dann aufgerufen wird, wenn sie die Höhe eines Berges benötigt (Den vollständigen Code finden Sie hier):

   agent = Agent(llm_utils.default_model(),
result_type=Mountain,
system_prompt=(
"You're a mountaineering information, who offers correct data to most people.",
"Use the supplied software to lookup the elevation of many mountains."
"Present all distances and heights in meters",
"Present location as distance and course from nearest massive metropolis",
))
@agent.software
def get_height_of_mountain(ctx: RunContext(Instruments), mountain_name: str) -> str:
return ctx.deps.elev_wiki.snippet(mountain_name)

Die Funktion macht jedoch etwas Seltsames. Es ruft ein Objekt namens elev_wiki aus dem Laufzeitkontext des Agenten ab. Dieses Objekt wird übergeben, wenn wir run_sync aufrufen:

class Instruments:
elev_wiki: wikipedia_tool.WikipediaContent
def __init__(self):
self.elev_wiki = OnlineWikipediaContent("Record of mountains by elevation")

instruments = Instruments() # Instruments or FakeTools

l_answer = agent.run_sync(l_question, deps=instruments) # word how we're capable of inject

Da der Laufzeitkontext an jeden Agentenaufruf oder Toolaufruf übergeben werden kann, können wir ihn für die Abhängigkeitsinjektion in PydanticAI verwenden. Sie werden dies im nächsten Abschnitt sehen.

Das Wiki selbst fragt lediglich Wikipedia on-line ab (Code hier) und extrahiert den Inhalt der Seite und übergibt die entsprechenden Berginformationen an den Agenten:

import wikipedia

class OnlineWikipediaContent(WikipediaContent):
def __init__(self, matter: str):
print(f"Will question on-line Wikipedia for data on {matter}")
self.web page = wikipedia.web page(matter)

def url(self) -> str:
return self.web page.url

def html(self) -> str:
return self.web page.html()

Wenn wir es ausführen, erhalten wir jetzt tatsächlich die richtigen Höhen:

Will question on-line Wikipedia for data on Record of mountains by elevation
>> Inform me in regards to the tallest mountain in British Columbia?
Mountain(identify='Mount Robson', location='100 km west of Jasper', top=3954.0)
0.75 : Appropriate mountain recognized;Top was 0.0m off. Appropriate reply is 3954
>> Is Mt. Hood simple to climb?
Mountain(identify='Mt. Hood', location='50 km ESE of Portland, OR', top=3429.0)
1.0 : Appropriate mountain recognized;Appropriate metropolis recognized;Top was 0.0m off. Appropriate reply is 3429
>> What is the tallest peak within the Enchantments?
Mountain(identify='Mount Stuart', location='Cascades, Washington, US', top=2869.0)
0 : Unsuitable mountain recognized. Appropriate reply is Dragontail
Common rating: 0.58

4b. Abhängigkeit, die einen Scheindienst einfügt

Es ist keine gute Idee, während der Entwicklung oder beim Testen jedes Mal auf den API-Aufruf an Wikipedia zu warten. Stattdessen möchten wir die Wikipedia-Antwort nachahmen, damit wir uns schnell weiterentwickeln können und sicher sein können, dass wir das Ergebnis erhalten.

Das geht ganz einfach. Wir erstellen ein Faux-Pendant zum Wikipedia-Dienst:

class FakeWikipediaContent(WikipediaContent):
def __init__(self, matter: str):
if matter == "Record of mountains by elevation":
print(f"Will used cached Wikipedia data on {matter}")
self.url_ = "https://en.wikipedia.org/wiki/List_of_mountains_by_elevation"
with open("mountains.html", "rb") as ifp:
self.html_ = ifp.learn().decode("utf-8")

def url(self) -> str:
return self.url_

def html(self) -> str:
return self.html_

Fügen Sie dann dieses gefälschte Objekt während der Entwicklung in den Laufzeitkontext des Agenten ein:

class FakeTools:
elev_wiki: wikipedia_tool.WikipediaContent
def __init__(self):
self.elev_wiki = FakeWikipediaContent("Record of mountains by elevation")

instruments = FakeTools() # Instruments or FakeTools

l_answer = agent.run_sync(l_question, deps=instruments) # word how we're capable of inject

Dieses Mal verwendet die Auswertung bei der Ausführung den zwischengespeicherten Wikipedia-Inhalt:

Will used cached Wikipedia data on Record of mountains by elevation
>> Inform me in regards to the tallest mountain in British Columbia?
Mountain(identify='Mount Robson', location='100 km west of Jasper', top=3954.0)
0.75 : Appropriate mountain recognized;Top was 0.0m off. Appropriate reply is 3954
>> Is Mt. Hood simple to climb?
Mountain(identify='Mt. Hood', location='50 km ESE of Portland, OR', top=3429.0)
1.0 : Appropriate mountain recognized;Appropriate metropolis recognized;Top was 0.0m off. Appropriate reply is 3429
>> What is the tallest peak within the Enchantments?
Mountain(identify='Mount Stuart', location='Cascades, Washington, US', top=2869.0)
0 : Unsuitable mountain recognized. Appropriate reply is Dragontail
Common rating: 0.58

Schauen Sie sich die obige Ausgabe genau an – es gibt andere Fehler als im Zero-Shot-Beispiel. In Abschnitt 2 wählte das LLM Vancouver als die dem Mt. Robson am nächsten gelegene Stadt und Dragontail als den höchsten Gipfel der Enchantments. Diese Antworten waren zufällig richtig. Jetzt werden Jasper und Mt. Stuart ausgewählt. Wir müssen mehr Arbeit leisten, um diese Fehler zu beheben – aber die bewertungsgesteuerte Entwicklung gibt uns zumindest eine Richtung vor.

Aktuelle Einschränkungen

PydanticAI ist sehr neu. Es gibt ein paar Stellen, wo es verbessert werden könnte:

  • Es gibt keinen Low-Degree-Zugriff auf das Modell selbst. Beispielsweise unterstützen verschiedene grundlegende Modelle Kontext-Caching, Immediate-Caching usw. Die Modellabstraktion in PydanticAI bietet keine Möglichkeit, diese auf dem Modell festzulegen. Im Idealfall können wir eine Kwargs-Methode finden, um solche Einstellungen vorzunehmen.
  • Die Notwendigkeit, zwei Versionen von Agentenabhängigkeiten zu erstellen, eine echte und eine gefälschte, ist weit verbreitet. Es wäre intestine, wenn wir ein Software annotieren oder eine einfache Möglichkeit bieten könnten, flächendeckend zwischen den beiden Arten von Diensten zu wechseln.
  • Während der Entwicklung benötigen Sie nicht so viel Protokollierung. Wenn Sie jedoch den Agenten ausführen, möchten Sie normalerweise die Eingabeaufforderungen und Antworten protokollieren. Manchmal möchten Sie die Zwischenantworten protokollieren. Der Weg dazu scheint ein kommerzielles Produkt namens Logfire zu sein. Ideally suited wäre ein OSS-Cloud-agnostisches Protokollierungsframework, das in die PydanticAI-Bibliothek integriert werden kann.

Es ist möglich, dass diese bereits vorhanden sind und ich sie übersehen habe, oder vielleicht sind sie bereits implementiert, wenn Sie diesen Artikel lesen. Hinterlassen Sie in jedem Fall einen Kommentar für zukünftige Leser.

Insgesamt gefällt mir PydanticAI – es bietet eine sehr saubere und pythonische Möglichkeit, Agentenanwendungen auf bewertungsgesteuerte Weise zu erstellen.

Vorgeschlagene nächste Schritte:

  1. Dies ist einer dieser Blogbeiträge, bei denen Sie von der tatsächlichen Ausführung der Beispiele profitieren werden, da darin ein Entwicklungsprozess und eine neue Bibliothek beschrieben werden. Dieses GitHub-Repo enthält das PydanticAI-Beispiel, das ich in diesem Beitrag durchgegangen bin: https://github.com/lakshmanok/lakblogs/tree/predominant/pydantic_ai_mountains Befolgen Sie die Anweisungen in der README-Datei, um es auszuprobieren.
  2. Pydantic AI-Dokumentation: https://ai.pydantic.dev/
  3. Patchen eines Langchain-Workflows mit Mock-Objekten. Meine „Vorher“-Lösung: https://github.com/lakshmanok/lakblogs/blob/predominant/genai_agents/eval_weather_agent.py

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert