Skip to content

Commit

Permalink
Merge branch 'main' into hub-vision-table
Browse files Browse the repository at this point in the history
  • Loading branch information
jxnl committed Feb 20, 2024
2 parents 10dd869 + 1396283 commit 55960b1
Show file tree
Hide file tree
Showing 39 changed files with 1,556 additions and 361 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ except ValidationError as e:
"""
1 validation error for QuestionAnswer
answer
Assertion failed, The statement promotes objectionable behavior. [type=assertion_error, input_value='The meaning of life is to be evil and steal', input_type=str]
Assertion failed, The statement promotes objectionable behavior by encouraging evil and stealing, which goes against the rule of not saying objectionable things. [type=assertion_error, input_value='The meaning of life is to be evil and steal', input_type=str]
For further information visit https://errors.pydantic.dev/2.6/v/assertion_error
"""
```
Expand Down
8 changes: 4 additions & 4 deletions docs/blog/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ The goal of the blog is to capture some content that does not neatly fit within

## Integrations

- [Ollama](posts/ollama.md)
- [llama-cpp-python](posts/llama-cpp-python.md)
- [Anyscale](posts/anyscale.md)
- [Together Compute](posts/together.md)
- [Ollama](./../hub/ollama.md)
- [llama-cpp-python](./../hub/llama-cpp-python.md)
- [Anyscale](./../hub/anyscale.md)
- [Together Compute](./../hub/together.md)

## Media

Expand Down
161 changes: 161 additions & 0 deletions docs/blog/posts/langsmith.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
draft: False
date: 2024-02-18
tags:
- langsmith
authors:
- jxnl
---

# Seamless Support with Langsmith

Its a common misconception that LangChain's [LangSmith](https://www.langchain.com/langsmith) is only compatible with LangChain's models. In reality, LangSmith is a unified DevOps platform for developing, collaborating, testing, deploying, and monitoring LLM applications. In this blog we will explore how LangSmith can be used to enhance the OpenAI client alongside `instructor`.

<!-- more -->

## LangSmith

In order to use langsmith, you first need to set your LangSmith API key.

```
export LANGCHAIN_API_KEY=<your-api-key>
```

Next, you will need to install the LangSmith SDK:

```
pip install -U langsmith
pip install -U instructor
```

If you want to pull this example down from [instructor-hub](../../hub/index.md) you can use the following command:

```bash
instructor hub pull --slug batch_classification_langsmith --py > batch_classification_langsmith.py
```

In this example we'll use the `wrap_openai` function to wrap the OpenAI client with LangSmith. This will allow us to use LangSmith's observability and monitoring features with the OpenAI client. Then we'll use `instructor` to patch the client with the `TOOLS` mode. This will allow us to use `instructor` to add additional functionality to the client.

```python
import instructor
import asyncio

from langsmith import traceable
from langsmith.wrappers import wrap_openai

from openai import AsyncOpenAI
from pydantic import BaseModel, Field, field_validator
from typing import List
from enum import Enum

# Wrap the OpenAI client with LangSmith
client = wrap_openai(AsyncOpenAI())

# Patch the client with instructor
client = instructor.patch(client, mode=instructor.Mode.TOOLS)

# Rate limit the number of requests
sem = asyncio.Semaphore(5)

# Use an Enum to define the types of questions
class QuestionType(Enum):
CONTACT = "CONTACT"
TIMELINE_QUERY = "TIMELINE_QUERY"
DOCUMENT_SEARCH = "DOCUMENT_SEARCH"
COMPARE_CONTRAST = "COMPARE_CONTRAST"
EMAIL = "EMAIL"
PHOTOS = "PHOTOS"
SUMMARY = "SUMMARY"


# You can add more instructions and examples in the description
# or you can put it in the prompt in `messages=[...]`
class QuestionClassification(BaseModel):
"""
Predict the type of question that is being asked.
Here are some tips on how to predict the question type:
CONTACT: Searches for some contact information.
TIMELINE_QUERY: "When did something happen?
DOCUMENT_SEARCH: "Find me a document"
COMPARE_CONTRAST: "Compare and contrast two things"
EMAIL: "Find me an email, search for an email"
PHOTOS: "Find me a photo, search for a photo"
SUMMARY: "Summarize a large amount of data"
"""

# If you want only one classification, just change it to
# `classification: QuestionType` rather than `classifications: List[QuestionType]``
chain_of_thought: str = Field(
..., description="The chain of thought that led to the classification"
)
classification: List[QuestionType] = Field(
description=f"An accuracy and correct prediction predicted class of question. Only allowed types: {[t.value for t in QuestionType]}, should be used",
)

@field_validator("classification", mode="before")
def validate_classification(cls, v):
# sometimes the API returns a single value, just make sure it's a list
if not isinstance(v, list):
v = [v]
return v


@traceable(name="classify-question")
async def classify(data: str) -> QuestionClassification:
"""
Perform multi-label classification on the input text.
Change the prompt to fit your use case.
Args:
data (str): The input text to classify.
"""
async with sem: # some simple rate limiting
return data, await client.chat.completions.create(
model="gpt-4-turbo-preview",
response_model=QuestionClassification,
max_retries=2,
messages=[
{
"role": "user",
"content": f"Classify the following question: {data}",
},
],
)


async def main(questions: List[str]):
tasks = [classify(question) for question in questions]

for task in asyncio.as_completed(tasks):
question, label = await task
resp = {
"question": question,
"classification": [c.value for c in label.classification],
"chain_of_thought": label.chain_of_thought,
}
resps.append(resp)
return resps


if __name__ == "__main__":
import asyncio

questions = [
"What was that ai app that i saw on the news the other day?",
"Can you find the trainline booking email?",
"what did I do on Monday?",
"Tell me about todays meeting and how it relates to the email on Monday",
]

resp = asyncio.run(main(questions))

for r in resp:
print("q:", r["question"])
#> q: what did I do on Monday?
print("c:", r["classification"])
#> c: ['SUMMARY']
```

If you follow what we've done is wrapped the client and proceeded to quickly use asyncio to classify a list of questions. This is a simple example of how you can use LangSmith to enhance the OpenAI client. You can use LangSmith to monitor and observe the client, and use `instructor` to add additional functionality to the client.

To take a look at trace of this run check out this shareable [link](https://smith.langchain.com/public/eaae9f95-3779-4bbb-824d-97aa8a57a4e0/r).
4 changes: 2 additions & 2 deletions docs/concepts/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ def extract(data) -> UserDetail:
start = time.perf_counter() # (1)
model = extract("Extract jason is 25 years old")
print(f"Time taken: {time.perf_counter() - start}")
#> Time taken: 0.41433916706591845
#> Time taken: 0.8392175831831992

start = time.perf_counter()
model = extract("Extract jason is 25 years old") # (2)
print(f"Time taken: {time.perf_counter() - start}")
#> Time taken: 1.7080456018447876e-06
#> Time taken: 8.33999365568161e-07
```

1. Using `time.perf_counter()` to measure the time taken to run the function is better than using `time.time()` because it's more accurate and less susceptible to system clock changes.
Expand Down
10 changes: 4 additions & 6 deletions docs/concepts/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ users = client.chat.completions.create(
)
for user in users:
print(user)
#> ('tasks', [User(name='Jason', age=10), User(name='John', age=30)])

#> name="Jason" "age"=10
#> name="John" "age"=10
#> name='Jason' age=10
#> name='John' age=30
```

## Streaming Tasks
Expand Down Expand Up @@ -159,8 +157,8 @@ async def print_iterable_results():
)
async for m in model:
print(m)
#> name='John Doe' age=32
#> name='Jane Smith' age=28
#> name='John Smith' age=30
#> name='Mary Jane' age=28


import asyncio
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/maybe.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ print(user2.model_dump_json(indent=2))
{
"result": null,
"error": false,
"message": "Unknown user"
"message": null
}
"""
```
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class SearchQuery(BaseModel):

def execute(self):
print(f"Searching for {self.query} of type {self.query_type}")
#> Searching for cat of type image
#> Searching for cat pictures of type image
return "Results for cat"


Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ function_calls = client.chat.completions.create(

for fc in function_calls:
print(fc)
#> location='Toronto' units='metric'
#> location='Toronto' units='imperial'
#> location='Dallas' units='imperial'
#> query='super bowl winner'
#> query='who won the super bowl'
```

1. Set the mode to `PARALLEL_TOOLS` to enable parallel function calling.
Expand Down
Loading

0 comments on commit 55960b1

Please sign in to comment.