Skip to content

Commit 718e28d

Browse files
OKUA1iryna-kondr
andcommitted
v0.1.0
Co-authored-by: Iryna Kondrashchenko <iryna-kondr@users.noreply.github.com>
1 parent 9b4ba62 commit 718e28d

14 files changed

Lines changed: 497 additions & 24 deletions

File tree

.github/workflows/pypi_deploy.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: PyPi Deploy
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v3
14+
15+
- name: Setup Python
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: '3.10'
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install build twine
24+
25+
- name: Build and publish
26+
env:
27+
TWINE_USERNAME: __token__
28+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
29+
run: |
30+
python -m build
31+
twine upload dist/*

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,4 @@ cython_debug/
160160
#.idea/
161161
test.py
162162
tmp.py
163+
tmp_client.py

README.md

Lines changed: 187 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,40 +27,61 @@ _Dingo_ allows you to easily integrate any function into ChatGPT by adding a sin
2727

2828
## Quick Start ⚡️
2929

30-
Step 1: Install `agent-dingo`
30+
**Step 1:** Install `agent-dingo`
3131

3232
```bash
3333
pip install agent-dingo
3434
```
3535

36-
Step 2: Configure your OpenAI API key
36+
**Step 2:** Configure your OpenAI API key
3737

3838
```bash
3939
export OPENAI_API_KEY=<YOUR_KEY>
4040
```
4141

42-
Step 3: Instantiate the agent
42+
**Step 3:** Instantiate the agent
4343

4444
```python
4545
from agent_dingo import AgentDingo
4646

4747
agent = AgentDingo()
4848
```
4949

50-
Step 4: Add `agent.function` decorator to the function you wish to integrate
50+
**Step 4:** Add `agent.function` decorator to the function you wish to integrate
5151

5252
```python
5353
@agent.function
5454
def get_current_weather(city: str):
5555
...
5656
```
5757

58-
Step 5: Run the conversation
58+
**Step 5:** Run the conversation
5959

6060
```python
6161
agent.chat("What is the current weather in Linz?")
6262
```
6363

64+
**Optional:** Run an OpenAI compatible server
65+
66+
```python
67+
from agent_dingo.wrapper import DingoWrapper
68+
DingoWrapper(agent).serve()
69+
```
70+
71+
The server can be accessed using the `openai` python package:
72+
73+
```python
74+
import openai
75+
76+
openai.api_base = "http://localhost:8080"
77+
78+
r = openai.ChatCompletion.create(
79+
model = "gpt-3.5-turbo",
80+
messages = [{"role": "user", "content": "What is the current weather in Linz?"}],
81+
temperature=0.0,
82+
)
83+
```
84+
6485
## Support us 🤝
6586

6687
You can support the project in the following ways:
@@ -71,7 +92,7 @@ You can support the project in the following ways:
7192

7293
📰 Post about _Dingo_ on LinkedIn or other platforms
7394

74-
## Our Related Projects 🔗
95+
🔗 Check out our other projects (cards below are clickable):
7596

7697
<a href="https://github.com/OKUA1/falcon"><img src="https://raw.githubusercontent.com/gist/OKUA1/6264a95a8abd225c74411a2b707b0242/raw/3cedb53538cb04656cd9d7d07e697e726896ce9f/falcon_light.svg"/></a> <br>
7798
<a href="https://github.com/iryna-kondr/scikit-llm"><img src="https://gist.githubusercontent.com/OKUA1/6264a95a8abd225c74411a2b707b0242/raw/029694673765a3af36d541925a67214e677155e5/skllm_light.svg"/></a>
@@ -174,6 +195,21 @@ from my_module import get_temperature
174195
agent.register_function(get_temperature)
175196
```
176197

198+
Alternatively, you can define a function descriptor manually and register it using the `register_descriptor` method. In this case, a `json_representation` compatible with [OpenAI function calling API](https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions) should be provided.
199+
200+
```python
201+
from agent_dingo.descriptor import FunctionDescriptor
202+
203+
d = FunctionDescriptor(
204+
name = "<function_name>",
205+
json_representation = {name: "<function_name>", description: "<function_description>", parameters: ...}
206+
func = function_callable
207+
requires_context = True or False
208+
)
209+
210+
agent.register_descriptor(d)
211+
```
212+
177213
### Running the conversation
178214

179215
Once the functions are registered, you can run the conversation using the `chat` method of the agent.
@@ -246,3 +282,148 @@ agent.chat(
246282
before_function_call=before_function_call,
247283
)
248284
```
285+
286+
### DingoWrapper + Web Server
287+
288+
In addition to using the agent directly, it is possible to wrap it into a `DingoWrapper`, which provides an OpenAI-like API.
289+
290+
```python
291+
from agent_dingo.wrapper import DingoWrapper
292+
wrapped_agent = DingoWrapper(agent, before_function_call = None, max_function_calls=10)
293+
```
294+
295+
Once the agent is wrapped, it can be used to create the chat completions using the `chat_completion` method.
296+
297+
```python
298+
r = wrapped_agent.chat_completion(
299+
messages = [{"role": "user", "content": "What is the current weather in Linz?"}],
300+
model = "gpt-3.5-turbo",
301+
temperature=0.0, #optional
302+
chat_context=None, #optional
303+
)
304+
```
305+
306+
In principle, this method can be used as a drop-in replacement for the `openai.ChatCompletion.create` method. However, there are several differences:
307+
308+
- DingoWrapper does not support most of the optional hyperparameters of the `openai.ChatCompletion.create` method (except `temperature`);
309+
- DingoWrapper has an additional (optional) `chat_context` parameter that can be used to pass the global context of the conversation;
310+
311+
Example:
312+
313+
```python
314+
# openai.ChatCompletion
315+
r = openai.ChatCompletion.create(
316+
messages = [{"role": "user", "content": "What is the current weather in Linz?"}],
317+
model = "gpt-3.5-turbo",
318+
temperature=0.0
319+
)
320+
321+
# DingoWrapper
322+
r = wrapped_agent.chat_completion(
323+
messages = [{"role": "user", "content": "What is the current weather in Linz?"}],
324+
model = "gpt-3.5-turbo",
325+
temperature=0.0
326+
)
327+
```
328+
329+
The `DingoWrapper` can also be used to run a web server (also compatible with the OpenAI API). The server can be started using the `serve` method.
330+
331+
The serve method requires additional dependencies:
332+
333+
```bash
334+
pip install agent_dingo[server]
335+
```
336+
337+
```python
338+
wrapped_agent.serve(port=8080, host="0.0.0.0", threads=4)
339+
```
340+
341+
Once the server has started, it can be accessed using e.g. the `openai` python package.
342+
343+
```python
344+
# client.py
345+
import openai
346+
347+
openai.api_base = "http://localhost:8080"
348+
349+
r = openai.ChatCompletion.create(
350+
model = "gpt-3.5-turbo",
351+
messages = [{"role": "user", "content": "What is the temperature in Linz ?"}],
352+
temperature=0.0,
353+
)
354+
355+
print(r)
356+
```
357+
358+
Response:
359+
360+
```json
361+
{
362+
"choices": [
363+
{
364+
"finish_reason": "stop",
365+
"index": 0,
366+
"message": {
367+
"content": "The current temperature in Linz is 25\u00b0C and it is sunny.",
368+
"role": "assistant"
369+
}
370+
}
371+
],
372+
"created": 1692537919,
373+
"id": "chatcmpl-d6a9d6cc-7a26-41d5-a4a6-2c737b652f4b",
374+
"model": "dingo-gpt-3.5-turbo-0613",
375+
"object": "chat.completion",
376+
"usage": {
377+
"completion_tokens": 32,
378+
"prompt_tokens": 318,
379+
"total_tokens": 350
380+
}
381+
}
382+
```
383+
384+
_Note: the "usage" metric accumulates the number of tokens used for all intermediate function calls during the conversation._
385+
386+
### LangChain Tools 🦜️🔗
387+
388+
It is possible to convert [Langchain Tools](https://python.langchain.com/docs/modules/agents/tools/) into function descriptors in order to register them with Dingo. The converter can be used as follows:
389+
390+
1. Install langchain:
391+
392+
```bash
393+
pip install agent_dingo[langchain]
394+
```
395+
396+
2. Define the tool, we will use the Wikipedia tool as an example:
397+
398+
```python
399+
from langchain.tools.wikipedia.tool import WikipediaQueryRun
400+
from langchain.utilities.wikipedia import WikipediaAPIWrapper
401+
tool = WikipediaQueryRun(api_wrapper = WikipediaAPIWrapper())
402+
```
403+
404+
Please refer to the [LangChain documentation](https://api.python.langchain.com/en/latest/api_reference.html#module-langchain.tools) for more details on how to define the tools.
405+
406+
3. Convert the tool into a function descriptor and register:
407+
408+
```python
409+
from agent_dingo.langchain import convert_langchain_tool
410+
descriptor = convert_langchain_tool(tool)
411+
agent.register_descriptor(descriptor)
412+
```
413+
414+
4. Run the conversation:
415+
416+
```python
417+
# The agent will query Wikipedia to obtain the answer.
418+
agent.chat("What is LangChain according to Wikipedia? Explain in one sentence.")
419+
420+
# > According to Wikipedia, LangChain is a framework designed to simplify the creation of applications using large language models (LLMs), with use-cases including document analysis and summarization, chatbots, and code analysis.
421+
```
422+
423+
In comparison, when we try to query ChatGPT directly with the same question, we get the following hallucinated response (since it does not have access to the relevant up-to-date information):
424+
425+
```python
426+
# > LangChain is a blockchain-based platform that aims to provide language learning services and connect language learners with native speakers for real-time practice and feedback.
427+
```
428+
429+
_Note: some of the tools might be incompatible with (or simply unsuitable for) Dingo. We do not guarantee that all of the tools will work out of the box._

agent_dingo/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from agent_dingo.agent import AgentDingo
22

3-
__version__ = '0.1.0rc1'
4-
__author__ = 'Oleh Kostromin, Iryna Kondrashchenko'
3+
__version__ = "0.1.0"
4+
__author__ = "Oleh Kostromin, Iryna Kondrashchenko"

agent_dingo/agent.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from agent_dingo.context import ChatContext
55
from agent_dingo.chat import send_message
66
from agent_dingo.docgen import generate_docstring
7+
from agent_dingo.usage import UsageMeter
8+
from agent_dingo.function_descriptor import FunctionDescriptor
9+
from dataclasses import asdict
710
import json
811
import os
912

@@ -99,6 +102,23 @@ def _is_codegen_allowed(self) -> bool:
99102
return bool(os.getenv("DINGO_ALLOW_CODEGEN", True))
100103
return self._allow_codegen
101104

105+
def register_descriptor(self, descriptor: FunctionDescriptor) -> None:
106+
"""Registers a function descriptor with the agent.
107+
108+
Parameters
109+
----------
110+
descriptor : FunctionDescriptor
111+
The function descriptor to register.
112+
"""
113+
if not isinstance(descriptor, FunctionDescriptor):
114+
raise ValueError("descriptor must be a FunctionDescriptor")
115+
self._registry.add(
116+
name=descriptor.name,
117+
func=descriptor.func,
118+
json_repr=descriptor.json_repr,
119+
requires_context=descriptor.requires_context,
120+
)
121+
102122
def register_function(self, func: Callable) -> None:
103123
"""Registers a function with the agent.
104124
@@ -152,6 +172,7 @@ def chat(
152172
temperature: float = 1.0,
153173
max_function_calls: int = 10,
154174
before_function_call: Callable = None,
175+
usage_meter: Optional[UsageMeter] = None,
155176
) -> Tuple[str, List[dict]]:
156177
"""Sends a message to the LLM and returns the response. Calls functions if the LLM requests it.
157178
@@ -190,6 +211,7 @@ def chat(
190211
model=model,
191212
functions=available_functions_i,
192213
temperature=temperature,
214+
usage_meter=usage_meter,
193215
)
194216
if response.get("function_call"):
195217
function_name = response["function_call"]["name"]

agent_dingo/chat.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Optional, List
1+
from typing import Optional, List, Callable
2+
from agent_dingo.usage import UsageMeter
23
import openai
34

45
from tenacity import retry, stop_after_attempt, wait_fixed
@@ -10,6 +11,7 @@ def send_message(
1011
model: str = "gpt-3.5-turbo-0613",
1112
functions: Optional[List] = None,
1213
temperature: float = 1.0,
14+
usage_meter: Optional[UsageMeter] = None,
1315
) -> dict:
1416
"""Sends messages to the LLM and returns the response.
1517
@@ -23,6 +25,8 @@ def send_message(
2325
List of functions to use, by default None
2426
temperature : float, optional
2527
Temperature to use, by default 1.
28+
log_usage : Callable, optional
29+
Function to log usage, by default None
2630
2731
Returns
2832
-------
@@ -36,4 +40,6 @@ def send_message(
3640
response = openai.ChatCompletion.create(
3741
model=model, messages=messages, temperature=temperature, **f
3842
)
43+
if usage_meter:
44+
usage_meter.log_raw(response)
3945
return response["choices"][0]["message"]

agent_dingo/function_descriptor.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from dataclasses import dataclass
2+
from typing import Callable
3+
4+
5+
@dataclass
6+
class FunctionDescriptor:
7+
name: str
8+
func: Callable
9+
json_repr: dict
10+
requires_context: bool

0 commit comments

Comments
 (0)