Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ MANIFEST
*.manifest
*.spec

# Root-level assistant manifests generated by tests (generate_manifest default dir)
/*_manifest.json

# Installer logs
pip-log.txt
pip-delete-this-directory.txt
Expand Down
23 changes: 12 additions & 11 deletions docs/docs/guide/configuring-event-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,19 @@ Nothing fancy here, just initialization of context and setting up of variables.

### Event Store Initialization

We create a PostgreSQL event store instance with the connection URL matching your Docker container configuration, we then register the event store with Graphite's dependency injection container to obtain a reference to the registered event store for later use.
We create a PostgreSQL event store instance with the connection URL matching your Docker container configuration, then place it in an `ExecutionServices` bundle and build a `GrafiRuntime` from it. Invocations run through that runtime; we keep a reference to the store for reading events later.

```python
from grafi.common.event_stores.event_store_postgres import EventStorePostgres
from grafi.common.containers.container import container
from grafi.runtime import GrafiRuntime, ExecutionServices

postgres_event_store = EventStorePostgres(
# must match what is in docker-compose.yaml
db_url="postgresql://postgres:postgres@localhost:5432/grafi_test_db",
)

container.register_event_store(postgres_event_store)
event_store = container.event_store
runtime = GrafiRuntime(ExecutionServices(event_store=postgres_event_store))
event_store = runtime.services.event_store
```

## Running the agent and Retrieving Events
Expand All @@ -137,12 +137,13 @@ async def run_agent():

react_agent = create_react_agent()

result = await react_agent.run(user_input, invoke_context)
# Run through the runtime so the agent uses the Postgres store.
result = await react_agent.run(user_input, invoke_context, runtime=runtime)

print("Output from React Agent:", result)


events = event_store.get_conversation_events(conversation_id)
events = await event_store.get_conversation_events(conversation_id)

print(f"Events for conversation {conversation_id}:")

Expand Down Expand Up @@ -170,19 +171,19 @@ import os
import uuid

from grafi.agents.react_agent import create_react_agent
from grafi.common.containers.container import container
from grafi.common.event_stores.event_store_postgres import EventStorePostgres
from grafi.common.models.invoke_context import InvokeContext
from grafi.common.models.message import Message
from grafi.runtime import GrafiRuntime, ExecutionServices

postgres_event_store = EventStorePostgres(
db_url="postgresql://postgres:postgres@localhost:5432/grafi_test_db",
)


container.register_event_store(postgres_event_store)
runtime = GrafiRuntime(ExecutionServices(event_store=postgres_event_store))

event_store = container.event_store
event_store = runtime.services.event_store

# Generate consistent IDs for the conversation
conversation_id = uuid.uuid4().hex
Expand Down Expand Up @@ -211,12 +212,12 @@ async def run_agent():

react_agent = create_react_agent()

result = await react_agent.run(user_input, invoke_context)
result = await react_agent.run(user_input, invoke_context, runtime=runtime)

print("Output from React Agent:", result)


events = event_store.get_conversation_events(conversation_id)
events = await event_store.get_conversation_events(conversation_id)

print(f"Events for conversation {conversation_id}:")

Expand Down
49 changes: 25 additions & 24 deletions docs/docs/guide/configuring-integration-with-opentelemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,20 +178,20 @@ tracer = setup_tracing(tracing_options=TracingOptions.IN_MEMORY)
### Example 1: Basic Setup with AUTO Detection

```python
from grafi.common.containers.container import container
from grafi.runtime import GrafiRuntime, ExecutionServices
from grafi.common.instrumentations.tracing import TracingOptions, setup_tracing

# Register the tracer with auto-detection
# Build a runtime that uses the auto-detected tracer
tracer = setup_tracing(tracing_options=TracingOptions.AUTO)
container.register_tracer(tracer)
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))

# Your assistant code here
# Invoke assistants through `runtime`
```

### Example 2: Export to an OTLP Collector

```python
from grafi.common.containers.container import container
from grafi.runtime import GrafiRuntime, ExecutionServices
from grafi.common.instrumentations.tracing import TracingOptions, setup_tracing

tracer = setup_tracing(
Expand All @@ -200,22 +200,22 @@ tracer = setup_tracing(
collector_port=4317,
project_name="my-assistant",
)
container.register_tracer(tracer)
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))

# Your assistant code here
# Invoke assistants through `runtime`
```

### Example 3: Testing with In-Memory Tracing

```python
from grafi.common.containers.container import container
from grafi.runtime import GrafiRuntime, ExecutionServices
from grafi.common.instrumentations.tracing import TracingOptions, setup_tracing

# Use in-memory tracing for tests
tracer = setup_tracing(tracing_options=TracingOptions.IN_MEMORY)
container.register_tracer(tracer)
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))

# Your test code here
# Invoke assistants through `runtime` in your test
```

### Example 4: Complete Assistant with Tracing
Expand All @@ -224,20 +224,20 @@ container.register_tracer(tracer)
import os
import uuid
import asyncio
from grafi.common.containers.container import container
from grafi.runtime import GrafiRuntime, ExecutionServices
from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent
from grafi.common.instrumentations.tracing import TracingOptions, setup_tracing
from grafi.common.models.async_result import async_func_wrapper
from grafi.common.models.invoke_context import InvokeContext
from grafi.common.models.message import Message
from grafi.assistants.assistant_base import AssistantBase

# Setup tracing
# Setup tracing and build a runtime that uses it
tracer = setup_tracing(tracing_options=TracingOptions.AUTO)
container.register_tracer(tracer)
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))

# Get event store
event_store = container.event_store
# Reference the event store (the default in-memory store here)
event_store = runtime.services.event_store

# Create your assistant
async def main():
Expand All @@ -264,7 +264,7 @@ async def main():
)

output = await async_func_wrapper(
assistant.invoke(input_data, is_sequential=True)
runtime.invoke(assistant, input_data, is_sequential=True)
)
print(output)

Expand Down Expand Up @@ -305,11 +305,11 @@ tracer = setup_tracing(
Set up tracing early in your application lifecycle, before creating assistants:

```python
# Good: Setup tracing first
# Good: Setup tracing first, build the runtime from it
tracer = setup_tracing(tracing_options=TracingOptions.AUTO)
container.register_tracer(tracer)
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))

# Then create assistants
# Then create assistants and invoke them through `runtime`
assistant = MyAssistant.builder().build()
```

Expand Down Expand Up @@ -341,13 +341,14 @@ Use IN_MEMORY mode in tests to avoid external dependencies:
```python
import pytest
from grafi.common.instrumentations.tracing import TracingOptions, setup_tracing
from grafi.runtime import bind_services, ExecutionServices

@pytest.fixture(autouse=True)
def setup_test_tracing():
tracer = setup_tracing(tracing_options=TracingOptions.IN_MEMORY)
container.register_tracer(tracer)
yield
# Cleanup if needed
# Bind a scope for the test so component invocations resolve these services.
with bind_services(ExecutionServices(tracer=tracer)):
yield
```

## Troubleshooting
Expand Down Expand Up @@ -401,9 +402,9 @@ def setup_test_tracing():

**Solution**:
1. Ensure OpenAI is instrumented (done automatically by `setup_tracing`)
2. Verify the tracer is registered before creating assistants:
2. Build the runtime with your tracer and invoke through it:
```python
container.register_tracer(tracer) # Must be before assistant creation
runtime = GrafiRuntime(ExecutionServices(tracer=tracer))
```

### Issue: Traces showing in wrong project
Expand Down
7 changes: 4 additions & 3 deletions docs/docs/guide/connecting-to-an-mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,16 +346,17 @@ import os
import uuid
from typing import Dict

from grafi.common.containers.container import container
from grafi.common.events.topic_events.publish_to_topic_event import PublishToTopicEvent
from grafi.common.models.invoke_context import InvokeContext
from grafi.common.models.mcp_connections import StreamableHttpConnection
from grafi.common.models.message import Message
from grafi.runtime import GrafiRuntime, ExecutionServices
from grafi.tools.function_calls.impl.mcp_tool import MCPTool

from assistant import StockAssistant

event_store = container.event_store
runtime = GrafiRuntime()
event_store = runtime.services.event_store

async def create_assistant():
api_key = os.getenv("OPENAI_API_KEY", "")
Expand Down Expand Up @@ -397,7 +398,7 @@ async def main():
invoke_context=execution_context, data=input_messages
)

async for response in assistant.invoke(publish_event):
async for response in runtime.invoke(assistant, publish_event):
print("Assistant output:")
for output in response:
print(output.content)
Expand Down
18 changes: 12 additions & 6 deletions docs/docs/guide/creating-a-simple-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,18 @@ workflow = (
With the `EventDrivenWorkflow` object created, we can invoke it by passing our `invoke_context` and a `List[Message]`. The workflow will execute and return the results, which we can then print. Save this complete code as `main.py`.

```python linenums="54"
async for result in workflow.invoke(
invoke_context,
[message]
):
for output_message in result:
print("Output message:", output_message.content)
# Bind a runtime scope so the workflow's components can resolve their services
# (event store / tracer / error reporter). ExecutionServices() uses in-process
# defaults; pass a durable store/tracer in production.
from grafi.runtime import bind_services, ExecutionServices

with bind_services(ExecutionServices()):
async for result in workflow.invoke(
invoke_context,
[message]
):
for output_message in result:
print("Output message:", output_message.content)
```

### 6. Entry Point
Expand Down
16 changes: 12 additions & 4 deletions docs/docs/guide/getting-started-with-assistants.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,21 @@ This function is not part of the framework, but rather a helper function used to
class FinanceAssistant(Assistant):

...
async def run(self, question: str, invoke_context: Optional[InvokeContext] = None) -> str:
async def run(
self,
question: str,
invoke_context: Optional[InvokeContext] = None,
runtime: Optional[GrafiRuntime] = None, # from grafi.runtime import GrafiRuntime
) -> str:
"""Run the assistant with a question and return the response."""
# Call helper function get_input()
input_event= self.get_input(question, invoke_context)
# This is the line that invokes the workflow
input_event = self.get_input(question, invoke_context)
# Run through a runtime, which binds the services (event store / tracer /
# error reporter) for the invocation. Pass a shared `runtime` to reuse a
# store across calls; otherwise a default in-process runtime is used.
runtime = runtime or GrafiRuntime()
response_str = ""
async for output in super().invoke(input_event):
async for output in runtime.invoke(self, input_event):
# Handle different content types
if output and len(output) > 0:
content = output.data[0].content
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/user-guide/assistant.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ The `AssistantBaseBuilder` provides a fluent interface for constructing assistan

```python
from grafi.assistants.assistant_base import AssistantBaseBuilder
from grafi.common.event_stores.in_memory_event_store import InMemoryEventStore
from openinference.semconv.trace import OpenInferenceSpanKindValues

builder = AssistantBaseBuilder(MyAssistant)
assistant = (builder
.name("Customer Support Assistant")
.type("support")
.oi_span_type(OpenInferenceSpanKindValues.AGENT)
.event_store(InMemoryEventStore())
.build())

# The event store / tracer are supplied at runtime, not on the assistant:
# runtime = GrafiRuntime(ExecutionServices(event_store=...))
# async for event in runtime.invoke(assistant, input_data): ...
```
4 changes: 2 additions & 2 deletions docs/docs/user-guide/builder-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,12 @@ def build(self) -> MyComponent:
When working with Graphite's existing components, use their provided builders:

```python
# Assistant construction
# Assistant construction (the event store is supplied at runtime via
# GrafiRuntime/ExecutionServices, not on the assistant builder).
assistant = (MyAssistant.builder()
.name("Customer Support")
.type("support")
.oi_span_type(OpenInferenceSpanKindValues.AGENT)
.event_store(InMemoryEventStore())
.build())

# Workflow construction
Expand Down
Loading
Loading