LangGraph のグラフを Colab 上で実行してみます。このチュートリアルでは、SQL データベースについて質問に答えられるエージェントを構築する手順を示します。
LangGraph on Colab : チュートリアル : SQL エージェントの構築
作成 : クラスキャット・セールスインフォメーション
作成日時 : 06/27/2025
* 本記事は langchain-ai.github.io の以下のページを独自に翻訳した上で、補足説明を加えてまとめ直しています :
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
◆ お問合せ : 下記までお願いします。
- クラスキャット セールス・インフォメーション
- sales-info@classcat.com
- ClassCatJP
LangGraph on Colab : チュートリアル : SQL エージェントの構築
このチュートリアルでは、SQL データベースについて質問に答えられるエージェントを構築する手順を示します。
大まかな流れとしては、エージェントは :
- データベースから利用可能なテーブルを取得する
- どのテーブルが質問に関連するか決定する
- 関連テーブルのスキーマを取得する
- 質問とスキーマの情報に基づいて、クエリーを生成する
- LLM を使用して、よくある間違いがないかクエリーをダブルチェックする
- クエリーを実行して結果を返す
- クエリーが成功するまで、データベースエンジンが検出した間違いを正す
- 結果に基づいてレスポンスを作成する
1. セットアップ
最初に幾つかの依存関係をインストールしましょう。このチュートリアルでは langchain-community の SQL データベースとツール抽象化を使用します。LangChain チャットモデル も必要とします。
%%capture --no-stderr
%pip install -U langgraph langchain_community "langchain[openai]"
LLM の選択
まず LLM を初期化 します。ツール呼び出し をサポートする任意のモデルが動作するはずです。以下では OpenAI を使用します。
from langchain.chat_models import init_chat_model
llm = init_chat_model("openai:gpt-4.1")
データベースの設定
このチュートリアル用に SQLite データベースを作成していきます。SQLite は簡単に設定して利用できる軽量データベースです。chinook (シヌーク) データベースをロードします、これはデジタルメディアストアを表すサンプルデータベースです。Find more information about the database here.
利便性のため、データベース (Chinook.db) をパブリック GCS バケットにホストしました。
import requests
url = "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"
response = requests.get(url)
if response.status_code == 200:
# Open a local file in binary write mode
with open("Chinook.db", "wb") as file:
# Write the content of the response (the file) to the local file
file.write(response.content)
print("File downloaded and saved as Chinook.db")
else:
print(f"Failed to download the file. Status code: {response.status_code}")
データベースとのやり取りには langchain_community パッケージで利用可能な便利な SQL データベース・ラッパーを使用します。ラッパーは SQL クエリーを実行して結果を取得する単純なインターフェイスを提供します :
from langchain_community.utilities import SQLDatabase
db = SQLDatabase.from_uri("sqlite:///Chinook.db")
print(f"Dialect: {db.dialect}")
print(f"Available tables: {db.get_usable_table_names()}")
print(f'Sample output: {db.run("SELECT * FROM Artist LIMIT 5;")}')
Dialect: sqlite Available tables: ['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track'] Sample output: [(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains')]
データベース操作のためのツール
langchain-community は SQLDatabase を操作するための組み込みツールを幾つか実装しています、これらはテーブルのリストアップ、テーブルスキーマの読み取り、そしてクエリーのチェックと実行のためのツールを含みます :
from langchain_community.agent_toolkits import SQLDatabaseToolkit
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()
for tool in tools:
print(f"{tool.name}: {tool.description}\n")
sql_db_query: Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again. If you encounter an issue with Unknown column 'xxxx' in 'field list', use sql_db_schema to query the correct table fields. sql_db_schema: Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3 sql_db_list_tables: Input is an empty string, output is a comma-separated list of tables in the database. sql_db_query_checker: Use this tool to double check if your query is correct before executing it. Always use this tool before executing a query with sql_db_query!
2. 事前構築済みエージェントの使用
これらのツールがあれば、事前訓練済みエージェントを単一行で初期化できます。エージェントの動作をカスタマイズするには、説明的なシステムプロンプトを記述します。
from langgraph.prebuilt import create_react_agent
system_prompt = """
You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run,
then look at the results of the query and return the answer. Unless the user
specifies a specific number of examples they wish to obtain, always limit your
query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting
examples in the database. Never query for all the columns from a specific table,
only ask for the relevant columns given the question.
You MUST double check your query before executing it. If you get an error while
executing a query, rewrite the query and try again.
DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the
database.
To start you should ALWAYS look at the tables in the database to see what you
can query. Do NOT skip this step.
Then you should query the schema of the most relevant tables.
""".format(
dialect=db.dialect,
top_k=5,
)
agent = create_react_agent(
llm,
tools,
prompt=system_prompt,
)
このエージェントをサンプル・クエリーで実行し、動作を観察してみましょう :
question = "Which sales agent made the most in sales in 2009?"
for step in agent.stream(
{"messages": [{"role": "user", "content": question}]},
stream_mode="values",
):
step["messages"][-1].pretty_print()
================================ Human Message ================================= Which sales agent made the most in sales in 2009? ================================== Ai Message ================================== Tool Calls: sql_db_list_tables (call_0MfnhUYmRNVe03m6fYClaoa0) Call ID: call_0MfnhUYmRNVe03m6fYClaoa0 Args: tool_input: ================================= Tool Message ================================= Name: sql_db_list_tables Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track ================================== Ai Message ================================== Tool Calls: sql_db_schema (call_XwI6uES0HeZ2xCRxkkdWzB3I) Call ID: call_XwI6uES0HeZ2xCRxkkdWzB3I Args: table_names: Employee, Invoice, InvoiceLine ================================= Tool Message ================================= Name: sql_db_schema CREATE TABLE "Employee" ( "EmployeeId" INTEGER NOT NULL, "LastName" NVARCHAR(20) NOT NULL, "FirstName" NVARCHAR(20) NOT NULL, "Title" NVARCHAR(30), "ReportsTo" INTEGER, "BirthDate" DATETIME, "HireDate" DATETIME, "Address" NVARCHAR(70), "City" NVARCHAR(40), "State" NVARCHAR(40), "Country" NVARCHAR(40), "PostalCode" NVARCHAR(10), "Phone" NVARCHAR(24), "Fax" NVARCHAR(24), "Email" NVARCHAR(60), PRIMARY KEY ("EmployeeId"), FOREIGN KEY("ReportsTo") REFERENCES "Employee" ("EmployeeId") ) /* 3 rows from Employee table: EmployeeId LastName FirstName Title ReportsTo BirthDate HireDate Address City State Country PostalCode Phone Fax Email 1 Adams Andrew General Manager None 1962-02-18 00:00:00 2002-08-14 00:00:00 11120 Jasper Ave NW Edmonton AB Canada T5K 2N1 +1 (780) 428-9482 +1 (780) 428-3457 andrew@chinookcorp.com 2 Edwards Nancy Sales Manager 1 1958-12-08 00:00:00 2002-05-01 00:00:00 825 8 Ave SW Calgary AB Canada T2P 2T3 +1 (403) 262-3443 +1 (403) 262-3322 nancy@chinookcorp.com 3 Peacock Jane Sales Support Agent 2 1973-08-29 00:00:00 2002-04-01 00:00:00 1111 6 Ave SW Calgary AB Canada T2P 5M5 +1 (403) 262-3443 +1 (403) 262-6712 jane@chinookcorp.com */ CREATE TABLE "Invoice" ( "InvoiceId" INTEGER NOT NULL, "CustomerId" INTEGER NOT NULL, "InvoiceDate" DATETIME NOT NULL, "BillingAddress" NVARCHAR(70), "BillingCity" NVARCHAR(40), "BillingState" NVARCHAR(40), "BillingCountry" NVARCHAR(40), "BillingPostalCode" NVARCHAR(10), "Total" NUMERIC(10, 2) NOT NULL, PRIMARY KEY ("InvoiceId"), FOREIGN KEY("CustomerId") REFERENCES "Customer" ("CustomerId") ) /* 3 rows from Invoice table: InvoiceId CustomerId InvoiceDate BillingAddress BillingCity BillingState BillingCountry BillingPostalCode Total 1 2 2009-01-01 00:00:00 Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 1.98 2 4 2009-01-02 00:00:00 Ullevålsveien 14 Oslo None Norway 0171 3.96 3 8 2009-01-03 00:00:00 Grétrystraat 63 Brussels None Belgium 1000 5.94 */ CREATE TABLE "InvoiceLine" ( "InvoiceLineId" INTEGER NOT NULL, "InvoiceId" INTEGER NOT NULL, "TrackId" INTEGER NOT NULL, "UnitPrice" NUMERIC(10, 2) NOT NULL, "Quantity" INTEGER NOT NULL, PRIMARY KEY ("InvoiceLineId"), FOREIGN KEY("TrackId") REFERENCES "Track" ("TrackId"), FOREIGN KEY("InvoiceId") REFERENCES "Invoice" ("InvoiceId") ) /* 3 rows from InvoiceLine table: InvoiceLineId InvoiceId TrackId UnitPrice Quantity 1 1 2 0.99 1 2 1 4 0.99 1 3 2 6 0.99 1 */ ================================== Ai Message ================================== Tool Calls: sql_db_schema (call_dHE5FKSycFhYIkdPKtpEk9rM) Call ID: call_dHE5FKSycFhYIkdPKtpEk9rM Args: table_names: Customer ================================= Tool Message ================================= Name: sql_db_schema CREATE TABLE "Customer" ( "CustomerId" INTEGER NOT NULL, "FirstName" NVARCHAR(40) NOT NULL, "LastName" NVARCHAR(20) NOT NULL, "Company" NVARCHAR(80), "Address" NVARCHAR(70), "City" NVARCHAR(40), "State" NVARCHAR(40), "Country" NVARCHAR(40), "PostalCode" NVARCHAR(10), "Phone" NVARCHAR(24), "Fax" NVARCHAR(24), "Email" NVARCHAR(60) NOT NULL, "SupportRepId" INTEGER, PRIMARY KEY ("CustomerId"), FOREIGN KEY("SupportRepId") REFERENCES "Employee" ("EmployeeId") ) /* 3 rows from Customer table: CustomerId FirstName LastName Company Address City State Country PostalCode Phone Fax Email SupportRepId 1 Luís Gonçalves Embraer - Empresa Brasileira de Aeronáutica S.A. Av. Brigadeiro Faria Lima, 2170 São José dos Campos SP Brazil 12227-000 +55 (12) 3923-5555 +55 (12) 3923-5566 luisg@embraer.com.br 3 2 Leonie Köhler None Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 +49 0711 2842222 None leonekohler@surfeu.de 5 3 François Tremblay None 1498 rue Bélanger Montréal QC Canada H2G 1A7 +1 (514) 721-4711 None ftremblay@gmail.com 3 */ ================================== Ai Message ================================== Tool Calls: sql_db_query_checker (call_QPtWY5cUZWgWhr13Kz6eBJkU) Call ID: call_QPtWY5cUZWgWhr13Kz6eBJkU Args: query: SELECT e.EmployeeId, e.FirstName, e.LastName, SUM(i.Total) as TotalSales FROM Employee e JOIN Customer c ON e.EmployeeId = c.SupportRepId JOIN Invoice i ON c.CustomerId = i.CustomerId WHERE i.InvoiceDate BETWEEN '2009-01-01' AND '2009-12-31' GROUP BY e.EmployeeId, e.FirstName, e.LastName ORDER BY TotalSales DESC LIMIT 1; ================================= Tool Message ================================= Name: sql_db_query_checker ```sql SELECT e.EmployeeId, e.FirstName, e.LastName, SUM(i.Total) as TotalSales FROM Employee e JOIN Customer c ON e.EmployeeId = c.SupportRepId JOIN Invoice i ON c.CustomerId = i.CustomerId WHERE i.InvoiceDate BETWEEN '2009-01-01' AND '2009-12-31' GROUP BY e.EmployeeId, e.FirstName, e.LastName ORDER BY TotalSales DESC LIMIT 1; ``` ================================== Ai Message ================================== Tool Calls: sql_db_query (call_X747YMElSeY5D3CfhE6KzLUj) Call ID: call_X747YMElSeY5D3CfhE6KzLUj Args: query: SELECT e.EmployeeId, e.FirstName, e.LastName, SUM(i.Total) as TotalSales FROM Employee e JOIN Customer c ON e.EmployeeId = c.SupportRepId JOIN Invoice i ON c.CustomerId = i.CustomerId WHERE i.InvoiceDate BETWEEN '2009-01-01' AND '2009-12-31' GROUP BY e.EmployeeId, e.FirstName, e.LastName ORDER BY TotalSales DESC LIMIT 1; ================================= Tool Message ================================= Name: sql_db_query [(5, 'Steve', 'Johnson', 164.34)] ================================== Ai Message ================================== The sales agent who made the most in sales in 2009 was Steve Johnson, with total sales of 164.34.
これは十分に機能しました : エージェントは正しくテーブルをリストし、スキーマを取得し、クエリーを記述し、クエリーをチェックし、そしてそれを実行して最終的なレスポンスを通知しました。
3. エージェントのカスタマイズ
事前構築済みエージェントはすぐに始められますが、各ステップでエージェントはツールの完全なセットへのアクセスを持ちます。上記では、動作を制約するためにシステムプロンプトに依存しました – 例えば、エージェントに常に “list tables” ツールから始めるように指示し、クエリーを実行する前には常に query-checker ツールを実行するように指示しました。
LangGraph では、エージェントのカスタマイズにより、より高度な制御を実施できます。下記では、特定のツール呼び出しのための専用ノードを持つ、単純な React-エージェントのセットアップを実装します。事前構築済みエージェントと同じ状態を使用します。
以下のステップで専用ノードを構築します :
- DB テーブルのリストアップ
- “get schema” ツールの呼び出し
- クエリーの生成
- クエリーの確認
これらのステップを専用ノードに配置すると、(1) 必要に応じてツール呼び出しを強制し、(2) 各ステップに関連するプロンプトをカスタマイズすることを可能にします。
from typing import Literal
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
get_schema_tool = next(tool for tool in tools if tool.name == "sql_db_schema")
get_schema_node = ToolNode([get_schema_tool], name="get_schema")
run_query_tool = next(tool for tool in tools if tool.name == "sql_db_query")
run_query_node = ToolNode([run_query_tool], name="run_query")
# Example: create a predetermined tool call
def list_tables(state: MessagesState):
tool_call = {
"name": "sql_db_list_tables",
"args": {},
"id": "abc123",
"type": "tool_call",
}
tool_call_message = AIMessage(content="", tool_calls=[tool_call])
list_tables_tool = next(tool for tool in tools if tool.name == "sql_db_list_tables")
tool_message = list_tables_tool.invoke(tool_call)
response = AIMessage(f"Available tables: {tool_message.content}")
return {"messages": [tool_call_message, tool_message, response]}
# Example: force a model to create a tool call
def call_get_schema(state: MessagesState):
# Note that LangChain enforces that all models accept `tool_choice="any"`
# as well as `tool_choice=`.
llm_with_tools = llm.bind_tools([get_schema_tool], tool_choice="any")
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
generate_query_system_prompt = """
You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run,
then look at the results of the query and return the answer. Unless the user
specifies a specific number of examples they wish to obtain, always limit your
query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting
examples in the database. Never query for all the columns from a specific table,
only ask for the relevant columns given the question.
DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.
""".format(
dialect=db.dialect,
top_k=5,
)
def generate_query(state: MessagesState):
system_message = {
"role": "system",
"content": generate_query_system_prompt,
}
# We do not force a tool call here, to allow the model to
# respond naturally when it obtains the solution.
llm_with_tools = llm.bind_tools([run_query_tool])
response = llm_with_tools.invoke([system_message] + state["messages"])
return {"messages": [response]}
check_query_system_prompt = """
You are a SQL expert with a strong attention to detail.
Double check the {dialect} query for common mistakes, including:
- Using NOT IN with NULL values
- Using UNION when UNION ALL should have been used
- Using BETWEEN for exclusive ranges
- Data type mismatch in predicates
- Properly quoting identifiers
- Using the correct number of arguments for functions
- Casting to the correct data type
- Using the proper columns for joins
If there are any of the above mistakes, rewrite the query. If there are no mistakes,
just reproduce the original query.
You will call the appropriate tool to execute the query after running this check.
""".format(dialect=db.dialect)
def check_query(state: MessagesState):
system_message = {
"role": "system",
"content": check_query_system_prompt,
}
# Generate an artificial user message to check
tool_call = state["messages"][-1].tool_calls[0]
user_message = {"role": "user", "content": tool_call["args"]["query"]}
llm_with_tools = llm.bind_tools([run_query_tool], tool_choice="any")
response = llm_with_tools.invoke([system_message, user_message])
response.id = state["messages"][-1].id
return {"messages": [response]}
最後に、これらのステップを Graph API を使用してワークフローに組み立てます。クエリー生成ステップで条件付きエッジを定義します、それはクエリーが生成された場合はクエリーチェッカーにルーティングし、ツール呼び出しが存在しない場合は終了し、LLM がクエリーにレスポンスを返したことを確認します。
def should_continue(state: MessagesState) -> Literal[END, "check_query"]:
messages = state["messages"]
last_message = messages[-1]
if not last_message.tool_calls:
return END
else:
return "check_query"
builder = StateGraph(MessagesState)
builder.add_node(list_tables)
builder.add_node(call_get_schema)
builder.add_node(get_schema_node, "get_schema")
builder.add_node(generate_query)
builder.add_node(check_query)
builder.add_node(run_query_node, "run_query")
builder.add_edge(START, "list_tables")
builder.add_edge("list_tables", "call_get_schema")
builder.add_edge("call_get_schema", "get_schema")
builder.add_edge("get_schema", "generate_query")
builder.add_conditional_edges(
"generate_query",
should_continue,
)
builder.add_edge("check_query", "run_query")
builder.add_edge("run_query", "generate_query")
agent = builder.compile()
We visualize the application below:
from IPython.display import Image, display
from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles
display(Image(agent.get_graph().draw_mermaid_png()))
We can now invoke the graph exactly as before:
question = "Which sales agent made the most in sales in 2009?"
for step in agent.stream(
{"messages": [{"role": "user", "content": question}]},
stream_mode="values",
):
step["messages"][-1].pretty_print()
================================ Human Message ================================= Which sales agent made the most in sales in 2009? ================================== Ai Message ================================== Available tables: Album, Artist, Customer, Employee, Genre, Invoice, InvoiceLine, MediaType, Playlist, PlaylistTrack, Track ================================== Ai Message ================================== Tool Calls: sql_db_schema (call_rMs3sF1HVcAGggGf89ZNvtLE) Call ID: call_rMs3sF1HVcAGggGf89ZNvtLE Args: table_names: Employee,Invoice,Customer ================================= Tool Message ================================= Name: sql_db_schema CREATE TABLE "Customer" ( "CustomerId" INTEGER NOT NULL, "FirstName" NVARCHAR(40) NOT NULL, "LastName" NVARCHAR(20) NOT NULL, "Company" NVARCHAR(80), "Address" NVARCHAR(70), "City" NVARCHAR(40), "State" NVARCHAR(40), "Country" NVARCHAR(40), "PostalCode" NVARCHAR(10), "Phone" NVARCHAR(24), "Fax" NVARCHAR(24), "Email" NVARCHAR(60) NOT NULL, "SupportRepId" INTEGER, PRIMARY KEY ("CustomerId"), FOREIGN KEY("SupportRepId") REFERENCES "Employee" ("EmployeeId") ) /* 3 rows from Customer table: CustomerId FirstName LastName Company Address City State Country PostalCode Phone Fax Email SupportRepId 1 Luís Gonçalves Embraer - Empresa Brasileira de Aeronáutica S.A. Av. Brigadeiro Faria Lima, 2170 São José dos Campos SP Brazil 12227-000 +55 (12) 3923-5555 +55 (12) 3923-5566 luisg@embraer.com.br 3 2 Leonie Köhler None Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 +49 0711 2842222 None leonekohler@surfeu.de 5 3 François Tremblay None 1498 rue Bélanger Montréal QC Canada H2G 1A7 +1 (514) 721-4711 None ftremblay@gmail.com 3 */ CREATE TABLE "Employee" ( "EmployeeId" INTEGER NOT NULL, "LastName" NVARCHAR(20) NOT NULL, "FirstName" NVARCHAR(20) NOT NULL, "Title" NVARCHAR(30), "ReportsTo" INTEGER, "BirthDate" DATETIME, "HireDate" DATETIME, "Address" NVARCHAR(70), "City" NVARCHAR(40), "State" NVARCHAR(40), "Country" NVARCHAR(40), "PostalCode" NVARCHAR(10), "Phone" NVARCHAR(24), "Fax" NVARCHAR(24), "Email" NVARCHAR(60), PRIMARY KEY ("EmployeeId"), FOREIGN KEY("ReportsTo") REFERENCES "Employee" ("EmployeeId") ) /* 3 rows from Employee table: EmployeeId LastName FirstName Title ReportsTo BirthDate HireDate Address City State Country PostalCode Phone Fax Email 1 Adams Andrew General Manager None 1962-02-18 00:00:00 2002-08-14 00:00:00 11120 Jasper Ave NW Edmonton AB Canada T5K 2N1 +1 (780) 428-9482 +1 (780) 428-3457 andrew@chinookcorp.com 2 Edwards Nancy Sales Manager 1 1958-12-08 00:00:00 2002-05-01 00:00:00 825 8 Ave SW Calgary AB Canada T2P 2T3 +1 (403) 262-3443 +1 (403) 262-3322 nancy@chinookcorp.com 3 Peacock Jane Sales Support Agent 2 1973-08-29 00:00:00 2002-04-01 00:00:00 1111 6 Ave SW Calgary AB Canada T2P 5M5 +1 (403) 262-3443 +1 (403) 262-6712 jane@chinookcorp.com */ CREATE TABLE "Invoice" ( "InvoiceId" INTEGER NOT NULL, "CustomerId" INTEGER NOT NULL, "InvoiceDate" DATETIME NOT NULL, "BillingAddress" NVARCHAR(70), "BillingCity" NVARCHAR(40), "BillingState" NVARCHAR(40), "BillingCountry" NVARCHAR(40), "BillingPostalCode" NVARCHAR(10), "Total" NUMERIC(10, 2) NOT NULL, PRIMARY KEY ("InvoiceId"), FOREIGN KEY("CustomerId") REFERENCES "Customer" ("CustomerId") ) /* 3 rows from Invoice table: InvoiceId CustomerId InvoiceDate BillingAddress BillingCity BillingState BillingCountry BillingPostalCode Total 1 2 2009-01-01 00:00:00 Theodor-Heuss-Straße 34 Stuttgart None Germany 70174 1.98 2 4 2009-01-02 00:00:00 Ullevålsveien 14 Oslo None Norway 0171 3.96 3 8 2009-01-03 00:00:00 Grétrystraat 63 Brussels None Belgium 1000 5.94 */ ================================== Ai Message ================================== Tool Calls: sql_db_query (call_TQyoJSV78xEWHqc69PiYHjdS) Call ID: call_TQyoJSV78xEWHqc69PiYHjdS Args: query: SELECT e.FirstName, e.LastName, SUM(i.Total) as TotalSales FROM Employee e JOIN Customer c ON e.EmployeeId = c.SupportRepId JOIN Invoice i ON c.CustomerId = i.CustomerId WHERE strftime('%Y', i.InvoiceDate) = '2009' GROUP BY e.EmployeeId ORDER BY TotalSales DESC LIMIT 1; ================================== Ai Message ================================== Tool Calls: sql_db_query (call_tSQOuxc7iQ1jvLzXSofCqK4V) Call ID: call_tSQOuxc7iQ1jvLzXSofCqK4V Args: query: SELECT e.FirstName, e.LastName, SUM(i.Total) as TotalSales FROM Employee e JOIN Customer c ON e.EmployeeId = c.SupportRepId JOIN Invoice i ON c.CustomerId = i.CustomerId WHERE strftime('%Y', i.InvoiceDate) = '2009' GROUP BY e.EmployeeId ORDER BY TotalSales DESC LIMIT 1; ================================= Tool Message ================================= Name: sql_db_query [('Steve', 'Johnson', 164.34)] ================================== Ai Message ================================== The sales agent who made the most in sales in 2009 was Steve Johnson, with total sales of 164.34.
以上