Skip to content

Commit 3a30e6d

Browse files
authored
Harrison/openai callback (#684)
1 parent aef82f5 commit 3a30e6d

File tree

5 files changed

+285
-1
lines changed

5 files changed

+285
-1
lines changed

docs/modules/agents/getting_started.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
],
153153
"metadata": {
154154
"kernelspec": {
155-
"display_name": "Python 3.9.0 64-bit ('llm-env')",
155+
"display_name": "Python 3 (ipykernel)",
156156
"language": "python",
157157
"name": "python3"
158158
},
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "e5715368",
6+
"metadata": {},
7+
"source": [
8+
"# Token Usage Tracking\n",
9+
"\n",
10+
"This notebook goes over how to track your token usage for specific calls. It is currently only implemented for the OpenAI API.\n",
11+
"\n",
12+
"Let's first look at an extremely simple example of tracking token usage for a single LLM call."
13+
]
14+
},
15+
{
16+
"cell_type": "code",
17+
"execution_count": 1,
18+
"id": "9455db35",
19+
"metadata": {},
20+
"outputs": [],
21+
"source": [
22+
"from langchain.llms import OpenAI\n",
23+
"from langchain.callbacks import get_openai_callback"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": 2,
29+
"id": "d1c55cc9",
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"llm = OpenAI(model_name=\"text-davinci-002\", n=2, best_of=2)"
34+
]
35+
},
36+
{
37+
"cell_type": "code",
38+
"execution_count": 4,
39+
"id": "31667d54",
40+
"metadata": {},
41+
"outputs": [
42+
{
43+
"name": "stdout",
44+
"output_type": "stream",
45+
"text": [
46+
"42\n"
47+
]
48+
}
49+
],
50+
"source": [
51+
"with get_openai_callback() as cb:\n",
52+
" result = llm(\"Tell me a joke\")\n",
53+
" print(cb.total_tokens)"
54+
]
55+
},
56+
{
57+
"cell_type": "markdown",
58+
"id": "c0ab6d27",
59+
"metadata": {},
60+
"source": [
61+
"Anything inside the context manager will get tracked. Here's an example of using it to track multiple calls in sequence."
62+
]
63+
},
64+
{
65+
"cell_type": "code",
66+
"execution_count": 6,
67+
"id": "e09420f4",
68+
"metadata": {},
69+
"outputs": [
70+
{
71+
"name": "stdout",
72+
"output_type": "stream",
73+
"text": [
74+
"83\n"
75+
]
76+
}
77+
],
78+
"source": [
79+
"with get_openai_callback() as cb:\n",
80+
" result = llm(\"Tell me a joke\")\n",
81+
" result2 = llm(\"Tell me a joke\")\n",
82+
" print(cb.total_tokens)"
83+
]
84+
},
85+
{
86+
"cell_type": "markdown",
87+
"id": "d8186e7b",
88+
"metadata": {},
89+
"source": [
90+
"If a chain or agent with multiple steps in it is used, it will track all those steps."
91+
]
92+
},
93+
{
94+
"cell_type": "code",
95+
"execution_count": 7,
96+
"id": "5d1125c6",
97+
"metadata": {},
98+
"outputs": [],
99+
"source": [
100+
"from langchain.agents import load_tools\n",
101+
"from langchain.agents import initialize_agent\n",
102+
"from langchain.llms import OpenAI\n",
103+
"\n",
104+
"llm = OpenAI(temperature=0)\n",
105+
"tools = load_tools([\"serpapi\", \"llm-math\"], llm=llm)\n",
106+
"agent = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True)"
107+
]
108+
},
109+
{
110+
"cell_type": "code",
111+
"execution_count": 8,
112+
"id": "2f98c536",
113+
"metadata": {},
114+
"outputs": [
115+
{
116+
"name": "stdout",
117+
"output_type": "stream",
118+
"text": [
119+
"\n",
120+
"\n",
121+
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
122+
"\u001b[32;1m\u001b[1;3m I need to find out who Olivia Wilde's boyfriend is and then calculate his age raised to the 0.23 power.\n",
123+
"Action: Search\n",
124+
"Action Input: \"Olivia Wilde boyfriend\"\u001b[0m\n",
125+
"Observation: \u001b[36;1m\u001b[1;3mJason Sudeikis\u001b[0m\n",
126+
"Thought:\u001b[32;1m\u001b[1;3m I need to find out Jason Sudeikis' age\n",
127+
"Action: Search\n",
128+
"Action Input: \"Jason Sudeikis age\"\u001b[0m\n",
129+
"Observation: \u001b[36;1m\u001b[1;3m47 years\u001b[0m\n",
130+
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 47 raised to the 0.23 power\n",
131+
"Action: Calculator\n",
132+
"Action Input: 47^0.23\u001b[0m\n",
133+
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.4242784855673896\n",
134+
"\u001b[0m\n",
135+
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
136+
"Final Answer: Jason Sudeikis, Olivia Wilde's boyfriend, is 47 years old and his age raised to the 0.23 power is 2.4242784855673896.\u001b[0m\n",
137+
"\n",
138+
"\u001b[1m> Finished chain.\u001b[0m\n",
139+
"1465\n"
140+
]
141+
}
142+
],
143+
"source": [
144+
"with get_openai_callback() as cb:\n",
145+
" response = agent.run(\"Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?\")\n",
146+
" print(cb.total_tokens)"
147+
]
148+
},
149+
{
150+
"cell_type": "code",
151+
"execution_count": null,
152+
"id": "80ca77a3",
153+
"metadata": {},
154+
"outputs": [],
155+
"source": []
156+
}
157+
],
158+
"metadata": {
159+
"kernelspec": {
160+
"display_name": "Python 3 (ipykernel)",
161+
"language": "python",
162+
"name": "python3"
163+
},
164+
"language_info": {
165+
"codemirror_mode": {
166+
"name": "ipython",
167+
"version": 3
168+
},
169+
"file_extension": ".py",
170+
"mimetype": "text/x-python",
171+
"name": "python",
172+
"nbconvert_exporter": "python",
173+
"pygments_lexer": "ipython3",
174+
"version": "3.10.9"
175+
}
176+
},
177+
"nbformat": 4,
178+
"nbformat_minor": 5
179+
}

docs/modules/llms/generic_how_to.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ The examples here all address certain "how-to" guides for working with LLMs.
99

1010
`Custom LLM <./examples/custom_llm.html>`_: How to create and use a custom LLM class, in case you have an LLM not from one of the standard providers (including one that you host yourself).
1111

12+
`Token Usage Tracking <./examples/token_usage_tracking.html>`_: How to track the token usage of various chains/agents/LLM calls.
13+
1214

1315
.. toctree::
1416
:maxdepth: 1

langchain/callbacks/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Callback handlers that allow listening to events in LangChain."""
2+
from contextlib import contextmanager
3+
from typing import Generator
4+
25
from langchain.callbacks.base import BaseCallbackHandler, BaseCallbackManager
6+
from langchain.callbacks.openai_info import OpenAICallbackHandler
37
from langchain.callbacks.shared import SharedCallbackManager
48
from langchain.callbacks.stdout import StdOutCallbackHandler
59

@@ -18,3 +22,13 @@ def set_handler(handler: BaseCallbackHandler) -> None:
1822
def set_default_callback_manager() -> None:
1923
"""Set default callback manager."""
2024
set_handler(StdOutCallbackHandler())
25+
26+
27+
@contextmanager
28+
def get_openai_callback() -> Generator[OpenAICallbackHandler, None, None]:
29+
"""Get OpenAI callback handler in a context manager."""
30+
handler = OpenAICallbackHandler()
31+
manager = get_callback_manager()
32+
manager.add_handler(handler)
33+
yield handler
34+
manager.remove_handler(handler)

langchain/callbacks/openai_info.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""Callback Handler that prints to std out."""
2+
from typing import Any, Dict, List, Optional
3+
4+
from langchain.callbacks.base import BaseCallbackHandler
5+
from langchain.schema import AgentAction, AgentFinish, LLMResult
6+
7+
8+
class OpenAICallbackHandler(BaseCallbackHandler):
9+
"""Callback Handler that tracks OpenAI info."""
10+
11+
total_tokens: int = 0
12+
13+
@property
14+
def always_verbose(self) -> bool:
15+
"""Whether to call verbose callbacks even if verbose is False."""
16+
return True
17+
18+
def on_llm_start(
19+
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
20+
) -> None:
21+
"""Print out the prompts."""
22+
pass
23+
24+
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
25+
"""Do nothing."""
26+
if response.llm_output is not None:
27+
if "token_usage" in response.llm_output:
28+
token_usage = response.llm_output["token_usage"]
29+
if "total_tokens" in token_usage:
30+
self.total_tokens += token_usage["total_tokens"]
31+
32+
def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
33+
"""Do nothing."""
34+
pass
35+
36+
def on_chain_start(
37+
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
38+
) -> None:
39+
"""Print out that we are entering a chain."""
40+
pass
41+
42+
def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
43+
"""Print out that we finished a chain."""
44+
pass
45+
46+
def on_chain_error(self, error: Exception, **kwargs: Any) -> None:
47+
"""Do nothing."""
48+
pass
49+
50+
def on_tool_start(
51+
self,
52+
serialized: Dict[str, Any],
53+
action: AgentAction,
54+
color: Optional[str] = None,
55+
**kwargs: Any,
56+
) -> None:
57+
"""Print out the log in specified color."""
58+
pass
59+
60+
def on_tool_end(
61+
self,
62+
output: str,
63+
color: Optional[str] = None,
64+
observation_prefix: Optional[str] = None,
65+
llm_prefix: Optional[str] = None,
66+
**kwargs: Any,
67+
) -> None:
68+
"""If not the final action, print out observation."""
69+
pass
70+
71+
def on_tool_error(self, error: Exception, **kwargs: Any) -> None:
72+
"""Do nothing."""
73+
pass
74+
75+
def on_text(
76+
self,
77+
text: str,
78+
color: Optional[str] = None,
79+
end: str = "",
80+
**kwargs: Optional[str],
81+
) -> None:
82+
"""Run when agent ends."""
83+
pass
84+
85+
def on_agent_finish(
86+
self, finish: AgentFinish, color: Optional[str] = None, **kwargs: Any
87+
) -> None:
88+
"""Run on agent end."""
89+
pass

0 commit comments

Comments
 (0)