Server Tool¶
server-tool demonstrates two client-selected, server-executed tools through
one standard Responses request:
lgos_package_versionuses the Responses custom-tool shape but is executed by LGOS rather than the client. Its free-form input names a supported Python distribution installed in the API runtime.web_searchalways uses the standard OpenAI declaration. The graph can run it through a self-managed SearXNG or Degoog endpoint, or use an upstream OpenAI Responses model's native search as its backend.
The graph is a model-backed workflow with no persistence. Clients own conversation history and opt in to either tool on each request.
The Chainlit and Open WebUI demos expose both switches. Open WebUI's bundled Function carries a narrow compatibility shim for the custom-tool output union omitted by the pinned host SDK; it does not replace packages in the official image.
LangGraph Topology¶
graph TD;
__start__ -.-> answer;
__start__ -.-> select_tools;
select_tools -. no calls .-> answer;
select_tools -. calls .-> tools;
tools --> answer;
answer --> __end__;
The StateGraph separates private tool selection from public answer generation.
Its tools node delegates execution to a native ToolNode containing only the
tools selected for this request. Both search backends return text and source
metadata through the same tool. The answer node streams text and then adds
citation annotations for exact source links retained in that text.
Request Flow¶
sequenceDiagram
participant UI as Chainlit / Open WebUI
box LGOS API process
participant API as /v1/responses
participant Graph as server-tool graph
participant Tools as package metadata / web_search
end
participant Search as SearXNG / Degoog
participant Model as Upstream model
UI->>API: input + explicit tools
API->>API: Validate graph allowlist
API->>Graph: Messages + GraphRequest context
Graph->>Model: Private tool selection
Model-->>Graph: Tool calls or none
Graph-->>API: Tool-call updates + status events
opt Tool calls requested
Graph->>Tools: Execute selected tools
alt Installed package version
Tools->>Tools: Read distribution metadata
else HTTP web search
Tools->>Search: GET configured URL?q=...&format=json
Search-->>Tools: JSON results
else upstream OpenAI web search
Tools->>Model: Native web_search request
Model-->>Tools: Search summary + citations
end
Tools-->>Graph: Tool results + source metadata
Graph-->>API: Tool-result updates
end
Graph->>Model: Generate answer from collected results
Model-->>Graph: Answer tokens
Graph-->>API: LangGraph messages
API-->>UI: Native tool items, progress commentary, answer tokens
Graph-->>API: Final message with citation annotations
API-->>UI: Annotations + response.completed
- The client includes
{"type":"custom","name":"lgos_package_version"}and/or{"type":"web_search"}intools. There is no tool discovery through the model endpoint and no automatic server-side enablement. - LGOS recognizes registered custom names as server selectors. Unregistered
functions remain client-owned; this demo rejects those because it handles
only its configured tools.
autolets the model decide,requiredrequires a supplied tool, and a named custom choice can force package lookup. Omitting a tool, or usingtool_choice="none", leaves it unavailable. - The graph binds the selected LangChain
@custom_tooland@toolobjects inselect_toolsand supplies the same selection toToolNodefor execution. The model selects every needed tool in one turn;ToolNodeexecutes parallel calls using LangGraph's normal behavior. Requests without enabled tools go directly toanswer. - Package lookup returns
custom_tool_call,custom_tool_call_output, and a message. Search returnsweb_search_callfollowed by a message with standard URL-citation annotations. Clients replay the complete output and execute onlyfunction_callitems. Backend-specific search payloads remain private.
Streaming combines native LangGraph updates for completed tool calls/results,
custom for existing status_event() progress, and messages for answer tokens.
The answer model streams normally. Private selection calls use
ChatOpenAI(disable_streaming=True), so intermediate model text never enters the
public answer.
The graph declares GraphFeature.CLIENT_EVENTS; progress appears as Responses
commentary. Non-streaming responses omit this transient commentary.
The selection stage only gathers information; the answer stage has no bound
tools. Citations are attached after generation without changing or buffering
the streamed text. Clients rendering the transcript select
phase="final_answer" messages and present commentary separately.
The http backend is one JSON GET implemented with the demo's HTTPX2 client.
Both SearXNG and Degoog return the small results shape the
adapter consumes, so there are no provider classes. The adapter validates
HTTP(S) result URLs, removes duplicates, limits the result set, and gives the
model compact title, URL, and snippet text. Search content is treated as
untrusted data. The openai backend makes a private model call with
{"type":"web_search"} and tool_choice="required". Its cited URLs become
the same source metadata consumed by the answer node. Internal provider calls
stay private; the outer response records the graph's web_search invocation.
Try It¶
Start the demo API. The default
DEMO_API_WEB_SEARCH_BACKEND=http uses the URL in
DEMO_API_WEB_SEARCH_URL. Point it at either endpoint:
# SearXNG (JSON output must be enabled)
DEMO_API_WEB_SEARCH_URL=https://searxng.example.com/search
# Degoog
DEMO_API_WEB_SEARCH_URL=https://degoog.example.com/api/search
To use an upstream OpenAI Responses model's native search instead:
The URL is ignored in openai mode. The configured upstream model or gateway
must support the OpenAI Responses web_search tool.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:3004/v1", api_key="DUMMY")
response = client.responses.create(
model="server-tool",
input=(
"Compare this server's installed LangGraph and OpenAI SDK versions "
"with their latest stable releases."
),
store=False,
tools=[
{"type": "custom", "name": "lgos_package_version"},
{"type": "web_search"},
],
tool_choice="required",
parallel_tool_calls=True,
)
for item in response.output:
print(item.type)
print(response.output_text)
lgos_package_version accepts langgraph-openai-serve, langgraph,
langchain, langchain-openai, or openai. This allowlist keeps arbitrary
environment inventory private. The tool reads installed distribution metadata;
it does not embed version numbers that can go stale. In the combined request,
web_search supplies current public release information.
To exercise only web search, use:
With only web_search supplied, required forces a search. In Chainlit, select
the server-tool profile and enable either tool in chat settings. The generated
Open WebUI Workspace Model exposes the same choices as Chat Variable checkboxes.
The declarations are fixed client knowledge; neither UI discovers tool names
from the server.
One public contract
The package tool's name selects its server-owned implementation and input
contract. It uses standard Responses custom-tool items, while LGOS
deliberately owns execution instead of returning the call for client
execution.
web_search uses the standard built-in declaration and output shape with
every backend. The LGOS graph chooses where search runs; the client never
names SearXNG, Degoog, or OpenAI as a provider. See the
server-tool contract.
The implementation uses standard OpenAI custom-tool call and output shapes,
web-search response shapes,
LangGraph ToolNode and tool routing,
LangChain's OpenAI built-in tools,
the SearXNG Search API,
Degoog Search API, and
Python distribution metadata.