Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import Tool
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from openinference.instrumentation.haystack import HaystackInstrumentor

# Configure HaystackInstrumentor with Phoenix endpoint
endpoint = "http://127.0.0.1:6006/v1/traces"
tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))

HaystackInstrumentor().instrument(tracer_provider=tracer_provider)


def search_documents(query: str, user_context: str) -> dict:
"""Search documents using query and user context."""
return {"results": [f"Found results for '{query}' (user: {user_context})"]}


# Create tool that reads from state
search_tool = Tool(
name="search",
description="Search documents",
parameters={
"type": "object",
"properties": {"query": {"type": "string"}, "user_context": {"type": "string"}},
"required": ["query"],
},
function=search_documents,
inputs_from_state={"user_name": "user_context"},
# Maps state's "user_name" to the tool’s input parameter “user_context”
)

# Define agent with state schema including user_name
agent = Agent(
chat_generator=OpenAIChatGenerator(),
tools=[search_tool],
state_schema={"user_name": {"type": str}, "search_results": {"type": list}},
)

# Initialize agent with user context
result = agent.run(
messages=[ChatMessage.from_user("Search for Python tutorials")], user_name="Alice"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Optional

from haystack.components.agents import Agent
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
from haystack.components.converters.html import HTMLToDocument
from haystack.components.fetchers.link_content import LinkContentFetcher
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.core.pipeline import Pipeline
from haystack.dataclasses import ChatMessage, Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.tools import tool
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from openinference.instrumentation.haystack import HaystackInstrumentor

endpoint = "http://127.0.0.1:6006/v1/traces"
tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))

HaystackInstrumentor().instrument(tracer_provider=tracer_provider)

document_store = InMemoryDocumentStore() # create a document store or an SQL database


@tool
def add_database_tool(name: str, surname: str, job_title: Optional[str], other: Optional[str]):
"""Use this tool to add names to the database with information about them"""
document_store.write_documents(
[Document(content=name + " " + surname + " " + (job_title or ""), meta={"other": other})]
)
return


database_assistant = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
tools=[add_database_tool],
system_prompt="You are a database assistant. Extract all people’s names and relevant details"
" from the given context (using only the provided text), add them to the "
"knowledge base automatically, and return a brief summary of the added entries.",
exit_conditions=["text"],
max_agent_steps=100,
raise_on_tool_invocation_failure=False,
)

builder = ChatPromptBuilder(
template=[
ChatMessage.from_user("""
{% for doc in docs %}
{{ doc.content|default|truncate(25000) }}
{% endfor %}
""")
],
required_variables=["docs"],
)

extraction_agent = Pipeline()
extraction_agent.add_component("fetcher", LinkContentFetcher())
extraction_agent.add_component("converter", HTMLToDocument())
extraction_agent.add_component("builder", builder)

extraction_agent.add_component("database_agent", database_assistant)
extraction_agent.connect("fetcher.streams", "converter.sources")
extraction_agent.connect("converter.documents", "builder.docs")
extraction_agent.connect("builder", "database_agent")

agent_output = extraction_agent.run(
{"fetcher": {"urls": ["https://en.wikipedia.org/wiki/Deepset"]}}
)

print(agent_output["database_agent"]["messages"][-1].text)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import asyncio

from haystack import Document
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
from haystack.document_stores.in_memory import InMemoryDocumentStore
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from openinference.instrumentation.haystack import HaystackInstrumentor

endpoint = "http://127.0.0.1:6006/v1/traces"
tracer_provider = trace_sdk.TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))

HaystackInstrumentor().instrument(tracer_provider=tracer_provider)

document_store = InMemoryDocumentStore()
documents = [
Document(content="There are over 7,000 languages spoken around the world today."),
Document(
content="Elephants have been observed to behave in a way that indicates "
"a high level of self-awareness, such as recognizing themselves "
"in mirrors."
),
Document(
content="In certain parts of the world, like the Maldives, Puerto Rico, "
"and San Diego, you can witness the phenomenon of bioluminescent "
"waves."
),
]
document_store.write_documents(documents=documents)


async def run():
retriever = InMemoryBM25Retriever(document_store=document_store)
result = await retriever.run_async(
query="How many languages are spoken around the world today?"
)
print(result)


if __name__ == "__main__":
asyncio.run(run())
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ def wrap_component_run_method(
tracer=self._tracer, wrap_component_run_method=wrap_component_run_method
),
)
from haystack.core.component.component import component

for class_path, cls in component.registry.items():
# Ensure the class looks like a Component (has run or run_async)
if hasattr(cls, "run"):
wrap_component_run_method(cls, cls.run)
if hasattr(cls, "run_async"):
wrap_component_run_method(cls, cls.run_async)

def _uninstrument(self, **kwargs: Any) -> None:
import haystack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def __call__(
bound_arguments = _get_bound_arguments(wrapped, *args, **kwargs)

with self._tracer.start_as_current_span(
name=_get_component_span_name(component_class_name)
name=_get_component_span_name(component_class_name, wrapped)
) as span:
component_type = _get_component_type(component)
span.set_attributes(_set_component_runner_request_attributes(bound_arguments, instance))
Expand Down Expand Up @@ -170,7 +170,7 @@ async def __call__(
bound_arguments = _get_bound_arguments(wrapped, *args, **kwargs)
component_type = _get_component_type(instance)
with self._tracer.start_as_current_span(
name=_get_component_span_name(component_class_name),
name=_get_component_span_name(component_class_name, wrapped),
attributes=_set_component_runner_request_attributes(bound_arguments, instance),
) as span:
result = await wrapped(*args, **kwargs)
Expand Down Expand Up @@ -325,11 +325,11 @@ def _get_component_class_name(component: "Component") -> str:
return str(component.__class__.__name__)


def _get_component_span_name(component_class_name: str) -> str:
def _get_component_span_name(component_class_name: str, wrapped: Callable[..., Any]) -> str:
"""
Gets the name of the span for a component.
"""
return f"{component_class_name}.run"
return f"{component_class_name}.{wrapped.__name__}"


def _get_component_type(component: "Component") -> ComponentType:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
interactions:
- request:
body: '{"messages":[{"role":"user","content":"Search for Python tutorials"}],"model":"gpt-4o-mini","n":1,"stream":false,"tools":[{"type":"function","function":{"name":"search","description":"Search
documents","parameters":{"type":"object","properties":{"query":{"type":"string"},"user_context":{"type":"string"}},"required":["query"]}}}]}'
headers: {}
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: "{\n \"id\": \"chatcmpl-CWletHyGzapkvp0MG02ee7Jwn1x7B\",\n \"object\":
\"chat.completion\",\n \"created\": 1761925731,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
\ \"id\": \"call_BjOwGgWvcqqtxtVBYWjhPRfu\",\n \"type\":
\"function\",\n \"function\": {\n \"name\": \"search\",\n
\ \"arguments\": \"{\\\"query\\\":\\\"Python tutorials\\\"}\"\n
\ }\n }\n ],\n \"refusal\": null,\n \"annotations\":
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 48,\n \"completion_tokens\":
14,\n \"total_tokens\": 62,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
\"default\",\n \"system_fingerprint\": \"fp_51db84afab\"\n}\n"
headers: {}
status:
code: 200
message: OK
- request:
body: '{"messages":[{"role":"user","content":"Search for Python tutorials"},{"role":"assistant","tool_calls":[{"type":"function","function":{"name":"search","arguments":"{\"query\":
\"Python tutorials\"}"},"id":"call_BjOwGgWvcqqtxtVBYWjhPRfu"}]},{"role":"tool","content":"{''results'':
[\"Found results for ''Python tutorials'' (user: Alice)\"]}","tool_call_id":"call_BjOwGgWvcqqtxtVBYWjhPRfu"}],"model":"gpt-4o-mini","n":1,"stream":false,"tools":[{"type":"function","function":{"name":"search","description":"Search
documents","parameters":{"type":"object","properties":{"query":{"type":"string"},"user_context":{"type":"string"}},"required":["query"]}}}]}'
headers: {}
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: "{\n \"id\": \"chatcmpl-CWleuO1zD8b4RnjEM1j1FOmAKwpQy\",\n \"object\":
\"chat.completion\",\n \"created\": 1761925732,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
\"assistant\",\n \"content\": \"I found some results for \\\"Python
tutorials.\\\" Would you like more specific information about them?\",\n \"refusal\":
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
87,\n \"completion_tokens\": 19,\n \"total_tokens\": 106,\n \"prompt_tokens_details\":
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
\"default\",\n \"system_fingerprint\": \"fp_51db84afab\"\n}\n"
headers: {}
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
interactions:
- request:
body: '{"messages":[{"role":"user","content":"Search for Python tutorials"}],"model":"gpt-4o-mini","n":1,"stream":false,"tools":[{"type":"function","function":{"name":"search","description":"Search
documents","parameters":{"type":"object","properties":{"query":{"type":"string"},"user_context":{"type":"string"}},"required":["query"]}}}]}'
headers: {}
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: "{\n \"id\": \"chatcmpl-CWletHyGzapkvp0MG02ee7Jwn1x7B\",\n \"object\":
\"chat.completion\",\n \"created\": 1761925731,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
\ \"id\": \"call_BjOwGgWvcqqtxtVBYWjhPRfu\",\n \"type\":
\"function\",\n \"function\": {\n \"name\": \"search\",\n
\ \"arguments\": \"{\\\"query\\\":\\\"Python tutorials\\\"}\"\n
\ }\n }\n ],\n \"refusal\": null,\n \"annotations\":
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"tool_calls\"\n
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 48,\n \"completion_tokens\":
14,\n \"total_tokens\": 62,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
\"default\",\n \"system_fingerprint\": \"fp_51db84afab\"\n}\n"
headers: {}
status:
code: 200
message: OK
- request:
body: '{"messages":[{"role":"user","content":"Search for Python tutorials"},{"role":"assistant","tool_calls":[{"type":"function","function":{"name":"search","arguments":"{\"query\":
\"Python tutorials\"}"},"id":"call_BjOwGgWvcqqtxtVBYWjhPRfu"}]},{"role":"tool","content":"{''results'':
[\"Found results for ''Python tutorials'' (user: Alice)\"]}","tool_call_id":"call_BjOwGgWvcqqtxtVBYWjhPRfu"}],"model":"gpt-4o-mini","n":1,"stream":false,"tools":[{"type":"function","function":{"name":"search","description":"Search
documents","parameters":{"type":"object","properties":{"query":{"type":"string"},"user_context":{"type":"string"}},"required":["query"]}}}]}'
headers: {}
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: "{\n \"id\": \"chatcmpl-CWleuO1zD8b4RnjEM1j1FOmAKwpQy\",\n \"object\":
\"chat.completion\",\n \"created\": 1761925732,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
\"assistant\",\n \"content\": \"I found some results for \\\"Python
tutorials.\\\" Would you like more specific information about them?\",\n \"refusal\":
null,\n \"annotations\": []\n },\n \"logprobs\": null,\n
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
87,\n \"completion_tokens\": 19,\n \"total_tokens\": 106,\n \"prompt_tokens_details\":
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
\"default\",\n \"system_fingerprint\": \"fp_51db84afab\"\n}\n"
headers: {}
status:
code: 200
message: OK
version: 1
Loading