Skip to content

Commit

Permalink
Merge branch 'main' into otel-exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
the-praxs committed Nov 23, 2024
2 parents a71807b + 0ce29b3 commit 7198696
Show file tree
Hide file tree
Showing 8 changed files with 622 additions and 9 deletions.
104 changes: 98 additions & 6 deletions agentops/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,30 @@ class Session:
Args:
session_id (UUID): The session id is used to record particular runs.
config (Configuration): The configuration object for the session.
tags (List[str], optional): Tags that can be used for grouping or sorting later. Examples could be ["GPT-4"].
host_env (dict, optional): A dictionary containing host and environment data.
Attributes:
init_timestamp (float): The timestamp for when the session started, represented as seconds since the epoch.
end_timestamp (float, optional): The timestamp for when the session ended, represented as seconds since the epoch. This is only set after end_session is called.
end_state (str, optional): The final state of the session. Suggested: "Success", "Fail", "Indeterminate". Defaults to "Indeterminate".
init_timestamp (str): The ISO timestamp for when the session started.
end_timestamp (str, optional): The ISO timestamp for when the session ended. Only set after end_session is called.
end_state (str, optional): The final state of the session. Options: "Success", "Fail", "Indeterminate". Defaults to "Indeterminate".
end_state_reason (str, optional): The reason for ending the session.
session_id (UUID): Unique identifier for the session.
tags (List[str]): List of tags associated with the session for grouping and filtering.
video (str, optional): URL to a video recording of the session.
host_env (dict, optional): Dictionary containing host and environment data.
config (Configuration): Configuration object containing settings for the session.
jwt (str, optional): JSON Web Token for authentication with the AgentOps API.
token_cost (Decimal): Running total of token costs for the session.
event_counts (dict): Counter for different types of events:
- llms: Number of LLM calls
- tools: Number of tool calls
- actions: Number of actions
- errors: Number of errors
- apis: Number of API calls
session_url (str, optional): URL to view the session in the AgentOps dashboard.
is_running (bool): Flag indicating if the session is currently active.
"""

def __init__(
Expand Down Expand Up @@ -209,6 +225,7 @@ def __init__(
"errors": 0,
"apis": 0,
}
self.session_url: Optional[str] = None

# Start session first to get JWT
self.is_running = self._start_session()
Expand Down Expand Up @@ -265,7 +282,8 @@ def end_session(
return None

if not any(end_state == state.value for state in EndState):
return logger.warning("Invalid end_state. Please use one of the EndState enums")
logger.warning("Invalid end_state. Please use one of the EndState enums")
return None

# 1. Set shutdown flag on exporter first to prevent new exports
if hasattr(self, "_span_processor") and hasattr(self._span_processor, "_span_exporter"):
Expand Down Expand Up @@ -311,7 +329,6 @@ def end_session(
"blue",
)
)

active_sessions.remove(self)
return self._token_cost

Expand Down Expand Up @@ -625,5 +642,80 @@ def get_analytics(self) -> Optional[Dict[str, Any]]:
"Cost": formatted_cost,
}

@staticmethod
def _format_duration(start_time, end_time):
start = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
end = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
duration = end - start

hours, remainder = divmod(duration.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)

parts = []
if hours > 0:
parts.append(f"{int(hours)}h")
if minutes > 0:
parts.append(f"{int(minutes)}m")
parts.append(f"{seconds:.1f}s")

return " ".join(parts)

def _get_response(self) -> Optional[Response]:
with self.lock:
payload = {"session": self.__dict__}
try:
response = HttpClient.post(
f"{self.config.endpoint}/v2/update_session",
json.dumps(filter_unjsonable(payload)).encode("utf-8"),
jwt=self.jwt,
)
except ApiServerException as e:
logger.error(f"Could not fetch response from server - {e}")
return None

logger.debug(response.body)
return response

def _get_token_cost(self, response: Response) -> Decimal:
token_cost = response.body.get("token_cost", "unknown")
if token_cost == "unknown" or token_cost is None:
return Decimal(0)
return Decimal(token_cost)

@staticmethod
def _format_token_cost(token_cost_d):
return (
"{:.2f}".format(token_cost_d)
if token_cost_d == 0
else "{:.6f}".format(token_cost_d.quantize(Decimal("0.000001"), rounding=ROUND_HALF_UP))
)

def get_analytics(self) -> Optional[dict[str, Union[Decimal, str]]]:
if not self.end_timestamp:
self.end_timestamp = get_ISO_time()

formatted_duration = self._format_duration(self.init_timestamp, self.end_timestamp)

response = self._get_response()
if response is None:
return None

self.token_cost = self._get_token_cost(response)
formatted_cost = self._format_token_cost(self.token_cost)

self.session_url = response.body.get(
"session_url",
f"https://app.agentops.ai/drilldown?session_id={self.session_id}",
)

return {
"LLM calls": self.event_counts["llms"],
"Tool calls": self.event_counts["tools"],
"Actions": self.event_counts["actions"],
"Errors": self.event_counts["errors"],
"Duration": formatted_duration,
"Cost": formatted_cost,
}


active_sessions: List[Session] = []
Binary file added docs/images/external/ollama/ollama-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"v1/integrations/langchain",
"v1/integrations/cohere",
"v1/integrations/anthropic",
"v1/integrations/ollama",
"v1/integrations/litellm",
"v1/integrations/multion",
"v1/integrations/rest"
Expand Down
38 changes: 35 additions & 3 deletions docs/v1/concepts/sessions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ Optionally, sessions may include:

_Note: Overrides any current tags_

#### `get_analytics`
**Returns** (dict): A dictionary containing various analytics metrics for the session.


## Starting a Session
When you call `agentops.init()`, a session is automatically started.
Expand All @@ -62,7 +65,7 @@ Both `agentops.init()` and `agentops.start_session()` work as a factory pattern

## Ending a Session
If a process ends without any call to agentops, it will show in the dashboard as `Indeterminate`.
To end with a state, call either `agentops.end_session(...)` [(reference)](/v1/usage/sdk-reference/#end-session) if only one session is in use. Otherwise use `session.end_session(...)`
To end with a state, call either `agentops.end_session(...)` [(reference)](/v1/usage/sdk-reference/#end-session) if only one session is in use. Otherwise use `session.end_session(...)`.

## Inherited Sessions
When working with multiple agents running in different processes, it's possible to initialize AgentOps or start a session
Expand All @@ -71,22 +74,51 @@ with an existing session_id.
`agentops.init(inherited_session_id=<id>)`
`agentops.start_session(inherited_session_id=<id>)`

You can retrieve the current `session_id` by assigning the returned value from `init()` or `start_session()`
You can retrieve the current `session_id` by assigning the returned value from `init()` or `start_session()`.

<CodeGroup>
```python python

```python
import agentops
session = agentops.init()
# pass session.session_id to the other process
```

```python
# -- other process --
session_id = retrieve_session_id() # <-- your function
agentops.init(inherited_session_id=<id>)
```

</CodeGroup>

Both processes will now contribute data to the same session.

## Session Analytics
You can retrieve the analytics for a session by calling `session.get_analytics()`.

The example below shows how to record events and retrieve analytics.

<CodeGroup>

```python
import agentops
session = agentops.init()
session.record(ActionEvent("llms"))
session.record(ActionEvent("tools"))
analytics = session.get_analytics()
print(analytics)
session.end_session("Success")
```

The output will look like this -

```bash
{'LLM calls': 0, 'Tool calls': 0, 'Actions': 0, 'Errors': 0, 'Duration': '0.9s', 'Cost': '0.00'}
```

</CodeGroup>

## The AgentOps SDK Client
_More info for the curious_

Expand Down
3 changes: 3 additions & 0 deletions docs/v1/examples/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ mode: "wide"
<Card title="MultiOn Example" icon="atom" href="/v1/examples/multion">
Create an autonomous browser agent capable of navigating the web and extracting information
</Card>
<Card title="Ollama Example" icon={<img src="https://www.github.com/agentops-ai/agentops/blob/main/docs/images/external/ollama/ollama-icon.png?raw=true" alt="Ollama" />} iconType="image" href="/v1/examples/ollama">
Simple Ollama integration with AgentOps
</Card>
</CardGroup>

## Video Guides
Expand Down
123 changes: 123 additions & 0 deletions docs/v1/examples/ollama.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: 'Ollama Example'
description: 'Using Ollama with AgentOps'
mode: "wide"
---

{/* SOURCE_FILE: examples/ollama_examples/ollama_examples.ipynb */}# AgentOps Ollama Integration

This example demonstrates how to use AgentOps to monitor your Ollama LLM calls.

First let's install the required packages

> ⚠️ **Important**: Make sure you have Ollama installed and running locally before running this notebook. You can install it from [ollama.ai](https://ollama.com).

```python
%pip install -U ollama
%pip install -U agentops
%pip install -U python-dotenv
```

Then import them


```python
import ollama
import agentops
import os
from dotenv import load_dotenv

```

Next, we'll set our API keys. For Ollama, we'll need to make sure Ollama is running locally.
[Get an AgentOps API key](https://agentops.ai/settings/projects)

1. Create an environment variable in a .env file or other method. By default, the AgentOps `init()` function will look for an environment variable named `AGENTOPS_API_KEY`. Or...
2. Replace `<your_agentops_key>` below and pass in the optional `api_key` parameter to the AgentOps `init(api_key=...)` function. Remember not to commit your API key to a public repo!


```python
# Let's load our environment variables
load_dotenv()

AGENTOPS_API_KEY = os.getenv("AGENTOPS_API_KEY") or "<your_agentops_key>"
```


```python
# Initialize AgentOps with some default tags
agentops.init(AGENTOPS_API_KEY, default_tags=["ollama-example"])
```

Now let's make some basic calls to Ollama. Make sure you have pulled the model first, use the following or replace with whichever model you want to use.


```python
ollama.pull("mistral")
```


```python
# Basic completion,
response = ollama.chat(model='mistral',
messages=[{
'role': 'user',
'content': 'What are the benefits of using AgentOps for monitoring LLMs?',
}]
)
print(response['message']['content'])
```

Let's try streaming responses as well


```python
# Streaming Example
stream = ollama.chat(
model='mistral',
messages=[{
'role': 'user',
'content': 'Write a haiku about monitoring AI agents',
}],
stream=True
)

for chunk in stream:
print(chunk['message']['content'], end='')

```


```python
# Conversation Example
messages = [
{
'role': 'user',
'content': 'What is AgentOps?'
},
{
'role': 'assistant',
'content': 'AgentOps is a monitoring and observability platform for LLM applications.'
},
{
'role': 'user',
'content': 'Can you give me 3 key features?'
}
]

response = ollama.chat(
model='mistral',
messages=messages
)
print(response['message']['content'])
```

> 💡 **Note**: In production environments, you should add proper error handling around the Ollama calls and use `agentops.end_session("Error")` when exceptions occur.
Finally, let's end our AgentOps session


```python
agentops.end_session("Success")
```
Loading

0 comments on commit 7198696

Please sign in to comment.