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
24 changes: 15 additions & 9 deletions .github/workflows/prod-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
id: build_setup
run: |
echo "build_start=$(date '+%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
echo "release_tag=RELEASE.$(date -u '+%Y-%m-%dT%H-%M-%SZ')" >> $GITHUB_OUTPUT
echo "commit_author=$(git log -1 --pretty=format:'%an')" >> $GITHUB_OUTPUT
echo "commit_email=$(git log -1 --pretty=format:'%ae')" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=format:'%s')" >> $GITHUB_OUTPUT
Expand All @@ -35,11 +36,17 @@ jobs:

- name: Build and push Service Docker image
run: |
docker build service -t registry.sciol.ac.cn/sciol/xyzen-service:latest --push
docker buildx build service \
-t registry.sciol.ac.cn/sciol/xyzen-service:latest \
-t registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.release_tag }} \
--push

- name: Build and push Web Docker image
run: |
docker build web -t registry.sciol.ac.cn/sciol/xyzen-web:latest --push
docker buildx build web \
-t registry.sciol.ac.cn/sciol/xyzen-web:latest \
-t registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.release_tag }} \
--push

- name: Set up kubeconfig
env:
Expand All @@ -51,11 +58,10 @@ jobs:

- name: Rolling update deployments
run: |
kubectl rollout restart deployment xyzen -n bohrium
kubectl rollout restart deployment xyzen-celery -n bohrium
kubectl rollout restart deployment xyzen -n sciol
kubectl rollout restart deployment xyzen-web -n sciol

kubectl -n bohrium set image deployment/xyzen *=registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.release_tag }}
kubectl -n bohrium set image deployment/xyzen-celery *=registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.release_tag }}
kubectl -n sciol set image deployment/xyzen *=registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.release_tag }}
kubectl -n sciol set image deployment/xyzen-web *=registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.release_tag }}

- name: Calculate build duration
if: always()
Expand Down Expand Up @@ -115,5 +121,5 @@ jobs:
commit_sha: ${{ steps.build_setup.outputs.commit_sha }}
commit_sha_short: ${{ steps.build_setup.outputs.commit_sha_short }}
commit_date: ${{ steps.build_setup.outputs.commit_date }}
service_image: 'registry.sciol.ac.cn/sciol/xyzen-service:latest'
web_image: 'registry.sciol.ac.cn/sciol/xyzen-web:latest'
service_image: 'registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.release_tag }}'
web_image: 'registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.release_tag }}'
21 changes: 14 additions & 7 deletions .github/workflows/test-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
id: build_setup
run: |
echo "build_start=$(date '+%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
echo "beta_tag=BETA.$(date -u '+%Y-%m-%dT%H-%M-%SZ')" >> $GITHUB_OUTPUT
echo "commit_author=$(git log -1 --pretty=format:'%an')" >> $GITHUB_OUTPUT
echo "commit_email=$(git log -1 --pretty=format:'%ae')" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=format:'%s')" >> $GITHUB_OUTPUT
Expand All @@ -35,11 +36,17 @@ jobs:

- name: Build and push Service Docker image
run: |
docker build service -t registry.sciol.ac.cn/sciol/xyzen-service:test --push
docker buildx build service \
-t registry.sciol.ac.cn/sciol/xyzen-service:test \
-t registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.beta_tag }} \
--push

- name: Build and push Web Docker image
run: |
docker build web -t registry.sciol.ac.cn/sciol/xyzen-web:test --push
docker buildx build web \
-t registry.sciol.ac.cn/sciol/xyzen-web:test \
-t registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.beta_tag }} \
--push

- name: Download Let's Encrypt CA
run: curl -o ca.crt https://letsencrypt.org/certs/isrgrootx1.pem
Expand All @@ -50,19 +57,19 @@ jobs:
--server=${{ secrets.SCIENCEOL_K8S_SERVER_URL }} \
--token=${{ secrets.SCIENCEOL_K8S_ADMIN_TOKEN }} \
--certificate-authority=ca.crt \
rollout restart deployment xyzen -n bohrium
set image deployment/xyzen -n bohrium *=registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.beta_tag }}

kubectl \
--server=${{ secrets.SCIENCEOL_K8S_SERVER_URL }} \
--token=${{ secrets.SCIENCEOL_K8S_ADMIN_TOKEN }} \
--certificate-authority=ca.crt \
rollout restart deployment xyzen-web -n bohrium
set image deployment/xyzen-web -n bohrium *=registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.beta_tag }}

kubectl \
--server=${{ secrets.SCIENCEOL_K8S_SERVER_URL }} \
--token=${{ secrets.SCIENCEOL_K8S_ADMIN_TOKEN }} \
--certificate-authority=ca.crt \
rollout restart deployment xyzen-celery -n bohrium
set image deployment/xyzen-celery -n bohrium *=registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.beta_tag }}

- name: Calculate build duration
if: always()
Expand Down Expand Up @@ -122,5 +129,5 @@ jobs:
commit_sha: ${{ steps.build_setup.outputs.commit_sha }}
commit_sha_short: ${{ steps.build_setup.outputs.commit_sha_short }}
commit_date: ${{ steps.build_setup.outputs.commit_date }}
service_image: 'registry.sciol.ac.cn/sciol/xyzen-service:test'
web_image: 'registry.sciol.ac.cn/sciol/xyzen-web:test'
service_image: 'registry.sciol.ac.cn/sciol/xyzen-service:${{ steps.build_setup.outputs.beta_tag }}'
web_image: 'registry.sciol.ac.cn/sciol/xyzen-web:${{ steps.build_setup.outputs.beta_tag }}'
9 changes: 9 additions & 0 deletions .vscode/settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@
"reportImplicitStringConcatenation": "none"
},

"black-formatter.args": ["--line-length", "119"],

"flake8.args": [
"--max-line-length",
"119",
"--ignore",
"F401 W503 F541 F841 E226"
],

"todo-tree.highlights.defaultHighlight": {
"icon": "alert",
"type": "text",
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ types/

## Core Patterns

**Stateless Async Execution**: Decouple connection management (FastAPI) from heavy computation (Celery).
* State Offloading: API containers remain stateless. Ephemeral state (Queues, Pub/Sub channels) resides in Redis; persistent state in DB.
* Pub/Sub Bridge: Workers process tasks independently and broadcast results back to the specific API pod via Redis channels (chat:{connection_id}), enabling independent scaling of Web and Worker layers.

**No-Foreign-Key Database**: Use logical references (`user_id: str`) instead of FK constraints. Handle relationships in service layer.

**Repository Pattern**: Data access via `repos/` classes. Business logic in `core/` services.
Expand Down
59 changes: 59 additions & 0 deletions PRODUCT_INTERACTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Product Interaction Design: Spatial Agent Workspace

## Vision

To transform the user experience from managing a "list of tools" to entering a "digital workspace" where Agents are active, specialized assets. The interface balances high-density information with immersive focus states.

## Core Concepts

### 1. The Canvas (The Team View)

Instead of a flat list, Agents exist on an infinite 2D canvas.

- **Visuals**: Agents are represented as "Nodes" or "Bases" rather than simple list items.
- **States**:
- **Idle**: Subtle pulsing or static.
- **Working**: Glowing, animated data streams.
- **Collaborating**: Connecting lines between agents.
- **Interaction**: Pan and zoom to explore the team. Drag agents to cluster them by function (e.g., "Creative Team", "Dev Ops").

### 2. The Focus (The Deep Dive)

Transitions should be seamless, maintaining context while focusing on the task.

- **Action**: Clicking an Agent transitions from "Map View" to "Focus View".
- **Animation**: The camera smoothly zooms in to the specific Agent node. The background (other agents) blurs but remains visible in the periphery, providing a sense of "location" within the system.
- **Feedback**: The "Chat Window" isn't a separate page; it slides out from the Agent node itself, reinforcing that you are talking _to_ that specific entity.

### 3. The Workspace (The Chat Interface)

The chat interface is the primary daily driver, so it expands to occupy valuable screen real estate while keeping asset context available.

- **Layout**:
- **Left/Center (Chat)**: Wide, comfortable reading area. The primary focus.
- **Right (Context/Assets)**: Collapsible sidebar showing the Agent's specific "Memory", "Tools", and "Files".
- **Switching**:
- **Fast Switch**: A "Dock" or "Mini-map" allows jumping between recently used agents without zooming all the way out.
- **Zoom Out**: A gesture or button seamlessly pulls the camera back to the Canvas view to see the whole team.

## User Journey: "Hiring to Commanding"

1. **Enter Workspace**: User lands on the Canvas. See 5 Agents scattered. "Market Analysis" agent is glowing red (busy).
2. **Select**: User clicks "Market Analysis".
3. **Transition**: Screen zooms in. Background blurs. Chat window slides in from the right, occupying 70% of the screen.
4. **Engage**: User chats. Uploads a PDF.
5. **Multitask**: User needs "Copywriter".
- _Option A_: Zoom out (Esc), find Copywriter, Zoom in.
- _Option B (Fast)_: Click "Copywriter" from the Quick Dock. Camera pans laterally to the Copywriter node.
6. **Collaborate**: User drags a connecting line from "Market Analysis" output to "Copywriter".

## Technical Prototype

The accompanying `SpatialWorkspace` component demonstrates:

- **Spatial Layout**: Absolute positioning on a scalable surface.
- **Camera Logic**: Calculating translation and scale to center a target element.
- **Immersive Transition**: CSS/Motion transitions for smooth zooming.
- **Contextual Chat**: Sidebar entry upon focus.

This design elevates the Agent from a "row in a database" to a "teammate at a desk".
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Xyzen

AI Laboratory Server
Your Next Agent Capital!

[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/)
Expand Down
14 changes: 13 additions & 1 deletion service/app/agents/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,19 @@ async def llm_node(state: StateDict) -> StateDict:
messages = messages + [HumanMessage(content=prompt)]

# Invoke LLM
response = await llm.ainvoke(messages)
try:
response = await llm.ainvoke(messages)
except ValueError as e:
if "expected value at line 1 column 1" in str(e):
logger.error(
f"[LLM Node: {config.id}] JSON parsing failed. This often happens due to "
"Azure Content Filters or empty model response."
)
raise ValueError(
"Model execution failed: The provider returned an invalid response. "
"This may be due to safety filters blocking the content."
) from e
raise

# Handle structured output
if structured_model and isinstance(response, BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions service/app/api/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from .agents import router as agents_router
from .auth import router as auth_router
from .avatar import router as avatar_router
from .checkin import router as checkin_router
from .files import router as files_router
from .folders import router as folders_router
Expand Down Expand Up @@ -89,3 +90,4 @@ async def root() -> RootResponse:
v1_router.include_router(folders_router, prefix="/folders")
v1_router.include_router(knowledge_sets_router, prefix="/knowledge-sets")
v1_router.include_router(marketplace_router, prefix="/marketplace")
v1_router.include_router(avatar_router, prefix="/avatar")
Loading
Loading