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
12 changes: 9 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,30 @@ RUST_DIR := voice
API_DIR := studio/api
WEB_DIR := studio/web

ifeq (, $(shell command -v uvx 2> /dev/null))
$(error "uvx could not be found. Please install uv (https://docs.astral.sh/uv/) before proceeding")
endif

PROTOC := uvx --python 3.12 --from grpcio-tools==1.80.0 python -m grpc_tools.protoc

# ── Protobuf ─────────────────────────────────────────────────────
.PHONY: proto
proto: ## Generate Python, TS, and ML stubs from proto definitions
# ML layer
mkdir -p $(ML_DIR)/stt
protoc \
$(PROTOC) \
--python_out=$(ML_DIR)/stt \
--pyi_out=$(ML_DIR)/stt \
--proto_path=$(PROTO_DIR) \
$(PROTO_DIR)/stt.proto
# Studio API layer
protoc \
$(PROTOC) \
--python_out=$(API_DIR)/app/schemas \
--pyi_out=$(API_DIR)/app/schemas \
--proto_path=$(PROTO_DIR) \
$(PROTO_DIR)/agent.proto
# Studio Web layer (TS interfaces)
protoc \
$(PROTOC) \
--plugin=protoc-gen-ts_proto=$(WEB_DIR)/node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=$(WEB_DIR)/src/lib/api \
--ts_proto_opt=esModuleInterop=true,forceLong=string,outputServices=false,outputJsonMethods=false,outputClientImpl=false,outputEncodeMethods=false,outputPartialMethods=false,outputTypeRegistry=false,onlyTypes=true,snakeToCamel=false \
Expand Down
16 changes: 13 additions & 3 deletions inference/stt/stt_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion inference/stt/stt_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
from collections.abc import Mapping as _Mapping
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

Expand Down
15 changes: 9 additions & 6 deletions proto/agent.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ message ToolDef {
bool cancel_on_barge_in = 4;
// If true, this tool has side effects
bool side_effect = 5;
// When true, the runtime passes the result through an LLM summarizer
// before feeding it back. Adds ~5-10s latency. Default false = truncate.
bool summarize_result = 6;
}

// A single node in the graph
message NodeDef {
string system_prompt = 1;
repeated string tools = 2; // keys referencing AgentGraphDef.tools
repeated string edges = 3; // node IDs this node can transfer to

optional string model = 4;
optional double temperature = 5;
optional uint32 max_tokens = 6;
Expand All @@ -66,23 +69,23 @@ message NodeDef {
message AgentGraphDef {
// The ID of the node to start with
string entry = 1;

// All nodes keyed by ID
map<string, NodeDef> nodes = 2;

// All tool definitions keyed by tool name
map<string, ToolDef> tools = 3;

// -- Agent-wide settings --
optional string language = 4; // ISO 639-1
optional string timezone = 5; // IANA timezone
optional string voice_id = 6; // Default TTS voice ID
optional string tts_provider = 7; // e.g. "elevenlabs"
optional string tts_model = 8; // e.g. "eleven_turbo_v2"

// Session recording configuration
optional RecordingConfig recording = 9;

// Envelope field for versions (e.g. "v3_graph")
optional string config_schema_version = 10;

Expand Down
14 changes: 12 additions & 2 deletions studio/api/app/agent_builder/edit_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@

# ── Tool field whitelist ─────────────────────────────────────────

_TOOL_FIELD_ALLOWED = {"description", "params", "script", "side_effect"}
_TOOL_FIELD_ALLOWED = {
"description",
"params",
"script",
"side_effect",
"summarize_result",
}

# ── Canonical field ordering ─────────────────────────────────────

Expand All @@ -46,7 +52,7 @@

_NODE_FIELD_ORDER = ["system_prompt", "greeting", "tools", "edges"]

_TOOL_FIELD_ORDER = ["description", "params", "script", "side_effect"]
_TOOL_FIELD_ORDER = ["description", "params", "script", "side_effect", "summarize_result"]


def _validate_string_list(value: Any, field_name: str) -> None:
Expand Down Expand Up @@ -158,6 +164,10 @@ def _validate_fields(self) -> UpsertTool:
self.fields["side_effect"], bool
):
raise ValueError("'side_effect' must be a boolean")
if "summarize_result" in self.fields and not isinstance(
self.fields["summarize_result"], bool
):
raise ValueError("'summarize_result' must be a boolean")
return self


Expand Down
48 changes: 44 additions & 4 deletions studio/api/app/api/agents.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Agent CRUD API routes."""

import asyncio
import copy
import uuid
from typing import Any

Expand Down Expand Up @@ -583,6 +584,9 @@ class AgentConfigPatch(BaseModel):
tts_provider: str | None = None
tts_model: str | None = None
gemini_live_model: str | None = None
# tool_id -> true/false
# null means "unset" (falls back to default truncate behavior)
tool_summarize_overrides: dict[str, bool | None] | None = None
regenerate_greeting: bool = False


Expand Down Expand Up @@ -610,10 +614,15 @@ async def patch_agent_config(
if not version:
raise HTTPException(status_code=404, detail="Active version not found")

config = dict(version.config_json)
patch = body.model_dump(exclude_unset=True, exclude={"regenerate_greeting"})
# Deep copy is required: shallow copy can mutate nested JSON objects in-place,
# which may prevent SQLAlchemy from detecting a JSONB change.
config = copy.deepcopy(version.config_json)
patch = body.model_dump(
exclude_unset=True, exclude={"regenerate_greeting", "tool_summarize_overrides"}
)
tool_summarize_overrides = body.tool_summarize_overrides
force_regen = body.regenerate_greeting
if not patch and not force_regen:
if not patch and not force_regen and tool_summarize_overrides is None:
raise HTTPException(status_code=400, detail="No fields to update")

# ── Language validation ──────────────────────────────────────────────
Expand Down Expand Up @@ -670,6 +679,28 @@ async def patch_agent_config(
for key, value in patch.items():
config[key] = value

# ── Per-tool summarize_result patch ───────────────────────────────────────
if tool_summarize_overrides is not None:
tools_obj = config.get("tools")
if not isinstance(tools_obj, dict):
raise HTTPException(
status_code=422,
detail="Invalid config: top-level 'tools' must be an object",
)

for tool_id, summarize in tool_summarize_overrides.items():
tool_def = tools_obj.get(tool_id)
if not isinstance(tool_def, dict):
raise HTTPException(
status_code=422,
detail=f"Unknown tool '{tool_id}' in tool_summarize_overrides",
)

if summarize is None:
tool_def.pop("summarize_result", None)
else:
tool_def["summarize_result"] = bool(summarize)

# ── Greeting regeneration ──────────────────────────────────────────
greeting_updated = False
new_greeting: str | None = None
Expand Down Expand Up @@ -704,6 +735,7 @@ async def patch_agent_config(
db,
agent_id=agent.id,
patch=patch,
tool_summarize_overrides=tool_summarize_overrides,
greeting_updated=greeting_updated,
new_greeting=new_greeting,
)
Expand Down Expand Up @@ -733,7 +765,8 @@ async def patch_agent_config(
async def _inject_config_change_event(
db: AsyncSession,
agent_id: uuid.UUID,
patch: dict[str, str],
patch: dict[str, Any],
tool_summarize_overrides: dict[str, bool | None] | None = None,
greeting_updated: bool = False,
new_greeting: str | None = None,
) -> None:
Expand Down Expand Up @@ -765,6 +798,13 @@ async def _inject_config_change_event(
else "Standard Pipeline"
)
changes.append(f"conversation mode set to {mode}")
if tool_summarize_overrides:
for tool_id, summarize in tool_summarize_overrides.items():
if summarize is None:
label = "auto"
else:
label = "enabled" if summarize else "disabled"
changes.append(f"{tool_id} AI summarization set to {label}")

if not changes:
return
Expand Down
48 changes: 29 additions & 19 deletions studio/api/app/schemas/agent_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading