mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-24 12:41:02 +02:00
Agent streaming
This commit is contained in:
parent
5d95c90120
commit
83e71db3ab
4 changed files with 170 additions and 45 deletions
|
|
@ -12,16 +12,18 @@ class AgentRequestTranslator(MessageTranslator):
|
||||||
state=data.get("state", None),
|
state=data.get("state", None),
|
||||||
group=data.get("group", None),
|
group=data.get("group", None),
|
||||||
history=data.get("history", []),
|
history=data.get("history", []),
|
||||||
user=data.get("user", "trustgraph")
|
user=data.get("user", "trustgraph"),
|
||||||
|
streaming=data.get("streaming", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
def from_pulsar(self, obj: AgentRequest) -> Dict[str, Any]:
|
def from_pulsar(self, obj: AgentRequest) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"question": obj.question,
|
"question": obj.question,
|
||||||
"state": obj.state,
|
"state": obj.state,
|
||||||
"group": obj.group,
|
"group": obj.group,
|
||||||
"history": obj.history,
|
"history": obj.history,
|
||||||
"user": obj.user
|
"user": obj.user,
|
||||||
|
"streaming": getattr(obj, "streaming", False)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,14 +35,36 @@ class AgentResponseTranslator(MessageTranslator):
|
||||||
|
|
||||||
def from_pulsar(self, obj: AgentResponse) -> Dict[str, Any]:
|
def from_pulsar(self, obj: AgentResponse) -> Dict[str, Any]:
|
||||||
result = {}
|
result = {}
|
||||||
if obj.answer:
|
|
||||||
result["answer"] = obj.answer
|
# Check if this is a streaming response (has chunk_type)
|
||||||
if obj.thought:
|
if hasattr(obj, 'chunk_type') and obj.chunk_type:
|
||||||
result["thought"] = obj.thought
|
result["chunk_type"] = obj.chunk_type
|
||||||
if obj.observation:
|
if obj.content:
|
||||||
result["observation"] = obj.observation
|
result["content"] = obj.content
|
||||||
|
result["end_of_message"] = getattr(obj, "end_of_message", False)
|
||||||
|
result["end_of_dialog"] = getattr(obj, "end_of_dialog", False)
|
||||||
|
else:
|
||||||
|
# Legacy format
|
||||||
|
if obj.answer:
|
||||||
|
result["answer"] = obj.answer
|
||||||
|
if obj.thought:
|
||||||
|
result["thought"] = obj.thought
|
||||||
|
if obj.observation:
|
||||||
|
result["observation"] = obj.observation
|
||||||
|
|
||||||
|
# Always include error if present
|
||||||
|
if hasattr(obj, 'error') and obj.error and obj.error.message:
|
||||||
|
result["error"] = {"message": obj.error.message, "code": obj.error.code}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def from_response_with_completion(self, obj: AgentResponse) -> Tuple[Dict[str, Any], bool]:
|
def from_response_with_completion(self, obj: AgentResponse) -> Tuple[Dict[str, Any], bool]:
|
||||||
"""Returns (response_dict, is_final)"""
|
"""Returns (response_dict, is_final)"""
|
||||||
return self.from_pulsar(obj), (obj.answer is not None)
|
# For streaming responses, check end_of_dialog
|
||||||
|
if hasattr(obj, 'chunk_type') and obj.chunk_type:
|
||||||
|
is_final = getattr(obj, 'end_of_dialog', False)
|
||||||
|
else:
|
||||||
|
# For legacy responses, check if answer is present
|
||||||
|
is_final = (obj.answer is not None)
|
||||||
|
|
||||||
|
return self.from_pulsar(obj), is_final
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
from pulsar.schema import Record, String, Array, Map
|
from pulsar.schema import Record, String, Array, Map, Boolean
|
||||||
|
|
||||||
from ..core.topic import topic
|
from ..core.topic import topic
|
||||||
from ..core.primitives import Error
|
from ..core.primitives import Error
|
||||||
|
|
@ -21,8 +21,16 @@ class AgentRequest(Record):
|
||||||
group = Array(String())
|
group = Array(String())
|
||||||
history = Array(AgentStep())
|
history = Array(AgentStep())
|
||||||
user = String() # User context for multi-tenancy
|
user = String() # User context for multi-tenancy
|
||||||
|
streaming = Boolean() # NEW: Enable streaming response delivery (default false)
|
||||||
|
|
||||||
class AgentResponse(Record):
|
class AgentResponse(Record):
|
||||||
|
# Streaming-first design
|
||||||
|
chunk_type = String() # "thought", "action", "observation", "answer", "error"
|
||||||
|
content = String() # The actual content (interpretation depends on chunk_type)
|
||||||
|
end_of_message = Boolean() # Current chunk type (thought/action/etc.) is complete
|
||||||
|
end_of_dialog = Boolean() # Entire agent dialog is complete
|
||||||
|
|
||||||
|
# Legacy fields (deprecated but kept for backward compatibility)
|
||||||
answer = String()
|
answer = String()
|
||||||
error = Error()
|
error = Error()
|
||||||
thought = String()
|
thought = String()
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ def output(text, prefix="> ", width=78):
|
||||||
|
|
||||||
async def question(
|
async def question(
|
||||||
url, question, flow_id, user, collection,
|
url, question, flow_id, user, collection,
|
||||||
plan=None, state=None, group=None, verbose=False
|
plan=None, state=None, group=None, verbose=False, streaming=True
|
||||||
):
|
):
|
||||||
|
|
||||||
if not url.endswith("/"):
|
if not url.endswith("/"):
|
||||||
|
|
@ -62,16 +62,17 @@ async def question(
|
||||||
"request": {
|
"request": {
|
||||||
"question": question,
|
"question": question,
|
||||||
"user": user,
|
"user": user,
|
||||||
"history": []
|
"history": [],
|
||||||
|
"streaming": streaming
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only add optional fields if they have values
|
# Only add optional fields if they have values
|
||||||
if state is not None:
|
if state is not None:
|
||||||
req["request"]["state"] = state
|
req["request"]["state"] = state
|
||||||
if group is not None:
|
if group is not None:
|
||||||
req["request"]["group"] = group
|
req["request"]["group"] = group
|
||||||
|
|
||||||
req = json.dumps(req)
|
req = json.dumps(req)
|
||||||
|
|
||||||
await ws.send(req)
|
await ws.send(req)
|
||||||
|
|
@ -89,14 +90,34 @@ async def question(
|
||||||
print("Ignore message")
|
print("Ignore message")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "thought" in obj["response"]:
|
response = obj["response"]
|
||||||
think(obj["response"]["thought"])
|
|
||||||
|
|
||||||
if "observation" in obj["response"]:
|
# Handle streaming format (new format with chunk_type)
|
||||||
observe(obj["response"]["observation"])
|
if "chunk_type" in response:
|
||||||
|
chunk_type = response["chunk_type"]
|
||||||
|
content = response.get("content", "")
|
||||||
|
|
||||||
if "answer" in obj["response"]:
|
if chunk_type == "thought":
|
||||||
print(obj["response"]["answer"])
|
think(content)
|
||||||
|
elif chunk_type == "observation":
|
||||||
|
observe(content)
|
||||||
|
elif chunk_type == "answer":
|
||||||
|
print(content)
|
||||||
|
elif chunk_type == "error":
|
||||||
|
raise RuntimeError(content)
|
||||||
|
else:
|
||||||
|
# Handle legacy format (backward compatibility)
|
||||||
|
if "thought" in response:
|
||||||
|
think(response["thought"])
|
||||||
|
|
||||||
|
if "observation" in response:
|
||||||
|
observe(response["observation"])
|
||||||
|
|
||||||
|
if "answer" in response:
|
||||||
|
print(response["answer"])
|
||||||
|
|
||||||
|
if "error" in response:
|
||||||
|
raise RuntimeError(response["error"])
|
||||||
|
|
||||||
if obj["complete"]: break
|
if obj["complete"]: break
|
||||||
|
|
||||||
|
|
@ -161,6 +182,12 @@ def main():
|
||||||
help=f'Output thinking/observations'
|
help=f'Output thinking/observations'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-streaming',
|
||||||
|
action="store_true",
|
||||||
|
help=f'Disable streaming (use legacy mode)'
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -176,6 +203,7 @@ def main():
|
||||||
state = args.state,
|
state = args.state,
|
||||||
group = args.group,
|
group = args.group,
|
||||||
verbose = args.verbose,
|
verbose = args.verbose,
|
||||||
|
streaming = not args.no_streaming,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,9 @@ class Processor(AgentService):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
# Check if streaming is enabled
|
||||||
|
streaming = getattr(request, 'streaming', False)
|
||||||
|
|
||||||
if request.history:
|
if request.history:
|
||||||
history = [
|
history = [
|
||||||
Action(
|
Action(
|
||||||
|
|
@ -215,12 +218,27 @@ class Processor(AgentService):
|
||||||
|
|
||||||
logger.debug(f"Think: {x}")
|
logger.debug(f"Think: {x}")
|
||||||
|
|
||||||
r = AgentResponse(
|
if streaming:
|
||||||
answer=None,
|
# Streaming format
|
||||||
error=None,
|
r = AgentResponse(
|
||||||
thought=x,
|
chunk_type="thought",
|
||||||
observation=None,
|
content=x,
|
||||||
)
|
end_of_message=True,
|
||||||
|
end_of_dialog=False,
|
||||||
|
# Legacy fields for backward compatibility
|
||||||
|
answer=None,
|
||||||
|
error=None,
|
||||||
|
thought=x,
|
||||||
|
observation=None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Legacy format
|
||||||
|
r = AgentResponse(
|
||||||
|
answer=None,
|
||||||
|
error=None,
|
||||||
|
thought=x,
|
||||||
|
observation=None,
|
||||||
|
)
|
||||||
|
|
||||||
await respond(r)
|
await respond(r)
|
||||||
|
|
||||||
|
|
@ -228,12 +246,27 @@ class Processor(AgentService):
|
||||||
|
|
||||||
logger.debug(f"Observe: {x}")
|
logger.debug(f"Observe: {x}")
|
||||||
|
|
||||||
r = AgentResponse(
|
if streaming:
|
||||||
answer=None,
|
# Streaming format
|
||||||
error=None,
|
r = AgentResponse(
|
||||||
thought=None,
|
chunk_type="observation",
|
||||||
observation=x,
|
content=x,
|
||||||
)
|
end_of_message=True,
|
||||||
|
end_of_dialog=False,
|
||||||
|
# Legacy fields for backward compatibility
|
||||||
|
answer=None,
|
||||||
|
error=None,
|
||||||
|
thought=None,
|
||||||
|
observation=x,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Legacy format
|
||||||
|
r = AgentResponse(
|
||||||
|
answer=None,
|
||||||
|
error=None,
|
||||||
|
thought=None,
|
||||||
|
observation=x,
|
||||||
|
)
|
||||||
|
|
||||||
await respond(r)
|
await respond(r)
|
||||||
|
|
||||||
|
|
@ -287,11 +320,25 @@ class Processor(AgentService):
|
||||||
else:
|
else:
|
||||||
f = json.dumps(act.final)
|
f = json.dumps(act.final)
|
||||||
|
|
||||||
r = AgentResponse(
|
if streaming:
|
||||||
answer=act.final,
|
# Streaming format - mark as final dialog
|
||||||
error=None,
|
r = AgentResponse(
|
||||||
thought=None,
|
chunk_type="answer",
|
||||||
)
|
content=f,
|
||||||
|
end_of_message=True,
|
||||||
|
end_of_dialog=True,
|
||||||
|
# Legacy fields for backward compatibility
|
||||||
|
answer=act.final,
|
||||||
|
error=None,
|
||||||
|
thought=None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Legacy format
|
||||||
|
r = AgentResponse(
|
||||||
|
answer=act.final,
|
||||||
|
error=None,
|
||||||
|
thought=None,
|
||||||
|
)
|
||||||
|
|
||||||
await respond(r)
|
await respond(r)
|
||||||
|
|
||||||
|
|
@ -336,14 +383,32 @@ class Processor(AgentService):
|
||||||
|
|
||||||
logger.debug("Send error response...")
|
logger.debug("Send error response...")
|
||||||
|
|
||||||
r = AgentResponse(
|
error_obj = Error(
|
||||||
error=Error(
|
type = "agent-error",
|
||||||
type = "agent-error",
|
message = str(e),
|
||||||
message = str(e),
|
|
||||||
),
|
|
||||||
response=None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check if streaming was enabled (may not be set if error occurred early)
|
||||||
|
streaming = getattr(request, 'streaming', False) if 'request' in locals() else False
|
||||||
|
|
||||||
|
if streaming:
|
||||||
|
# Streaming format
|
||||||
|
r = AgentResponse(
|
||||||
|
chunk_type="error",
|
||||||
|
content=str(e),
|
||||||
|
end_of_message=True,
|
||||||
|
end_of_dialog=True,
|
||||||
|
# Legacy fields for backward compatibility
|
||||||
|
error=error_obj,
|
||||||
|
response=None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Legacy format
|
||||||
|
r = AgentResponse(
|
||||||
|
error=error_obj,
|
||||||
|
response=None,
|
||||||
|
)
|
||||||
|
|
||||||
await respond(r)
|
await respond(r)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue