Skip to content

SDKs and frameworks

Give your LangChain or LangGraph agent a native telem_search tool. Inside a LangGraph graph, the tool attaches query lineage to every search.

Using a coding agent? Find the one you use on Integrations.

create_telem_search_tool(client) builds a native LangChain tool that uses a Telem client. The model sets the query and, when the task needs it, a date window. LangGraph injects its ToolRuntime as a hidden second argument, and the tool reads the thread and the messages from it.

Terminal window
pip install "telem-sdk[langchain]"

Create an API key under API keys in the Telem console. The guided installer writes it to ~/.telem/credentials.json, which the client reads. You can also set it as TELEM_API_KEY, or pass it in code. See API keys for the full rules.

Add the tool to a tool node in your graph, and give each conversation a thread_id when you invoke the graph:

from langgraph.prebuilt import ToolNode
from telem import Telem
from telem.integrations.langchain import create_telem_search_tool
client = Telem() # environment, then ~/.telem/credentials.json
search = create_telem_search_tool(client, num_results=5)
builder.add_node("tools", ToolNode([search])) # builder: your StateGraph
graph = builder.compile()
result = graph.invoke(
{"messages": [...]},
config={"configurable": {"thread_id": chat_id}},
)

The tool answers with a ToolMessage. Its content is the text the model reads, and its artifact is the typed SearchResponse. Search options such as tier, providers_include, providers_exclude and num_results pass through to every search() the tool makes. See Python SDK.

create_telem_search_tool() also takes goal, what the conversation is for, and context, why the searches are made. Telem stores both with each search. The model cannot change them for one search: the tool sends the same text every time.

The tool gives the model no way to read a page. Your own code can still read pages with client.fetch(): see the Fetch tab on Python SDK.

The tool works the same with a sync Telem client (.invoke) or an async AsyncTelem client (.ainvoke). Called directly, outside a graph (search.invoke(...)), it sends only the query, with no lineage.

To start a child graph as a subagent, build its config with create_telem_child_config(), from the parent tool that starts it. The child’s searches then appear in the console under the agent that started it:

from telem.integrations.langchain import create_telem_child_config
child_config = create_telem_child_config(
parent_runtime,
child_thread_id="child-1",
)
child_graph.invoke({"messages": [...]}, config=child_config)

The config records the parent’s messages at that moment, and gives the child a new thread_id that you choose. Call it when you start the child, not before. The record is only as current as the parent’s state when you call it.

configurable.thread_id is the conversation id: one thread is one conversation. A child config from create_telem_child_config carries the child’s own. Without a thread_id, the tool uses the first message’s id, and then the tool call id.

Never put thread state in a shared tool closure. ToolRuntime is injected for each invocation. So one tool object is safe to reuse across concurrent threads. If you store thread_id in a closure, one thread’s identity leaks into the requests of another thread.

Was this page helpful?